@volksverpetzer/ui-web 0.7.0 → 0.9.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/Button.css +1 -0
- package/dist/Input.css +16 -12
- package/dist/LinkButton.css +37 -0
- package/dist/LinkButton.d.ts +21 -0
- package/dist/LinkButton.js +26 -0
- package/dist/ThemeToggle.css +6 -0
- package/dist/ThemeToggle.d.ts +9 -0
- package/dist/ThemeToggle.js +75 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/styles.css +62 -12
- package/package.json +2 -2
package/dist/Button.css
CHANGED
package/dist/Input.css
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
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
|
+
*/
|
|
1
10
|
.vvp-ui-input {
|
|
2
11
|
display: block;
|
|
3
12
|
box-sizing: border-box;
|
|
4
13
|
width: 100%;
|
|
5
14
|
min-width: 0;
|
|
6
|
-
border-radius: var(--vvp-radius-
|
|
7
|
-
border: 1px solid var(--vvp-surface
|
|
15
|
+
border-radius: var(--vvp-radius-md, 12px);
|
|
16
|
+
border: 1px solid var(--vvp-surface, #e2f0f5);
|
|
8
17
|
background: var(--vvp-background, #fff);
|
|
9
18
|
color: var(--vvp-text, #111);
|
|
10
|
-
padding: var(--vvp-spacing-
|
|
19
|
+
padding: var(--vvp-spacing-lg, 16px);
|
|
11
20
|
font-family: inherit;
|
|
12
21
|
font-size: var(--vvp-font-size-base, 16px);
|
|
13
22
|
line-height: 1.4;
|
|
14
|
-
transition:
|
|
15
|
-
border-color 0.15s ease,
|
|
16
|
-
box-shadow 0.15s ease;
|
|
23
|
+
transition: border-color 0.15s ease;
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
.vvp-ui-input::placeholder {
|
|
@@ -21,10 +28,8 @@
|
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
.vvp-ui-input:focus-visible {
|
|
24
|
-
outline:
|
|
25
|
-
|
|
26
|
-
box-shadow: 0 0 0 3px
|
|
27
|
-
color-mix(in srgb, var(--vvp-primary, #1b7194) 25%, transparent);
|
|
31
|
+
outline: 2px solid var(--vvp-primary-muted, #3893c0);
|
|
32
|
+
outline-offset: 2px;
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
.vvp-ui-input:disabled {
|
|
@@ -43,6 +48,5 @@
|
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
.vvp-ui-input[aria-invalid="true"]:focus-visible {
|
|
46
|
-
|
|
47
|
-
color-mix(in srgb, var(--vvp-error, #c62828) 25%, transparent);
|
|
51
|
+
outline-color: var(--vvp-error, #c62828);
|
|
48
52
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* LinkButton's fixed look: a subtle bordered "pill card" with a soft
|
|
3
|
+
* drop shadow, distinct from Button's ghost variant (see LinkButton.tsx
|
|
4
|
+
* for why). Colors are expressed via the same --vvp-* tokens Button.css
|
|
5
|
+
* uses, so this stays in sync with the shared palette automatically.
|
|
6
|
+
*
|
|
7
|
+
* The shadow comes from @volksverpetzer/design-tokens'
|
|
8
|
+
* --vvp-elevation-accent-* — the one brand-tinted exception to that
|
|
9
|
+
* package's otherwise-neutral elevation scale (see elevation-accent.json).
|
|
10
|
+
* The literal color-mix() here is only a fallback for apps that haven't
|
|
11
|
+
* loaded elevation-accent.css; it must stay in sync with that token.
|
|
12
|
+
*/
|
|
13
|
+
.vvp-ui-linkbtn {
|
|
14
|
+
border-radius: var(--vvp-radius-md, 12px);
|
|
15
|
+
background: transparent;
|
|
16
|
+
color: var(--vvp-primary, #1b7194);
|
|
17
|
+
border: 1.5px solid var(--vvp-surface, #e2f0f5);
|
|
18
|
+
box-shadow: var(
|
|
19
|
+
--vvp-elevation-accent-rest,
|
|
20
|
+
0px 8px 20px color-mix(in srgb, var(--vvp-primary-muted, #3893c0) 6%, transparent)
|
|
21
|
+
);
|
|
22
|
+
transition:
|
|
23
|
+
transform 120ms ease,
|
|
24
|
+
box-shadow 120ms ease,
|
|
25
|
+
border-color 120ms ease,
|
|
26
|
+
background 120ms ease;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.vvp-ui-linkbtn:hover {
|
|
30
|
+
transform: translateY(-1px);
|
|
31
|
+
border-color: var(--vvp-primary-muted, #3893c0);
|
|
32
|
+
background: var(--vvp-surface, #e2f0f5);
|
|
33
|
+
box-shadow: var(
|
|
34
|
+
--vvp-elevation-accent-hover,
|
|
35
|
+
0px 8px 20px color-mix(in srgb, var(--vvp-primary-muted, #3893c0) 18%, transparent)
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AnchorHTMLAttributes, ReactNode } from "react";
|
|
2
|
+
import type { ButtonSize } from "./Button";
|
|
3
|
+
import "./Button.css";
|
|
4
|
+
import "./LinkButton.css";
|
|
5
|
+
export interface LinkButtonProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
6
|
+
href: string;
|
|
7
|
+
size?: ButtonSize;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Anchor-based counterpart to `Button` — for navigation (external links,
|
|
12
|
+
* downloads), where an actual link is the correct element, not a button
|
|
13
|
+
* with an onClick that changes location.
|
|
14
|
+
*
|
|
15
|
+
* Unlike `Button`, this has no `variant` choice: a link is inherently a
|
|
16
|
+
* secondary/tertiary action, never a page's primary CTA, so it always
|
|
17
|
+
* renders the one bordered, low-emphasis look (see LinkButton.css) rather
|
|
18
|
+
* than reusing `Button`'s ghost variant directly — `Button`'s ghost shape
|
|
19
|
+
* is shared with `ThemeToggle`, which needs its own pill/circle geometry.
|
|
20
|
+
*/
|
|
21
|
+
export declare const LinkButton: import("react").ForwardRefExoticComponent<LinkButtonProps & import("react").RefAttributes<HTMLAnchorElement>>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import "./Button.css";
|
|
4
|
+
import "./LinkButton.css";
|
|
5
|
+
/**
|
|
6
|
+
* Anchor-based counterpart to `Button` — for navigation (external links,
|
|
7
|
+
* downloads), where an actual link is the correct element, not a button
|
|
8
|
+
* with an onClick that changes location.
|
|
9
|
+
*
|
|
10
|
+
* Unlike `Button`, this has no `variant` choice: a link is inherently a
|
|
11
|
+
* secondary/tertiary action, never a page's primary CTA, so it always
|
|
12
|
+
* renders the one bordered, low-emphasis look (see LinkButton.css) rather
|
|
13
|
+
* than reusing `Button`'s ghost variant directly — `Button`'s ghost shape
|
|
14
|
+
* is shared with `ThemeToggle`, which needs its own pill/circle geometry.
|
|
15
|
+
*/
|
|
16
|
+
export const LinkButton = forwardRef(function LinkButton({ size = "md", className, children, ...rest }, ref) {
|
|
17
|
+
const classes = [
|
|
18
|
+
"vvp-ui-btn",
|
|
19
|
+
"vvp-ui-linkbtn",
|
|
20
|
+
`vvp-ui-btn--${size}`,
|
|
21
|
+
className,
|
|
22
|
+
]
|
|
23
|
+
.filter(Boolean)
|
|
24
|
+
.join(" ");
|
|
25
|
+
return (_jsx("a", { ref: ref, className: classes, ...rest, children: children }));
|
|
26
|
+
});
|
|
@@ -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
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export { Button, type ButtonProps, type ButtonVariant, type ButtonSize, } from "./Button";
|
|
2
|
+
export { LinkButton, type LinkButtonProps } from "./LinkButton";
|
|
2
3
|
export { Badge, type BadgeProps, type BadgeVariant } from "./Badge";
|
|
3
4
|
export { Card, type CardProps } from "./Card";
|
|
4
5
|
export { ProgressBar, type ProgressBarProps, type ProgressBarMilestone, } from "./ProgressBar";
|
|
5
6
|
export { Input, type InputProps } from "./Input";
|
|
6
7
|
export { Alert, type AlertProps, type AlertVariant } from "./Alert";
|
|
7
8
|
export { Slider, type SliderProps } from "./Slider";
|
|
9
|
+
export { ThemeToggle } from "./ThemeToggle";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export { Button, } from "./Button";
|
|
2
|
+
export { LinkButton } from "./LinkButton";
|
|
2
3
|
export { Badge } from "./Badge";
|
|
3
4
|
export { Card } from "./Card";
|
|
4
5
|
export { ProgressBar, } from "./ProgressBar";
|
|
5
6
|
export { Input } from "./Input";
|
|
6
7
|
export { Alert } from "./Alert";
|
|
7
8
|
export { Slider } from "./Slider";
|
|
9
|
+
export { ThemeToggle } from "./ThemeToggle";
|
package/dist/styles.css
CHANGED
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
cursor: pointer;
|
|
86
86
|
font-family: inherit;
|
|
87
87
|
line-height: 1;
|
|
88
|
+
text-decoration: none;
|
|
88
89
|
transition:
|
|
89
90
|
background-color 0.15s ease,
|
|
90
91
|
color 0.15s ease,
|
|
@@ -163,22 +164,29 @@
|
|
|
163
164
|
border: 1px solid var(--vvp-surface-input, #badde8);
|
|
164
165
|
}
|
|
165
166
|
|
|
167
|
+
/*
|
|
168
|
+
* radius/border-color/padding are pinned to whichever existing token lands
|
|
169
|
+
* closest to vvp_crowdfunding's donation-form input (the component's actual
|
|
170
|
+
* first real consumer, pre-dating this package): radius 14px sits exactly
|
|
171
|
+
* between md/lg, resolved to md since that's the scale's own "buttons and
|
|
172
|
+
* form controls" step; padding 14px/16px both round to lg; the border color
|
|
173
|
+
* (#d8e6ee) is closer to --vvp-surface than the likelier-sounding
|
|
174
|
+
* --vvp-surface-input by actual RGB distance (~16 vs ~32).
|
|
175
|
+
*/
|
|
166
176
|
.vvp-ui-input {
|
|
167
177
|
display: block;
|
|
168
178
|
box-sizing: border-box;
|
|
169
179
|
width: 100%;
|
|
170
180
|
min-width: 0;
|
|
171
|
-
border-radius: var(--vvp-radius-
|
|
172
|
-
border: 1px solid var(--vvp-surface
|
|
181
|
+
border-radius: var(--vvp-radius-md, 12px);
|
|
182
|
+
border: 1px solid var(--vvp-surface, #e2f0f5);
|
|
173
183
|
background: var(--vvp-background, #fff);
|
|
174
184
|
color: var(--vvp-text, #111);
|
|
175
|
-
padding: var(--vvp-spacing-
|
|
185
|
+
padding: var(--vvp-spacing-lg, 16px);
|
|
176
186
|
font-family: inherit;
|
|
177
187
|
font-size: var(--vvp-font-size-base, 16px);
|
|
178
188
|
line-height: 1.4;
|
|
179
|
-
transition:
|
|
180
|
-
border-color 0.15s ease,
|
|
181
|
-
box-shadow 0.15s ease;
|
|
189
|
+
transition: border-color 0.15s ease;
|
|
182
190
|
}
|
|
183
191
|
|
|
184
192
|
.vvp-ui-input::placeholder {
|
|
@@ -186,10 +194,8 @@
|
|
|
186
194
|
}
|
|
187
195
|
|
|
188
196
|
.vvp-ui-input:focus-visible {
|
|
189
|
-
outline:
|
|
190
|
-
|
|
191
|
-
box-shadow: 0 0 0 3px
|
|
192
|
-
color-mix(in srgb, var(--vvp-primary, #1b7194) 25%, transparent);
|
|
197
|
+
outline: 2px solid var(--vvp-primary-muted, #3893c0);
|
|
198
|
+
outline-offset: 2px;
|
|
193
199
|
}
|
|
194
200
|
|
|
195
201
|
.vvp-ui-input:disabled {
|
|
@@ -208,8 +214,45 @@
|
|
|
208
214
|
}
|
|
209
215
|
|
|
210
216
|
.vvp-ui-input[aria-invalid="true"]:focus-visible {
|
|
211
|
-
|
|
212
|
-
|
|
217
|
+
outline-color: var(--vvp-error, #c62828);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/*
|
|
221
|
+
* LinkButton's fixed look: a subtle bordered "pill card" with a soft
|
|
222
|
+
* drop shadow, distinct from Button's ghost variant (see LinkButton.tsx
|
|
223
|
+
* for why). Colors are expressed via the same --vvp-* tokens Button.css
|
|
224
|
+
* uses, so this stays in sync with the shared palette automatically.
|
|
225
|
+
*
|
|
226
|
+
* The shadow comes from @volksverpetzer/design-tokens'
|
|
227
|
+
* --vvp-elevation-accent-* — the one brand-tinted exception to that
|
|
228
|
+
* package's otherwise-neutral elevation scale (see elevation-accent.json).
|
|
229
|
+
* The literal color-mix() here is only a fallback for apps that haven't
|
|
230
|
+
* loaded elevation-accent.css; it must stay in sync with that token.
|
|
231
|
+
*/
|
|
232
|
+
.vvp-ui-linkbtn {
|
|
233
|
+
border-radius: var(--vvp-radius-md, 12px);
|
|
234
|
+
background: transparent;
|
|
235
|
+
color: var(--vvp-primary, #1b7194);
|
|
236
|
+
border: 1.5px solid var(--vvp-surface, #e2f0f5);
|
|
237
|
+
box-shadow: var(
|
|
238
|
+
--vvp-elevation-accent-rest,
|
|
239
|
+
0px 8px 20px color-mix(in srgb, var(--vvp-primary-muted, #3893c0) 6%, transparent)
|
|
240
|
+
);
|
|
241
|
+
transition:
|
|
242
|
+
transform 120ms ease,
|
|
243
|
+
box-shadow 120ms ease,
|
|
244
|
+
border-color 120ms ease,
|
|
245
|
+
background 120ms ease;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.vvp-ui-linkbtn:hover {
|
|
249
|
+
transform: translateY(-1px);
|
|
250
|
+
border-color: var(--vvp-primary-muted, #3893c0);
|
|
251
|
+
background: var(--vvp-surface, #e2f0f5);
|
|
252
|
+
box-shadow: var(
|
|
253
|
+
--vvp-elevation-accent-hover,
|
|
254
|
+
0px 8px 20px color-mix(in srgb, var(--vvp-primary-muted, #3893c0) 18%, transparent)
|
|
255
|
+
);
|
|
213
256
|
}
|
|
214
257
|
|
|
215
258
|
.vvp-ui-progress {
|
|
@@ -356,3 +399,10 @@
|
|
|
356
399
|
cursor: not-allowed;
|
|
357
400
|
}
|
|
358
401
|
|
|
402
|
+
.vvp-ui-theme-toggle {
|
|
403
|
+
position: fixed;
|
|
404
|
+
top: var(--vvp-spacing-lg, 16px);
|
|
405
|
+
right: var(--vvp-spacing-lg, 16px);
|
|
406
|
+
z-index: 50;
|
|
407
|
+
}
|
|
408
|
+
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volksverpetzer/ui-web",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Shared brand-visible React components (Button, Badge, Card, ProgressBar, Input, Alert) for vvp_link_shortener, vvp_divi5_extensions, and
|
|
3
|
+
"version": "0.9.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": {
|