@volksverpetzer/ui-web 0.12.1 → 0.13.1

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.
@@ -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/ToolPage.css CHANGED
@@ -132,6 +132,14 @@
132
132
  gap: var(--vvp-spacing-xs, 4px);
133
133
  }
134
134
 
135
+ /*
136
+ * The decorative background is 220px tall and fades out; without extra space
137
+ * the content slot (a card, form, list) starts on top of the jagged edge.
138
+ */
139
+ .vvp-ui-tool-page--with-background .vvp-ui-tool-page__content {
140
+ margin-top: var(--vvp-spacing-huge, 40px);
141
+ }
142
+
135
143
  .vvp-ui-tool-page__content {
136
144
  width: 100%;
137
145
  }
package/dist/ToolPage.js CHANGED
@@ -14,7 +14,13 @@ const GithubIcon = () => (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", wid
14
14
  * photo) instead of the abstract shape.
15
15
  */
16
16
  export function ToolPage({ icon, title, tags, children, helpText, githubUrl, background = true, headerImage, centered = false, className, ...rest }) {
17
- const classes = ["vvp-ui-tool-page", className].filter(Boolean).join(" ");
17
+ const classes = [
18
+ "vvp-ui-tool-page",
19
+ !headerImage && background && "vvp-ui-tool-page--with-background",
20
+ className,
21
+ ]
22
+ .filter(Boolean)
23
+ .join(" ");
18
24
  const innerClasses = [
19
25
  "vvp-ui-tool-page__inner",
20
26
  centered && "vvp-ui-tool-page__inner--centered",
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
@@ -8,4 +8,5 @@ export { Alert } from "./Alert";
8
8
  export { Slider } from "./Slider";
9
9
  export { ThemeToggle } from "./ThemeToggle";
10
10
  export { InputButton } from "./InputButton";
11
+ export { PillGroup, } from "./PillGroup";
11
12
  export { ToolPage } from "./ToolPage";
package/dist/styles.css CHANGED
@@ -309,6 +309,59 @@
309
309
  );
310
310
  }
311
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
+
312
365
  .vvp-ui-progress {
313
366
  width: 100%;
314
367
  text-align: center;
@@ -603,6 +656,14 @@
603
656
  gap: var(--vvp-spacing-xs, 4px);
604
657
  }
605
658
 
659
+ /*
660
+ * The decorative background is 220px tall and fades out; without extra space
661
+ * the content slot (a card, form, list) starts on top of the jagged edge.
662
+ */
663
+ .vvp-ui-tool-page--with-background .vvp-ui-tool-page__content {
664
+ margin-top: var(--vvp-spacing-huge, 40px);
665
+ }
666
+
606
667
  .vvp-ui-tool-page__content {
607
668
  width: 100%;
608
669
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volksverpetzer/ui-web",
3
- "version": "0.12.1",
3
+ "version": "0.13.1",
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.2.18",
31
- "react": "^19.2.3",
30
+ "@types/react": "^19.3.0",
31
+ "react": "^19.3.0",
32
32
  "typescript": "^6.0.3"
33
33
  },
34
34
  "scripts": {