@viraui/react 2026.10.0 → 2026.11.0-next.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.
Files changed (127) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +8 -0
  2. package/dist/catalog.json +12 -0
  3. package/dist/components/avatar/avatar.d.ts +6 -0
  4. package/dist/components/avatar/avatar.js +2 -2
  5. package/dist/components/badge/badge.css +1 -1
  6. package/dist/components/button/button.css +1 -1
  7. package/dist/components/dialog/dialog-sheet.d.ts +1 -1
  8. package/dist/components/gauge/arcs.d.ts +134 -0
  9. package/dist/components/gauge/arcs.js +145 -0
  10. package/dist/components/gauge/context.d.ts +134 -0
  11. package/dist/components/gauge/context.js +83 -0
  12. package/dist/components/gauge/control.d.ts +107 -0
  13. package/dist/components/gauge/control.js +223 -0
  14. package/dist/components/gauge/demos/_shared.d.ts +28 -0
  15. package/dist/components/gauge/demos/activity-ring.d.ts +9 -0
  16. package/dist/components/gauge/demos/air-quality.d.ts +9 -0
  17. package/dist/components/gauge/demos/analog-dial.d.ts +9 -0
  18. package/dist/components/gauge/demos/battery.d.ts +9 -0
  19. package/dist/components/gauge/demos/chronograph.d.ts +9 -0
  20. package/dist/components/gauge/demos/clock.d.ts +9 -0
  21. package/dist/components/gauge/demos/compass.d.ts +9 -0
  22. package/dist/components/gauge/demos/data-usage.d.ts +9 -0
  23. package/dist/components/gauge/demos/dimmer.d.ts +9 -0
  24. package/dist/components/gauge/demos/fuel.d.ts +9 -0
  25. package/dist/components/gauge/demos/heart-rate.d.ts +9 -0
  26. package/dist/components/gauge/demos/internet-speed.d.ts +9 -0
  27. package/dist/components/gauge/demos/nested-rings.d.ts +11 -0
  28. package/dist/components/gauge/demos/orrery.d.ts +12 -0
  29. package/dist/components/gauge/demos/power-meter.d.ts +9 -0
  30. package/dist/components/gauge/demos/progress-ring.d.ts +9 -0
  31. package/dist/components/gauge/demos/range.d.ts +10 -0
  32. package/dist/components/gauge/demos/saas-metric.d.ts +9 -0
  33. package/dist/components/gauge/demos/segments.d.ts +9 -0
  34. package/dist/components/gauge/demos/simple.d.ts +9 -0
  35. package/dist/components/gauge/demos/speed-fuel.d.ts +10 -0
  36. package/dist/components/gauge/demos/speedometer.d.ts +9 -0
  37. package/dist/components/gauge/demos/sprint.d.ts +9 -0
  38. package/dist/components/gauge/demos/sun-path.d.ts +9 -0
  39. package/dist/components/gauge/demos/system.d.ts +11 -0
  40. package/dist/components/gauge/demos/tachometer.d.ts +9 -0
  41. package/dist/components/gauge/demos/temperature.d.ts +9 -0
  42. package/dist/components/gauge/demos/thermostat.d.ts +9 -0
  43. package/dist/components/gauge/demos/timer.d.ts +9 -0
  44. package/dist/components/gauge/demos/volume.d.ts +10 -0
  45. package/dist/components/gauge/demos/wind.d.ts +9 -0
  46. package/dist/components/gauge/dot.d.ts +55 -0
  47. package/dist/components/gauge/dot.js +34 -0
  48. package/dist/components/gauge/gauge.css +1 -0
  49. package/dist/components/gauge/gauge.d.ts +84 -0
  50. package/dist/components/gauge/gauge.js +62 -0
  51. package/dist/components/gauge/gauge.module.js +24 -0
  52. package/dist/components/gauge/index.d.ts +15 -0
  53. package/dist/components/gauge/inset.d.ts +30 -0
  54. package/dist/components/gauge/inset.js +39 -0
  55. package/dist/components/gauge/math.d.ts +69 -0
  56. package/dist/components/gauge/math.js +135 -0
  57. package/dist/components/gauge/needle.d.ts +81 -0
  58. package/dist/components/gauge/needle.js +162 -0
  59. package/dist/components/gauge/scale.d.ts +44 -0
  60. package/dist/components/gauge/scale.js +21 -0
  61. package/dist/components/gauge/text.d.ts +79 -0
  62. package/dist/components/gauge/text.js +42 -0
  63. package/dist/components/gauge/ticks.d.ts +169 -0
  64. package/dist/components/gauge/ticks.js +124 -0
  65. package/dist/components/gauge/tooltip.d.ts +117 -0
  66. package/dist/components/gauge/tooltip.js +337 -0
  67. package/dist/components/gauge/transition.d.ts +60 -0
  68. package/dist/components/gauge/transition.js +129 -0
  69. package/dist/components/gauge/use-animated-value.d.ts +14 -0
  70. package/dist/components/gauge/use-animated-value.js +111 -0
  71. package/dist/components/gauge/utils.d.ts +51 -0
  72. package/dist/components/gauge/utils.js +80 -0
  73. package/dist/components/icon-meter/icon-meter.css +1 -0
  74. package/dist/components/icon-meter/icon-meter.d.ts +62 -0
  75. package/dist/components/icon-meter/icon-meter.js +105 -0
  76. package/dist/components/icon-meter/icon-meter.module.js +11 -0
  77. package/dist/components/icon-meter/index.d.ts +2 -0
  78. package/dist/components/icon-meter/utils.d.ts +22 -0
  79. package/dist/components/icon-meter/utils.js +40 -0
  80. package/dist/components/index.d.ts +2 -0
  81. package/dist/components/popover/popover-sheet.d.ts +1 -2
  82. package/dist/components/stack/stack.d.ts +9 -2
  83. package/dist/components/tabs/tabs.d.ts +1 -2
  84. package/dist/index.js +17 -1
  85. package/dist/internal/chrome-icons/chrome-icons.d.ts +4 -0
  86. package/dist/internal/chrome-icons/chrome-icons.js +12 -4
  87. package/dist/node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom.development.js +192 -0
  88. package/dist/node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom.production.js +156 -0
  89. package/dist/node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/index.js +22 -0
  90. package/dist/preflight.css +1 -1
  91. package/dist/specs/components/avatar/props.xml +3 -0
  92. package/dist/specs/components/avatar/usage.xml +1 -0
  93. package/dist/specs/components/badge/a11y.xml +1 -1
  94. package/dist/specs/components/button/props.xml +2 -2
  95. package/dist/specs/components/button/usage.xml +1 -1
  96. package/dist/specs/components/gauge/a11y.xml +8 -0
  97. package/dist/specs/components/gauge/meta.xml +38 -0
  98. package/dist/specs/components/gauge/patterns.xml +1158 -0
  99. package/dist/specs/components/gauge/props.xml +452 -0
  100. package/dist/specs/components/gauge/usage.xml +21 -0
  101. package/dist/specs/components/icon-button/props.xml +2 -2
  102. package/dist/specs/components/icon-button/usage.xml +1 -1
  103. package/dist/specs/components/icon-meter/a11y.xml +9 -0
  104. package/dist/specs/components/icon-meter/meta.xml +21 -0
  105. package/dist/specs/components/icon-meter/patterns.xml +23 -0
  106. package/dist/specs/components/icon-meter/props.xml +44 -0
  107. package/dist/specs/components/icon-meter/usage.xml +13 -0
  108. package/dist/specs/components/linear-progress/usage.xml +1 -0
  109. package/dist/specs/components/meter/meta.xml +1 -0
  110. package/dist/specs/components/meter/props.xml +1 -0
  111. package/dist/specs/components/meter/usage.xml +1 -0
  112. package/dist/specs/components/stack/patterns.xml +21 -21
  113. package/dist/specs/components/stack/props.xml +6 -6
  114. package/dist/specs/components/stack/usage.xml +4 -2
  115. package/dist/specs/components/text/patterns.xml +1 -1
  116. package/dist/specs/components/text/usage.xml +1 -1
  117. package/dist/specs/components/toggle-button/props.xml +1 -1
  118. package/dist/specs/components/toggle-button/usage.xml +1 -1
  119. package/dist/specs/components/toggle-text-button/props.xml +1 -1
  120. package/dist/specs/foundation/attributes.xml +3 -3
  121. package/dist/specs/foundation/modules.xml +1 -1
  122. package/dist/specs/foundation/theme-topics.xml +7 -2
  123. package/dist/specs/index.xml +21 -1
  124. package/dist/specs/patterns/compose-layout-first.xml +1 -1
  125. package/dist/specs/patterns/settings-panel.xml +1 -1
  126. package/dist/specs/patterns/settings-row.xml +1 -1
  127. package/package.json +2 -2
@@ -0,0 +1,111 @@
1
+ "use client";
2
+ import { easingFunction, resolveTransition, springPhysics, stepSpring } from "./transition.js";
3
+ import { useEffect, useRef, useState } from "react";
4
+ //#region src/components/gauge/use-animated-value.ts
5
+ /** Folds a change into [-span/2, span/2), the short way round a circle. */
6
+ var shortestDelta = (delta, span) => {
7
+ const s = Math.max(span, 1e-9);
8
+ return ((delta + s / 2) % s + s) % s - s / 2;
9
+ };
10
+ var prefersReducedMotion = () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
11
+ /**
12
+ * Follows `target` with the given transition and returns the value to draw
13
+ * this frame. With no transition it returns `target` as is and renders
14
+ * nothing extra. Honours `prefers-reduced-motion` by snapping.
15
+ *
16
+ * `span` is the size of the domain and sets the rest threshold. `initial` is
17
+ * where the value starts on mount, for a sweep-in from the minimum. `wrap`
18
+ * treats the domain as circular: every change is followed the shortest way
19
+ * round, so a heading crossing north never swings the long way back. The
20
+ * returned value may then sit outside the domain mid-flight; map it back
21
+ * with a modulo.
22
+ */
23
+ var useAnimatedValue = (target, transition, span, initial, wrap) => {
24
+ const enabled = Boolean(transition);
25
+ const [display, setDisplay] = useState(initial ?? target);
26
+ const spring = useRef({
27
+ value: initial ?? target,
28
+ velocity: 0
29
+ });
30
+ const start = initial ?? target;
31
+ const [series, setSeries] = useState(() => ({
32
+ raw: target,
33
+ goal: wrap ? start + shortestDelta(target - start, span) : target
34
+ }));
35
+ if (wrap && (target !== series.raw || !enabled && target !== series.goal)) setSeries(enabled ? {
36
+ raw: target,
37
+ goal: series.goal + shortestDelta(target - series.raw, span)
38
+ } : {
39
+ raw: target,
40
+ goal: target
41
+ });
42
+ const goal = wrap ? series.goal : target;
43
+ const transitionRef = useRef(transition);
44
+ useEffect(() => {
45
+ transitionRef.current = transition;
46
+ });
47
+ const configKey = JSON.stringify(transition);
48
+ if (!enabled && display !== target) setDisplay(target);
49
+ useEffect(() => {
50
+ if (!enabled) spring.current = {
51
+ value: target,
52
+ velocity: 0
53
+ };
54
+ }, [enabled, target]);
55
+ useEffect(() => {
56
+ const config = resolveTransition(transitionRef.current);
57
+ if (!config) return;
58
+ const precision = Math.max(span, 1e-6) * 1e-4;
59
+ const snap = prefersReducedMotion();
60
+ const restAt = target;
61
+ let frame = 0;
62
+ let last = performance.now();
63
+ const from = spring.current.value;
64
+ const startAt = last;
65
+ const tick = (now) => {
66
+ const dt = (now - last) / 1e3;
67
+ last = now;
68
+ let done;
69
+ if (snap) done = true;
70
+ else if (config.type === "spring") {
71
+ const physics = springPhysics(config);
72
+ const { state, done: springDone } = stepSpring(spring.current, goal, physics, dt, precision);
73
+ spring.current = state;
74
+ done = springDone;
75
+ } else {
76
+ const duration = Math.max(config.duration ?? .5, .001);
77
+ const progress = Math.min((now - startAt) / 1e3 / duration, 1);
78
+ const eased = easingFunction(config.ease)(progress);
79
+ const value = from + (goal - from) * eased;
80
+ spring.current = {
81
+ value,
82
+ velocity: 0
83
+ };
84
+ done = progress >= 1;
85
+ }
86
+ if (done) {
87
+ spring.current = {
88
+ value: restAt,
89
+ velocity: 0
90
+ };
91
+ if (wrap) setSeries((s) => s.raw === restAt && s.goal === restAt ? s : {
92
+ raw: restAt,
93
+ goal: restAt
94
+ });
95
+ }
96
+ setDisplay(spring.current.value);
97
+ if (!done) frame = requestAnimationFrame(tick);
98
+ };
99
+ frame = requestAnimationFrame(tick);
100
+ return () => cancelAnimationFrame(frame);
101
+ }, [
102
+ goal,
103
+ target,
104
+ configKey,
105
+ span,
106
+ wrap
107
+ ]);
108
+ return enabled ? display : target;
109
+ };
110
+ //#endregion
111
+ export { useAnimatedValue };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Shared vocabulary and pure helpers for the gauge primitives: stroke and
3
+ * font options, zones, and the formatters that turn a value into a label.
4
+ * Everything here is framework-free so it can be used in generated code.
5
+ */
6
+ export type StrokeCap = 'round' | 'butt';
7
+ export type FontFamily = 'sans' | 'rounded' | 'mono';
8
+ export type FontWeight = 'regular' | 'medium' | 'semibold' | 'bold';
9
+ export type NeedleStyle = 'line' | 'pointer' | 'compass' | 'arrow';
10
+ /**
11
+ * Which end of a range a part follows: `to`, the far end, which is the value
12
+ * itself on a single-value gauge, or `from`, where the range starts.
13
+ */
14
+ export type GaugeEnd = 'from' | 'to';
15
+ /** A colour for every mark alike, or one per mark worked out from its value. */
16
+ export type ValueColor = string | ((value: number) => string);
17
+ /** Which edge of a text sits at its position: left, centre or right. */
18
+ export type TextAnchor = 'start' | 'middle' | 'end';
19
+ export type Zone = {
20
+ /** Upper cutoff of the zone in domain units. Ignored for the last zone. */
21
+ to: number;
22
+ color: string;
23
+ };
24
+ /** Theme font-family CSS value for SVG `fontFamily` / `style.fontFamily`. */
25
+ export declare const fontFamily: (family?: FontFamily) => string;
26
+ /** Theme font-weight CSS value for SVG `fontWeight` / `style.fontWeight`. */
27
+ export declare const fontWeight: (weight?: FontWeight) => number;
28
+ /** Colour of the zone a value falls in, or `fallback` when there are none. */
29
+ export declare const zoneColor: (zones: Zone[], value: number, fallback: string) => string;
30
+ /** Cutoff values that separate neighbouring zones. */
31
+ export declare const zoneCutoffs: (zones: Zone[]) => number[];
32
+ /**
33
+ * Formats a heading in degrees as a compass point when it lands on one of the
34
+ * sixteen, and as degrees otherwise, so a 0–360 domain reads like a bezel:
35
+ * N, 30°, 60°, E, and so on.
36
+ */
37
+ export declare const compassLabel: (degrees: number) => string;
38
+ /** Formats a value in hours as a clock numeral, so 0 and 12 both read "12". */
39
+ export declare const clockLabel: (hours: number) => string;
40
+ /**
41
+ * Hours past midnight as a 24-hour clock time: 6.5 reads "06:30". Negative
42
+ * hours reach back into the day before, and past 24 into the day after.
43
+ */
44
+ export declare const clockTime: (hours: number) => string;
45
+ /** A duration in hours as "7h 32m". */
46
+ export declare const durationLabel: (hours: number) => string;
47
+ /**
48
+ * `color` thinned towards transparent: the one ink of a face taken down to
49
+ * its quieter tones, holding apart in either theme.
50
+ */
51
+ export declare const fadeColor: (color: string, percent: number) => string;
@@ -0,0 +1,80 @@
1
+ //#region src/components/gauge/utils.ts
2
+ var FONT_FAMILY = {
3
+ sans: "var(--font-family-body)",
4
+ rounded: "var(--font-family-body)",
5
+ mono: "var(--font-family-mono)"
6
+ };
7
+ var FONT_WEIGHT = {
8
+ regular: 400,
9
+ medium: 500,
10
+ semibold: 600,
11
+ bold: 700
12
+ };
13
+ /** Theme font-family CSS value for SVG `fontFamily` / `style.fontFamily`. */
14
+ var fontFamily = (family = "sans") => FONT_FAMILY[family];
15
+ /** Theme font-weight CSS value for SVG `fontWeight` / `style.fontWeight`. */
16
+ var fontWeight = (weight = "regular") => FONT_WEIGHT[weight];
17
+ /** Colour of the zone a value falls in, or `fallback` when there are none. */
18
+ var zoneColor = (zones, value, fallback) => {
19
+ if (zones.length === 0) return fallback;
20
+ for (let i = 0; i < zones.length - 1; i += 1) {
21
+ const zone = zones[i];
22
+ if (zone && value < zone.to) return zone.color;
23
+ }
24
+ return zones[zones.length - 1]?.color ?? fallback;
25
+ };
26
+ /** Cutoff values that separate neighbouring zones. */
27
+ var zoneCutoffs = (zones) => zones.slice(0, -1).map((zone) => zone.to);
28
+ var compassPoints = [
29
+ "N",
30
+ "NNE",
31
+ "NE",
32
+ "ENE",
33
+ "E",
34
+ "ESE",
35
+ "SE",
36
+ "SSE",
37
+ "S",
38
+ "SSW",
39
+ "SW",
40
+ "WSW",
41
+ "W",
42
+ "WNW",
43
+ "NW",
44
+ "NNW"
45
+ ];
46
+ /**
47
+ * Formats a heading in degrees as a compass point when it lands on one of the
48
+ * sixteen, and as degrees otherwise, so a 0–360 domain reads like a bezel:
49
+ * N, 30°, 60°, E, and so on.
50
+ */
51
+ var compassLabel = (degrees) => {
52
+ const turn = (degrees % 360 + 360) % 360;
53
+ const index = Math.round(turn / 22.5) % 16;
54
+ const exact = Math.abs(turn - index * 22.5) < .5;
55
+ const point = compassPoints[index];
56
+ return exact && point ? point : `${Math.round(turn)}°`;
57
+ };
58
+ /** Formats a value in hours as a clock numeral, so 0 and 12 both read "12". */
59
+ var clockLabel = (hours) => String((Math.round(hours) % 12 + 12) % 12 || 12);
60
+ var pad = (n) => String(n).padStart(2, "0");
61
+ /**
62
+ * Hours past midnight as a 24-hour clock time: 6.5 reads "06:30". Negative
63
+ * hours reach back into the day before, and past 24 into the day after.
64
+ */
65
+ var clockTime = (hours) => {
66
+ const minutes = Math.round((hours % 24 + 24) % 24 * 60);
67
+ return `${pad(Math.floor(minutes / 60) % 24)}:${pad(minutes % 60)}`;
68
+ };
69
+ /** A duration in hours as "7h 32m". */
70
+ var durationLabel = (hours) => {
71
+ const minutes = Math.round(hours * 60);
72
+ return `${Math.floor(minutes / 60)}h ${pad(minutes % 60)}m`;
73
+ };
74
+ /**
75
+ * `color` thinned towards transparent: the one ink of a face taken down to
76
+ * its quieter tones, holding apart in either theme.
77
+ */
78
+ var fadeColor = (color, percent) => `oklch(from ${color} l c h / ${percent}%)`;
79
+ //#endregion
80
+ export { clockLabel, clockTime, compassLabel, durationLabel, fadeColor, fontFamily, fontWeight, zoneColor, zoneCutoffs };
@@ -0,0 +1 @@
1
+ @layer viraui.preflight, viraui.components, viraui.overrides;@layer viraui.components{@property --vui-icon-meter-icon-color{syntax:"<color>";inherits:true;initial-value:#0000}@property --vui-icon-meter-empty-color{syntax:"<color>";inherits:true;initial-value:#0000}@property --__icon-meter-icon-size{syntax:"<length>";inherits:true;initial-value:0}.viraui__icon-meter-module__IconMeter_2k97C{--vui-icon-meter-icon-color:var(--global-primary);--vui-icon-meter-empty-color:var(--global-contrast);--__icon-meter-icon-size:var(--space-l);position:relative;&[data-dimension=s]{--__icon-meter-icon-size:var(--space-m)}&[data-dimension=l]{--__icon-meter-icon-size:var(--space-xl)}}.viraui__icon-meter-module__Icon_ZIDLS{display:block;flex:none;inline-size:var(--__icon-meter-icon-size);block-size:var(--__icon-meter-icon-size)}.viraui__icon-meter-module__HalfDefs_LjhPl{position:absolute;inline-size:0;block-size:0;overflow:hidden;pointer-events:none}}
@@ -0,0 +1,62 @@
1
+ import * as React from 'react';
2
+ type IconMeterIconProps = {
3
+ 'aria-hidden'?: boolean | 'true';
4
+ className?: string;
5
+ fill?: string;
6
+ size?: number | string;
7
+ };
8
+ /**
9
+ * Public IconMeter props.
10
+ *
11
+ * Display-only discrete icon row (APG Meter). Not a track Meter, Gauge, progress, or interactive rating.
12
+ */
13
+ export type IconMeterProps = Omit<React.ComponentPropsWithRef<'div'>, 'children'> & {
14
+ /**
15
+ * Current reading. Clamped to `[0, iconCount]` after `iconCount` is clamped. `0` paints all empty icons.
16
+ */
17
+ value: number;
18
+ /**
19
+ * Number of icon slots. Clamped to at least `3`.
20
+ *
21
+ * @defaultValue 5
22
+ */
23
+ iconCount?: number;
24
+ /**
25
+ * Recolorable icon component instantiated once per slot with `fill` / `size` / `className`.
26
+ *
27
+ * @defaultValue ChromeStar
28
+ */
29
+ icon?: React.ComponentType<IconMeterIconProps>;
30
+ /**
31
+ * Compact size step. Maps to `data-dimension` for icon size, gap, and label text size.
32
+ *
33
+ * @defaultValue 'm'
34
+ */
35
+ size?: 's' | 'm' | 'l';
36
+ /**
37
+ * CSS color string for full / half icons (`--vui-icon-meter-icon-color`). Pass any valid color or theme var.
38
+ *
39
+ * @defaultValue 'var(--global-primary)'
40
+ */
41
+ highlightColor?: string;
42
+ /**
43
+ * Custom visible label. When omitted and not hidden, the clamped value string is shown.
44
+ *
45
+ * @defaultValue Clamped value as text.
46
+ */
47
+ label?: React.ReactNode;
48
+ /**
49
+ * Hides the visible label. Provide `aria-label` or `aria-labelledby` for the accessible name.
50
+ *
51
+ * @defaultValue false
52
+ */
53
+ hideLabel?: boolean;
54
+ /**
55
+ * Overrides the default AT value text (`"{value} of {iconCount}"`).
56
+ *
57
+ * @defaultValue Derived `"{clampedValue} of {clampedIconCount}"`.
58
+ */
59
+ valueText?: string;
60
+ };
61
+ export declare const IconMeter: React.FC<IconMeterProps>;
62
+ export {};
@@ -0,0 +1,105 @@
1
+ import './icon-meter.css';
2
+ import { clsx } from "../../core/clsx.js";
3
+ import { ChromeStar } from "../../internal/chrome-icons/chrome-icons.js";
4
+ import { Text } from "../text/text.js";
5
+ import { Stack } from "../stack/stack.js";
6
+ import icon_meter_module_default from "./icon-meter.module.js";
7
+ import { clampIconCount, clampValue, getSlotFill, roundMeterValue } from "./utils.js";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ import * as React from "react";
10
+ import { Meter } from "@base-ui/react";
11
+ //#region src/components/icon-meter/icon-meter.tsx
12
+ var LABEL_TEXT_SIZE = {
13
+ s: "s",
14
+ m: "m",
15
+ l: "l"
16
+ };
17
+ var ICON_COLUMN_GAP = {
18
+ s: "2xs",
19
+ m: "xs",
20
+ l: "s"
21
+ };
22
+ var EMPTY_FILL = "var(--vui-icon-meter-empty-color)";
23
+ var FULL_FILL = "var(--vui-icon-meter-icon-color)";
24
+ var IconMeter = ({ "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className, hideLabel = false, highlightColor = "var(--global-primary)", icon: Icon = ChromeStar, iconCount = 5, label, ref, size = "m", style, value, valueText, ...otherProps }) => {
25
+ const halfGradientId = `IconMeter-half-${React.useId().replaceAll(":", "")}`;
26
+ const clampedIconCount = clampIconCount(iconCount);
27
+ const clampedValue = clampValue(value, clampedIconCount);
28
+ const roundedValue = roundMeterValue(clampedValue);
29
+ const resolvedValueText = valueText ?? `${clampedValue} of ${clampedIconCount}`;
30
+ const visibleLabel = label ?? String(clampedValue);
31
+ const dynamicStyle = {
32
+ "--vui-icon-meter-icon-color": highlightColor,
33
+ ...style
34
+ };
35
+ const icons = Array.from({ length: clampedIconCount }, (_, slotIndex) => {
36
+ const fillKind = getSlotFill(slotIndex, roundedValue);
37
+ let fill = EMPTY_FILL;
38
+ if (fillKind === "full") fill = FULL_FILL;
39
+ else if (fillKind === "half") fill = `url(#${halfGradientId})`;
40
+ return /* @__PURE__ */ jsx(Icon, {
41
+ "aria-hidden": "true",
42
+ className: icon_meter_module_default.Icon,
43
+ fill,
44
+ size: "100%"
45
+ }, slotIndex);
46
+ });
47
+ return /* @__PURE__ */ jsxs(Meter.Root, {
48
+ ...otherProps,
49
+ ref,
50
+ ...ariaLabel != null && { "aria-label": ariaLabel },
51
+ ...ariaLabelledby != null && { "aria-labelledby": ariaLabelledby },
52
+ className: clsx(icon_meter_module_default.IconMeter, className),
53
+ "data-dimension": size,
54
+ getAriaValueText: () => resolvedValueText,
55
+ max: clampedIconCount,
56
+ min: 0,
57
+ render: /* @__PURE__ */ jsx(Stack, {
58
+ direction: "row",
59
+ vAlign: "center",
60
+ columnGap: "s"
61
+ }),
62
+ style: dynamicStyle,
63
+ value: clampedValue,
64
+ children: [
65
+ /* @__PURE__ */ jsx("svg", {
66
+ "aria-hidden": "true",
67
+ className: icon_meter_module_default.HalfDefs,
68
+ focusable: "false",
69
+ children: /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", {
70
+ id: halfGradientId,
71
+ x1: "0",
72
+ x2: "1",
73
+ y1: "0",
74
+ y2: "0",
75
+ children: [/* @__PURE__ */ jsx("stop", {
76
+ offset: "50%",
77
+ stopColor: FULL_FILL
78
+ }), /* @__PURE__ */ jsx("stop", {
79
+ offset: "50%",
80
+ stopColor: EMPTY_FILL
81
+ })]
82
+ }) })
83
+ }),
84
+ /* @__PURE__ */ jsx(Stack, {
85
+ columnGap: ICON_COLUMN_GAP[size],
86
+ "data-testid": "icon-meter-icons",
87
+ direction: "row",
88
+ vAlign: "center",
89
+ children: icons
90
+ }),
91
+ !hideLabel && /* @__PURE__ */ jsx(Meter.Label, {
92
+ render: /* @__PURE__ */ jsx(Text, {
93
+ size: LABEL_TEXT_SIZE[size],
94
+ tone: "muted",
95
+ weight: "semibold",
96
+ whiteSpace: "nowrap",
97
+ selection: false
98
+ }),
99
+ children: visibleLabel
100
+ })
101
+ ]
102
+ });
103
+ };
104
+ //#endregion
105
+ export { IconMeter };
@@ -0,0 +1,11 @@
1
+ //#region src/components/icon-meter/icon-meter.module.css
2
+ var IconMeter = "viraui__icon-meter-module__IconMeter_2k97C";
3
+ var Icon = "viraui__icon-meter-module__Icon_ZIDLS";
4
+ var HalfDefs = "viraui__icon-meter-module__HalfDefs_LjhPl";
5
+ var icon_meter_module_default = {
6
+ IconMeter,
7
+ Icon,
8
+ HalfDefs
9
+ };
10
+ //#endregion
11
+ export { HalfDefs, Icon, IconMeter, icon_meter_module_default as default };
@@ -0,0 +1,2 @@
1
+ export { IconMeter } from './icon-meter';
2
+ export type { IconMeterProps } from './icon-meter';
@@ -0,0 +1,22 @@
1
+ /** Slot paint after fraction rounding. */
2
+ export type IconMeterSlotFill = 'empty' | 'half' | 'full';
3
+ /**
4
+ * Clamp icon slot count to at least 3. Non-finite values become 3.
5
+ */
6
+ export declare const clampIconCount: (iconCount: number) => number;
7
+ /**
8
+ * Clamp the meter value into `[0, max]`. Non-finite values become 0.
9
+ */
10
+ export declare const clampValue: (value: number, max: number) => number;
11
+ /**
12
+ * Round a raw value into empty / half / full bands per unit fraction.
13
+ *
14
+ * - fraction `< 0.25` → floor (empty remainder)
15
+ * - fraction `≥ 0.75` → ceil (full)
16
+ * - else → `integer + 0.5` (half)
17
+ */
18
+ export declare const roundMeterValue: (rawValue: number) => number;
19
+ /**
20
+ * Resolve empty / half / full for zero-based slot index against a rounded value.
21
+ */
22
+ export declare const getSlotFill: (slotIndex: number, roundedValue: number) => IconMeterSlotFill;
@@ -0,0 +1,40 @@
1
+ //#region src/components/icon-meter/utils.ts
2
+ /**
3
+ * Clamp icon slot count to at least 3. Non-finite values become 3.
4
+ */
5
+ var clampIconCount = (iconCount) => {
6
+ if (!Number.isFinite(iconCount)) return 3;
7
+ return Math.max(3, Math.trunc(iconCount));
8
+ };
9
+ /**
10
+ * Clamp the meter value into `[0, max]`. Non-finite values become 0.
11
+ */
12
+ var clampValue = (value, max) => {
13
+ if (!Number.isFinite(value)) return 0;
14
+ return Math.min(Math.max(value, 0), max);
15
+ };
16
+ /**
17
+ * Round a raw value into empty / half / full bands per unit fraction.
18
+ *
19
+ * - fraction `< 0.25` → floor (empty remainder)
20
+ * - fraction `≥ 0.75` → ceil (full)
21
+ * - else → `integer + 0.5` (half)
22
+ */
23
+ var roundMeterValue = (rawValue) => {
24
+ const integer = Math.trunc(rawValue);
25
+ const fraction = rawValue - integer;
26
+ if (fraction >= .75) return Math.ceil(rawValue);
27
+ if (fraction < .25) return Math.floor(rawValue);
28
+ return integer + .5;
29
+ };
30
+ /**
31
+ * Resolve empty / half / full for zero-based slot index against a rounded value.
32
+ */
33
+ var getSlotFill = (slotIndex, roundedValue) => {
34
+ const iconIndex = slotIndex + 1;
35
+ if (roundedValue >= iconIndex) return "full";
36
+ if (roundedValue > iconIndex - 1) return "half";
37
+ return "empty";
38
+ };
39
+ //#endregion
40
+ export { clampIconCount, clampValue, getSlotFill, roundMeterValue };
@@ -18,8 +18,10 @@ export * from './elevator';
18
18
  export * from './fieldset';
19
19
  export * from './fit-text';
20
20
  export * from './glow';
21
+ export * from './gauge';
21
22
  export * from './grid';
22
23
  export * from './icon-button';
24
+ export * from './icon-meter';
23
25
  export * from './linear-progress';
24
26
  export * from './masonry';
25
27
  export * from './menu';
@@ -1,5 +1,4 @@
1
1
  import { Popover as PrimitivePopover, PopoverPositioner } from '@base-ui/react/popover';
2
- import { StackProps } from '..';
3
2
  import type * as React from 'react';
4
3
  type PopoverSheetPositionerProps = Omit<PopoverPositioner.Props, 'className' | 'style' | 'render'>;
5
4
  type PopoverSheetContainerProps = {
@@ -41,7 +40,7 @@ type PopoverSheetStyledProps = PopoverSheetPositionerProps & PopoverSheetContain
41
40
  *
42
41
  * @defaultValue `start`
43
42
  */
44
- headerAlign?: Extract<StackProps['hAlign'], 'start' | 'center' | 'end'>;
43
+ headerAlign?: 'start' | 'center' | 'end';
45
44
  };
46
45
  type PopoverSheetUnstyledProps = PopoverSheetPositionerProps & PopoverSheetContainerProps & {
47
46
  /**
@@ -1,7 +1,8 @@
1
1
  import { useRender } from '@base-ui/react/use-render';
2
2
  import { ThemeTypes } from '@viraui/foundation/vira/types';
3
3
  import type * as React from 'react';
4
- type StackAlignment = 'start' | 'center' | 'end' | 'stretch' | 'initial' | 'space-between' | 'space-around' | 'space-evenly';
4
+ /** Flex alignment on a physical axis; maps to `justify-content` or `align-items` by `direction`. */
5
+ type StackAlignment = NonNullable<React.CSSProperties['justifyContent'] | React.CSSProperties['alignItems']>;
5
6
  type StackSpace = ThemeTypes['space'];
6
7
  type StackAxisPadding = StackSpace | [StackSpace | 0, StackSpace | 0];
7
8
  /**
@@ -59,7 +60,7 @@ export type StackProps = React.ComponentPropsWithoutRef<'div'> & {
59
60
  *
60
61
  * Prefer this (with cross-axis stretch) so content-sized children like Button span a panel —
61
62
  * do not add `width: 100%` CSS on those children.
62
- * Use with `maxWidth` under a parent `hAlign="center"` for a fluid, capped, centered shell.
63
+ * Use with `maxWidth` under a parent `hAlign="safe center"` for a fluid, capped, centered shell.
63
64
  * Orthogonal to `expandChildren` (self size vs children grow). Pair with `fullHeight` when the
64
65
  * root must fill both parent axes.
65
66
  *
@@ -91,12 +92,18 @@ export type StackProps = React.ComponentPropsWithoutRef<'div'> & {
91
92
  /**
92
93
  * Aligns content on the horizontal axis from the consumer point of view.
93
94
  *
95
+ * Accepts CSS `justify-content` / `align-items` values (which property is used
96
+ * depends on `direction`), including overflow keywords such as `safe center`.
97
+ *
94
98
  * @defaultValue 'initial'
95
99
  */
96
100
  hAlign?: StackAlignment;
97
101
  /**
98
102
  * Aligns content on the vertical axis from the consumer point of view.
99
103
  *
104
+ * Accepts CSS `justify-content` / `align-items` values (which property is used
105
+ * depends on `direction`), including overflow keywords such as `safe center`.
106
+ *
100
107
  * @defaultValue 'initial'
101
108
  */
102
109
  vAlign?: StackAlignment;
@@ -1,5 +1,4 @@
1
1
  import { TabsRoot } from '@base-ui/react/tabs';
2
- import { StackProps } from '..';
3
2
  import { ButtonProps } from '../button/button';
4
3
  import { TabsListProps } from './tabs-list';
5
4
  import { TabsPanelProps } from './tabs-panel';
@@ -20,7 +19,7 @@ export type TabsProps = TabsRoot.Props & {
20
19
  *
21
20
  * @defaultValue 'start'
22
21
  */
23
- listAlignment?: Extract<StackProps['hAlign'], 'start' | 'center' | 'end' | 'stretch'>;
22
+ listAlignment?: 'start' | 'center' | 'end' | 'stretch';
24
23
  /**
25
24
  * Shared visual size for tab items (`Button`) and the active indicator footprint.
26
25
  *
package/dist/index.js CHANGED
@@ -32,9 +32,25 @@ import { Elevator } from "./components/elevator/elevator.js";
32
32
  import { Fieldset } from "./components/fieldset/fieldset.js";
33
33
  import { FitText } from "./components/fit-text/fit-text.js";
34
34
  import { Glow } from "./components/glow/glow.js";
35
+ import { angleToValue, arcBox, arcPath, clamp, cutValues, degreesToRadians, foldValue, polar, stepValues, valueToAngle, valueToTurnedAngle } from "./components/gauge/math.js";
36
+ import { arcScale } from "./components/gauge/scale.js";
37
+ import { DEFAULT_TRANSITION, cubicBezier, easingFunction, resolveTransition, springPhysics, stepSpring } from "./components/gauge/transition.js";
38
+ import { useAnimatedValue } from "./components/gauge/use-animated-value.js";
39
+ import { useGauge, useRoundGauge } from "./components/gauge/context.js";
40
+ import { Gauge } from "./components/gauge/gauge.js";
41
+ import { GaugeInset } from "./components/gauge/inset.js";
42
+ import { GaugeArc, GaugeStack, GaugeTrack, GaugeZones } from "./components/gauge/arcs.js";
43
+ import { GaugeControl } from "./components/gauge/control.js";
44
+ import { clockLabel, clockTime, compassLabel, durationLabel, fadeColor, fontFamily, fontWeight, zoneColor, zoneCutoffs } from "./components/gauge/utils.js";
45
+ import { GaugeMarks, GaugeTickFill, GaugeTickLabels, GaugeTicks } from "./components/gauge/ticks.js";
46
+ import { GaugeText, GaugeValue } from "./components/gauge/text.js";
47
+ import { GaugeDot } from "./components/gauge/dot.js";
48
+ import { GaugeTooltip } from "./components/gauge/tooltip.js";
49
+ import { GaugeHub, GaugeNeedle } from "./components/gauge/needle.js";
35
50
  import { Grid } from "./components/grid/grid.js";
36
51
  import { IconButton } from "./components/icon-button/icon-button.js";
37
52
  import { IconButtonLink } from "./components/icon-button/icon-button-link.js";
53
+ import { IconMeter } from "./components/icon-meter/icon-meter.js";
38
54
  import { LinearProgress } from "./components/linear-progress/linear-progress.js";
39
55
  import { Masonry } from "./components/masonry/masonry.js";
40
56
  import { MenuCheckboxItem } from "./components/menu/menu-checkbox-item.js";
@@ -74,4 +90,4 @@ import { ToggleTextButton } from "./components/toggle-text-button/toggle-text-bu
74
90
  import { Typewriter } from "./components/typewriter/typewriter.js";
75
91
  import { TooltipContent } from "./components/tooltip/tooltip-content.js";
76
92
  import { Tooltip, TooltipProvider, TooltipTrigger } from "./components/tooltip/tooltip.js";
77
- export { Accordion, Annotate, Autocomplete, Avatar, AvatarGroup, Badge, Beam, Bleed, BreakpointsProvider, Button, ButtonLink, Checkbox, CheckboxGroup, Chip, ClampText, Dialog, DialogClose, DialogIndent, DialogIndentBackground, DialogSheet, DialogTrigger, DirectionProvider, Disclosure, ELEVATION_DEFAULTS, Elevator, Fieldset, FitText, Glow, Grid, IconButton, IconButtonLink, LinearProgress, Masonry, Menu, MenuCheckboxItem, MenuGroup, MenuItem, MenuLinkItem, MenuPopup, MenuRadioGroup, MenuRadioItem, MenuSeparator, MenuSubmenu, MenuSubmenuTrigger, MenuTrigger, Meter, NumberField, OTPField, Popover, PopoverClose, PopoverDescription, PopoverSheet, PopoverTitle, PopoverTrigger, ProgressiveBlur, Prose, Radio, RadioGroup, Select, Separator, Shimmer, Skeleton, Slider, Spinner, Stack, StaticNoise, Surface, Switch, Tabs, Text, Textarea, Textfield, Title, Toast, ToggleButton, ToggleGroup, ToggleTextButton, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Typewriter, getElevationProps, useBreakpoints, useDirection };
93
+ export { Accordion, Annotate, Autocomplete, Avatar, AvatarGroup, Badge, Beam, Bleed, BreakpointsProvider, Button, ButtonLink, Checkbox, CheckboxGroup, Chip, ClampText, DEFAULT_TRANSITION, Dialog, DialogClose, DialogIndent, DialogIndentBackground, DialogSheet, DialogTrigger, DirectionProvider, Disclosure, ELEVATION_DEFAULTS, Elevator, Fieldset, FitText, Gauge, GaugeArc, GaugeControl, GaugeDot, GaugeHub, GaugeInset, GaugeMarks, GaugeNeedle, GaugeStack, GaugeText, GaugeTickFill, GaugeTickLabels, GaugeTicks, GaugeTooltip, GaugeTrack, GaugeValue, GaugeZones, Glow, Grid, IconButton, IconButtonLink, IconMeter, LinearProgress, Masonry, Menu, MenuCheckboxItem, MenuGroup, MenuItem, MenuLinkItem, MenuPopup, MenuRadioGroup, MenuRadioItem, MenuSeparator, MenuSubmenu, MenuSubmenuTrigger, MenuTrigger, Meter, NumberField, OTPField, Popover, PopoverClose, PopoverDescription, PopoverSheet, PopoverTitle, PopoverTrigger, ProgressiveBlur, Prose, Radio, RadioGroup, Select, Separator, Shimmer, Skeleton, Slider, Spinner, Stack, StaticNoise, Surface, Switch, Tabs, Text, Textarea, Textfield, Title, Toast, ToggleButton, ToggleGroup, ToggleTextButton, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Typewriter, angleToValue, arcBox, arcPath, arcScale, clamp, clockLabel, clockTime, compassLabel, cubicBezier, cutValues, degreesToRadians, durationLabel, easingFunction, fadeColor, foldValue, fontFamily, fontWeight, getElevationProps, polar, resolveTransition, springPhysics, stepSpring, stepValues, useAnimatedValue, useBreakpoints, useDirection, useGauge, useRoundGauge, valueToAngle, valueToTurnedAngle, zoneColor, zoneCutoffs };
@@ -7,6 +7,8 @@ import type * as React from 'react';
7
7
  */
8
8
  export type ChromeIconProps = {
9
9
  className?: string;
10
+ /** Paint for the glyph; defaults to `currentColor`. */
11
+ fill?: string;
10
12
  size?: number | string;
11
13
  };
12
14
  export declare const ChromePlus: React.FC<ChromeIconProps>;
@@ -18,3 +20,5 @@ export declare const ChromeChevronDown: React.FC<ChromeIconProps>;
18
20
  /** End-edge chevron (submenu). Mirror in RTL via CSS `:dir(rtl)`, not JS. */
19
21
  export declare const ChromeChevronEnd: React.FC<ChromeIconProps>;
20
22
  export declare const ChromeXmark: React.FC<ChromeIconProps>;
23
+ /** Default IconMeter glyph — solid star; honors `fill` for empty / half / full paint. */
24
+ export declare const ChromeStar: React.FC<ChromeIconProps>;
@@ -1,9 +1,9 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  //#region src/internal/chrome-icons/chrome-icons.tsx
3
- var ChromeSvg = ({ children, className, size = 24 }) => /* @__PURE__ */ jsx("svg", {
3
+ var ChromeSvg = ({ children, className, fill = "currentColor", size = 24 }) => /* @__PURE__ */ jsx("svg", {
4
4
  "aria-hidden": "true",
5
5
  className,
6
- fill: "currentColor",
6
+ fill,
7
7
  height: size,
8
8
  viewBox: "0 0 18 18",
9
9
  width: size,
@@ -46,10 +46,18 @@ var ChromeChevronEnd = ({ className, size }) => /* @__PURE__ */ jsx(ChromeSvg, {
46
46
  size,
47
47
  children: /* @__PURE__ */ jsx("path", { d: "M13.28 8.47 7.03 2.22a.75.75 0 1 0-1.061 1.061l5.72 5.72-5.72 5.72a.75.75 0 0 0 1.06 1.061l6.25-6.25a.75.75 0 0 0 0-1.061z" })
48
48
  });
49
- var ChromeXmark = ({ className, size }) => /* @__PURE__ */ jsx(ChromeSvg, {
49
+ var ChromeXmark = ({ className, fill, size }) => /* @__PURE__ */ jsx(ChromeSvg, {
50
50
  className,
51
+ fill,
51
52
  size,
52
53
  children: /* @__PURE__ */ jsx("path", { d: "M13.664 3.23a.783.783 0 1 1 1.107 1.106L10.107 9l4.664 4.664a.783.783 0 0 1-1.107 1.107L9 10.107 4.336 14.77a.783.783 0 1 1-1.107-1.107L7.893 9 3.23 4.336a.782.782 0 1 1 1.107-1.107L9 7.893z" })
53
54
  });
55
+ /** Default IconMeter glyph — solid star; honors `fill` for empty / half / full paint. */
56
+ var ChromeStar = ({ className, fill, size }) => /* @__PURE__ */ jsx(ChromeSvg, {
57
+ className,
58
+ fill,
59
+ size,
60
+ children: /* @__PURE__ */ jsx("path", { d: "M16.713 7.017A.75.75 0 0 0 16 6.5h-4.803L9.713 1.953a.75.75 0 0 0-1.426 0L6.803 6.5H2a.75.75 0 0 0-.443 1.356l3.888 2.843-1.485 4.57a.751.751 0 0 0 1.155.838L9 13.284l3.885 2.823a.75.75 0 0 0 1.155-.839l-1.485-4.57 3.888-2.843a.75.75 0 0 0 .27-.838" })
61
+ });
54
62
  //#endregion
55
- export { ChromeCheck, ChromeChevronDown, ChromeChevronEnd, ChromeChevronExpandY, ChromeChevronUp, ChromeMinus, ChromePlus, ChromeXmark };
63
+ export { ChromeCheck, ChromeChevronDown, ChromeChevronEnd, ChromeChevronExpandY, ChromeChevronUp, ChromeMinus, ChromePlus, ChromeStar, ChromeXmark };