@brijbyte/agentic-ui 0.0.2 → 0.0.4
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/README.md +132 -35
- package/dist/accordion/accordion.d.ts +15 -1
- package/dist/accordion/accordion.d.ts.map +1 -1
- package/dist/accordion/accordion.js +6 -1
- package/dist/accordion/accordion.js.map +1 -1
- package/dist/accordion/parts.d.ts +1 -1
- package/dist/accordion/parts.js +2 -2
- package/dist/alert-dialog/alert-dialog.d.ts +12 -1
- package/dist/alert-dialog/alert-dialog.d.ts.map +1 -1
- package/dist/alert-dialog/alert-dialog.js +6 -1
- package/dist/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/alert-dialog/parts.d.ts +1 -1
- package/dist/alert-dialog/parts.js +2 -2
- package/dist/badge/badge.d.ts +7 -1
- package/dist/badge/badge.d.ts.map +1 -1
- package/dist/badge/badge.js +4 -0
- package/dist/badge/badge.js.map +1 -1
- package/dist/button/button.css +22 -8
- package/dist/button/button.d.ts +19 -8
- package/dist/button/button.d.ts.map +1 -1
- package/dist/button/button.js +6 -1
- package/dist/button/button.js.map +1 -1
- package/dist/button/button.module.js.map +1 -1
- package/dist/card/card.d.ts +11 -1
- package/dist/card/card.d.ts.map +1 -1
- package/dist/card/card.js +7 -0
- package/dist/card/card.js.map +1 -1
- package/dist/checkbox/checkbox.d.ts +14 -1
- package/dist/checkbox/checkbox.d.ts.map +1 -1
- package/dist/checkbox/checkbox.js +5 -1
- package/dist/checkbox/checkbox.js.map +1 -1
- package/dist/checkbox/parts.js +1 -1
- package/dist/collapsible/collapsible.d.ts +12 -1
- package/dist/collapsible/collapsible.d.ts.map +1 -1
- package/dist/collapsible/collapsible.js +5 -0
- package/dist/collapsible/collapsible.js.map +1 -1
- package/dist/collapsible/parts.js +1 -1
- package/dist/context-menu/context-menu.d.ts +6 -1
- package/dist/context-menu/context-menu.d.ts.map +1 -1
- package/dist/context-menu/context-menu.js +4 -0
- package/dist/context-menu/context-menu.js.map +1 -1
- package/dist/context-menu/parts.js +1 -1
- package/dist/dialog/dialog.d.ts +14 -2
- package/dist/dialog/dialog.d.ts.map +1 -1
- package/dist/dialog/dialog.js +6 -0
- package/dist/dialog/dialog.js.map +1 -1
- package/dist/dialog/parts.js +1 -1
- package/dist/drawer/drawer.d.ts +12 -1
- package/dist/drawer/drawer.d.ts.map +1 -1
- package/dist/drawer/drawer.js +5 -0
- package/dist/drawer/drawer.js.map +1 -1
- package/dist/drawer/parts.d.ts +1 -1
- package/dist/drawer/parts.js +1 -1
- package/dist/index.css +1773 -1316
- package/dist/index.d.ts +29 -20
- package/dist/index.js +50 -37
- package/dist/input/input.d.ts +8 -0
- package/dist/input/input.d.ts.map +1 -1
- package/dist/input/input.js +6 -1
- package/dist/input/input.js.map +1 -1
- package/dist/menu/menu.css +3 -8
- package/dist/menu/menu.d.ts +12 -5
- package/dist/menu/menu.d.ts.map +1 -1
- package/dist/menu/menu.js +10 -24
- package/dist/menu/menu.js.map +1 -1
- package/dist/menu/menu.module.js +1 -1
- package/dist/menu/menu.module.js.map +1 -1
- package/dist/menu/menuitemshortcut.js +1 -1
- package/dist/menu/parts.js +1 -1
- package/dist/meter/circular-meter.d.ts +48 -0
- package/dist/meter/circular-meter.d.ts.map +1 -0
- package/dist/meter/circular-meter.js +86 -0
- package/dist/meter/circular-meter.js.map +1 -0
- package/dist/meter/index.d.ts +4 -0
- package/dist/meter/index.js +5 -0
- package/dist/meter/meter.css +152 -0
- package/dist/meter/meter.d.ts +58 -0
- package/dist/meter/meter.d.ts.map +1 -0
- package/dist/meter/meter.js +50 -0
- package/dist/meter/meter.js.map +1 -0
- package/dist/meter/meter.module.css.d.ts +2 -0
- package/dist/meter/meter.module.js +27 -0
- package/dist/meter/meter.module.js.map +1 -0
- package/dist/meter/meterState.js +18 -0
- package/dist/meter/meterState.js.map +1 -0
- package/dist/meter/parts.d.ts +31 -0
- package/dist/meter/parts.d.ts.map +1 -0
- package/dist/meter/parts.js +56 -0
- package/dist/meter/parts.js.map +1 -0
- package/dist/number-field/number-field.d.ts +17 -1
- package/dist/number-field/number-field.d.ts.map +1 -1
- package/dist/number-field/number-field.js +5 -1
- package/dist/number-field/number-field.js.map +1 -1
- package/dist/number-field/parts.js +1 -1
- package/dist/popover/index.d.ts +3 -0
- package/dist/popover/index.js +4 -0
- package/dist/popover/parts.d.ts +43 -0
- package/dist/popover/parts.d.ts.map +1 -0
- package/dist/popover/parts.js +96 -0
- package/dist/popover/parts.js.map +1 -0
- package/dist/popover/popover.css +173 -0
- package/dist/popover/popover.d.ts +49 -0
- package/dist/popover/popover.d.ts.map +1 -0
- package/dist/popover/popover.js +68 -0
- package/dist/popover/popover.js.map +1 -0
- package/dist/popover/popover.module.css.d.ts +2 -0
- package/dist/popover/popover.module.js +16 -0
- package/dist/popover/popover.module.js.map +1 -0
- package/dist/progress/parts.js +1 -1
- package/dist/progress/progress.d.ts +11 -0
- package/dist/progress/progress.d.ts.map +1 -1
- package/dist/progress/progress.js +5 -0
- package/dist/progress/progress.js.map +1 -1
- package/dist/radio/index.d.ts +3 -0
- package/dist/radio/index.js +4 -0
- package/dist/radio/parts.d.ts +18 -0
- package/dist/radio/parts.d.ts.map +1 -0
- package/dist/radio/parts.js +42 -0
- package/dist/radio/parts.js.map +1 -0
- package/dist/radio/radio.css +84 -0
- package/dist/radio/radio.d.ts +31 -0
- package/dist/radio/radio.d.ts.map +1 -0
- package/dist/radio/radio.js +33 -0
- package/dist/radio/radio.js.map +1 -0
- package/dist/radio/radio.module.css.d.ts +2 -0
- package/dist/radio/radio.module.js +11 -0
- package/dist/radio/radio.module.js.map +1 -0
- package/dist/radio-group/index.d.ts +3 -0
- package/dist/radio-group/index.js +4 -0
- package/dist/radio-group/parts.d.ts +13 -0
- package/dist/radio-group/parts.d.ts.map +1 -0
- package/dist/radio-group/parts.js +31 -0
- package/dist/radio-group/parts.js.map +1 -0
- package/dist/radio-group/radio-group.css +17 -0
- package/dist/radio-group/radio-group.d.ts +37 -0
- package/dist/radio-group/radio-group.d.ts.map +1 -0
- package/dist/radio-group/radio-group.js +28 -0
- package/dist/radio-group/radio-group.js.map +1 -0
- package/dist/radio-group/radio-group.module.css.d.ts +2 -0
- package/dist/radio-group/radio-group.module.js +9 -0
- package/dist/radio-group/radio-group.module.js.map +1 -0
- package/dist/select/parts.js +1 -1
- package/dist/select/select.d.ts +15 -2
- package/dist/select/select.d.ts.map +1 -1
- package/dist/select/select.js +5 -1
- package/dist/select/select.js.map +1 -1
- package/dist/separator/separator.d.ts +4 -0
- package/dist/separator/separator.d.ts.map +1 -1
- package/dist/separator/separator.js +5 -1
- package/dist/separator/separator.js.map +1 -1
- package/dist/shared/PopupArrow.js +22 -0
- package/dist/shared/PopupArrow.js.map +1 -0
- package/dist/slider/parts.js +1 -1
- package/dist/slider/slider.d.ts +19 -1
- package/dist/slider/slider.d.ts.map +1 -1
- package/dist/slider/slider.js +6 -0
- package/dist/slider/slider.js.map +1 -1
- package/dist/styles/tokens.css +21 -8
- package/dist/switch/parts.js +1 -1
- package/dist/switch/switch.css +11 -2
- package/dist/switch/switch.d.ts +13 -1
- package/dist/switch/switch.d.ts.map +1 -1
- package/dist/switch/switch.js +5 -1
- package/dist/switch/switch.js.map +1 -1
- package/dist/switch/switch.module.js.map +1 -1
- package/dist/tabs/parts.js +1 -1
- package/dist/tabs/tabs.d.ts +9 -2
- package/dist/tabs/tabs.d.ts.map +1 -1
- package/dist/tabs/tabs.js +4 -0
- package/dist/tabs/tabs.js.map +1 -1
- package/dist/toast/parts.js +1 -1
- package/dist/toast/toast.d.ts +12 -1
- package/dist/toast/toast.d.ts.map +1 -1
- package/dist/toast/toast.js +8 -0
- package/dist/toast/toast.js.map +1 -1
- package/dist/tokens.css +23 -11
- package/dist/tooltip/parts.js +1 -1
- package/dist/tooltip/tooltip.d.ts +10 -1
- package/dist/tooltip/tooltip.d.ts.map +1 -1
- package/dist/tooltip/tooltip.js +4 -0
- package/dist/tooltip/tooltip.js.map +1 -1
- package/package.json +23 -2
- package/src/accordion/accordion.tsx +14 -0
- package/src/alert-dialog/alert-dialog.tsx +11 -0
- package/src/badge/badge.tsx +6 -0
- package/src/button/button.module.css +29 -13
- package/src/button/button.tsx +19 -8
- package/src/card/card.tsx +10 -0
- package/src/checkbox/checkbox.tsx +13 -0
- package/src/collapsible/collapsible.tsx +11 -0
- package/src/context-menu/context-menu.tsx +5 -0
- package/src/dialog/dialog.tsx +13 -1
- package/src/drawer/drawer.tsx +11 -0
- package/src/index.ts +25 -233
- package/src/input/input.tsx +8 -0
- package/src/menu/menu.module.css +3 -10
- package/src/menu/menu.tsx +13 -26
- package/src/meter/circular-meter.tsx +114 -0
- package/src/meter/index.ts +9 -0
- package/src/meter/meter.module.css +162 -0
- package/src/meter/meter.tsx +86 -0
- package/src/meter/meterState.ts +29 -0
- package/src/meter/parts.tsx +72 -0
- package/src/number-field/number-field.tsx +16 -0
- package/src/popover/index.ts +14 -0
- package/src/popover/parts.tsx +120 -0
- package/src/popover/popover.module.css +189 -0
- package/src/popover/popover.tsx +80 -0
- package/src/progress/progress.tsx +11 -0
- package/src/radio/index.ts +6 -0
- package/src/radio/parts.tsx +43 -0
- package/src/radio/radio.module.css +96 -0
- package/src/radio/radio.tsx +37 -0
- package/src/radio-group/index.ts +5 -0
- package/src/radio-group/parts.tsx +32 -0
- package/src/radio-group/radio-group.module.css +17 -0
- package/src/radio-group/radio-group.tsx +63 -0
- package/src/select/select.tsx +14 -1
- package/src/separator/separator.tsx +4 -0
- package/src/shared/PopupArrow.tsx +41 -0
- package/src/slider/slider.tsx +18 -0
- package/src/styles/tokens.css +23 -11
- package/src/switch/switch.module.css +11 -2
- package/src/switch/switch.tsx +12 -0
- package/src/tabs/tabs.tsx +8 -1
- package/src/toast/toast.tsx +11 -0
- package/src/tooltip/tooltip.tsx +9 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { Meter as BaseMeter } from "@base-ui/react/meter";
|
|
3
|
+
import styles from "./meter.module.css";
|
|
4
|
+
import { getMeterState } from "./meterState";
|
|
5
|
+
|
|
6
|
+
export type MeterSize = "sm" | "md" | "lg";
|
|
7
|
+
|
|
8
|
+
export interface MeterProps {
|
|
9
|
+
/** Current value. Must be between `min` and `max`. */
|
|
10
|
+
value: number;
|
|
11
|
+
/** Minimum value of the range. @default 0 */
|
|
12
|
+
min?: number;
|
|
13
|
+
/** Maximum value of the range. @default 100 */
|
|
14
|
+
max?: number;
|
|
15
|
+
/** Upper boundary of the low zone. Values ≤ this are "low". */
|
|
16
|
+
low?: number;
|
|
17
|
+
/** Lower boundary of the high zone. Values ≥ this are "high". */
|
|
18
|
+
high?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Which zone is optimal — determines fill colour:
|
|
21
|
+
* - `"high"` (default) → high=green, mid=amber, low=red (battery, signal)
|
|
22
|
+
* - `"low"` → low=green, mid=amber, high=red (CPU load, disk)
|
|
23
|
+
* - `"mid"` → mid=green, low/high=amber (temperature)
|
|
24
|
+
*/
|
|
25
|
+
optimum?: "low" | "mid" | "high";
|
|
26
|
+
/** Accessible + visible label. */
|
|
27
|
+
label?: ReactNode;
|
|
28
|
+
/** Show the formatted value next to the label. @default false */
|
|
29
|
+
showValue?: boolean;
|
|
30
|
+
/** `Intl.NumberFormatOptions` for value formatting. */
|
|
31
|
+
format?: Intl.NumberFormatOptions;
|
|
32
|
+
/** Show tick marks at low/high threshold positions. @default false */
|
|
33
|
+
showTicks?: boolean;
|
|
34
|
+
/** Bar thickness. @default "md" */
|
|
35
|
+
size?: MeterSize;
|
|
36
|
+
className?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Displays a scalar measurement within a known range. Use `low` / `high` /
|
|
41
|
+
* `optimum` thresholds to colour the fill based on whether the value is in
|
|
42
|
+
* an optimal, suboptimal, or critical zone.
|
|
43
|
+
*/
|
|
44
|
+
export function Meter({
|
|
45
|
+
value,
|
|
46
|
+
min = 0,
|
|
47
|
+
max = 100,
|
|
48
|
+
low,
|
|
49
|
+
high,
|
|
50
|
+
optimum = "high",
|
|
51
|
+
label,
|
|
52
|
+
showValue = false,
|
|
53
|
+
format,
|
|
54
|
+
showTicks = false,
|
|
55
|
+
size = "md",
|
|
56
|
+
className = "",
|
|
57
|
+
}: MeterProps) {
|
|
58
|
+
const clampedValue = Math.min(Math.max(value, min), max);
|
|
59
|
+
const state = getMeterState(clampedValue, min, max, low, high, optimum);
|
|
60
|
+
const lowPct = low != null ? ((low - min) / (max - min)) * 100 : null;
|
|
61
|
+
const highPct = high != null ? ((high - min) / (max - min)) * 100 : null;
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<BaseMeter.Root
|
|
65
|
+
value={clampedValue}
|
|
66
|
+
min={min}
|
|
67
|
+
max={max}
|
|
68
|
+
{...(format !== undefined && { format })}
|
|
69
|
+
className={`${styles.root} ${className}`}
|
|
70
|
+
>
|
|
71
|
+
{(label != null || showValue) && (
|
|
72
|
+
<div className={styles["label-row"]}>
|
|
73
|
+
{label != null && <BaseMeter.Label className={styles.label}>{label}</BaseMeter.Label>}
|
|
74
|
+
{showValue && <BaseMeter.Value className={styles.value} />}
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
77
|
+
<BaseMeter.Track className={`${styles.track} ${styles[`track-${size}`]}`}>
|
|
78
|
+
<BaseMeter.Indicator className={styles.indicator} data-meter-state={state} />
|
|
79
|
+
{showTicks && lowPct != null && <div className={styles.tick} style={{ left: `${lowPct}%` }} />}
|
|
80
|
+
{showTicks && highPct != null && <div className={styles.tick} style={{ left: `${highPct}%` }} />}
|
|
81
|
+
</BaseMeter.Track>
|
|
82
|
+
</BaseMeter.Root>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export { styles as MeterStyles };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derives the semantic zone from value + thresholds,
|
|
3
|
+
* following the HTML <meter> spec colouring algorithm.
|
|
4
|
+
*
|
|
5
|
+
* Returns undefined when no thresholds are defined (plain accent colour).
|
|
6
|
+
*/
|
|
7
|
+
export function getMeterState(
|
|
8
|
+
value: number,
|
|
9
|
+
min: number,
|
|
10
|
+
max: number,
|
|
11
|
+
low: number | undefined,
|
|
12
|
+
high: number | undefined,
|
|
13
|
+
optimum: "low" | "mid" | "high",
|
|
14
|
+
): "optimum" | "suboptimal" | "critical" | undefined {
|
|
15
|
+
if (low === undefined && high === undefined) return undefined;
|
|
16
|
+
|
|
17
|
+
const effectiveLow = low ?? min;
|
|
18
|
+
const effectiveHigh = high ?? max;
|
|
19
|
+
|
|
20
|
+
const zone: "low" | "mid" | "high" = value <= effectiveLow ? "low" : value >= effectiveHigh ? "high" : "mid";
|
|
21
|
+
|
|
22
|
+
if (zone === optimum) return "optimum";
|
|
23
|
+
|
|
24
|
+
// "mid" optimum → no critical zone, extremes are equally suboptimal
|
|
25
|
+
if (optimum === "mid") return "suboptimal";
|
|
26
|
+
|
|
27
|
+
// "low" or "high" optimum → adjacent zone = suboptimal, far end = critical
|
|
28
|
+
return zone === "mid" ? "suboptimal" : "critical";
|
|
29
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Styled primitives for Meter.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```tsx
|
|
6
|
+
* import { Meter } from '@base-ui/react/meter';
|
|
7
|
+
* import { MeterTrack, MeterIndicator, MeterLabel, MeterValue } from '@brijbyte/agentic-ui/meter';
|
|
8
|
+
*
|
|
9
|
+
* <Meter.Root value={68} min={0} max={100}>
|
|
10
|
+
* <div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
11
|
+
* <MeterLabel>Storage</MeterLabel>
|
|
12
|
+
* <MeterValue />
|
|
13
|
+
* </div>
|
|
14
|
+
* <MeterTrack>
|
|
15
|
+
* <MeterIndicator />
|
|
16
|
+
* </MeterTrack>
|
|
17
|
+
* </Meter.Root>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
import { forwardRef } from "react";
|
|
21
|
+
import type { ComponentRef, ComponentPropsWithoutRef } from "react";
|
|
22
|
+
import { Meter as BaseMeter } from "@base-ui/react/meter";
|
|
23
|
+
import styles from "./meter.module.css";
|
|
24
|
+
|
|
25
|
+
type BaseTrackProps = ComponentPropsWithoutRef<typeof BaseMeter.Track>;
|
|
26
|
+
type BaseIndicatorProps = ComponentPropsWithoutRef<typeof BaseMeter.Indicator>;
|
|
27
|
+
type BaseLabelProps = ComponentPropsWithoutRef<typeof BaseMeter.Label>;
|
|
28
|
+
type BaseValueProps = ComponentPropsWithoutRef<typeof BaseMeter.Value>;
|
|
29
|
+
|
|
30
|
+
export interface MeterTrackProps extends Omit<BaseTrackProps, "className"> {
|
|
31
|
+
className?: string;
|
|
32
|
+
size?: "sm" | "md" | "lg";
|
|
33
|
+
}
|
|
34
|
+
export interface MeterIndicatorProps extends Omit<BaseIndicatorProps, "className"> {
|
|
35
|
+
className?: string;
|
|
36
|
+
/** Threshold state — controls fill colour. Set automatically by the high-level Meter wrapper. */
|
|
37
|
+
"data-meter-state"?: "optimum" | "suboptimal" | "critical";
|
|
38
|
+
}
|
|
39
|
+
export interface MeterLabelProps extends Omit<BaseLabelProps, "className"> {
|
|
40
|
+
className?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface MeterValueProps extends Omit<BaseValueProps, "className"> {
|
|
43
|
+
className?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const MeterTrack = forwardRef<ComponentRef<typeof BaseMeter.Track>, MeterTrackProps>(function MeterTrack(
|
|
47
|
+
{ className, size = "md", ...props },
|
|
48
|
+
ref,
|
|
49
|
+
) {
|
|
50
|
+
return <BaseMeter.Track ref={ref} className={`${styles.track} ${styles[`track-${size}`]} ${className ?? ""}`} {...props} />;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export const MeterIndicator = forwardRef<ComponentRef<typeof BaseMeter.Indicator>, MeterIndicatorProps>(function MeterIndicator(
|
|
54
|
+
{ className, ...props },
|
|
55
|
+
ref,
|
|
56
|
+
) {
|
|
57
|
+
return <BaseMeter.Indicator ref={ref} className={`${styles.indicator} ${className ?? ""}`} {...props} />;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export const MeterLabel = forwardRef<ComponentRef<typeof BaseMeter.Label>, MeterLabelProps>(function MeterLabel(
|
|
61
|
+
{ className, ...props },
|
|
62
|
+
ref,
|
|
63
|
+
) {
|
|
64
|
+
return <BaseMeter.Label ref={ref} className={`${styles.label} ${className ?? ""}`} {...props} />;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export const MeterValue = forwardRef<ComponentRef<typeof BaseMeter.Value>, MeterValueProps>(function MeterValue(
|
|
68
|
+
{ className, ...props },
|
|
69
|
+
ref,
|
|
70
|
+
) {
|
|
71
|
+
return <BaseMeter.Value ref={ref} className={`${styles.value} ${className ?? ""}`} {...props} />;
|
|
72
|
+
});
|
|
@@ -6,17 +6,29 @@ import styles from "./number-field.module.css";
|
|
|
6
6
|
export interface NumberFieldProps {
|
|
7
7
|
/** Visible label text. When present a scrub-area is also rendered. */
|
|
8
8
|
label?: ReactNode;
|
|
9
|
+
/** Initial value (uncontrolled). */
|
|
9
10
|
defaultValue?: number;
|
|
11
|
+
/** Current value (controlled). `null` when the field is empty. */
|
|
10
12
|
value?: number | null;
|
|
13
|
+
/** Called when the value changes. */
|
|
11
14
|
onValueChange?: (value: number | null) => void;
|
|
15
|
+
/** Minimum allowed value. */
|
|
12
16
|
min?: number;
|
|
17
|
+
/** Maximum allowed value. */
|
|
13
18
|
max?: number;
|
|
19
|
+
/** Increment/decrement step size. @default 1 */
|
|
14
20
|
step?: number;
|
|
21
|
+
/** Prevent interaction. */
|
|
15
22
|
disabled?: boolean;
|
|
23
|
+
/** Allow reading but not editing. */
|
|
16
24
|
readOnly?: boolean;
|
|
25
|
+
/** Mark the field as required for form validation. */
|
|
17
26
|
required?: boolean;
|
|
27
|
+
/** HTML name attribute for form submission. */
|
|
18
28
|
name?: string;
|
|
29
|
+
/** `Intl.NumberFormatOptions` for display formatting. */
|
|
19
30
|
format?: Intl.NumberFormatOptions;
|
|
31
|
+
/** Allow changing the value by scrolling the mouse wheel. */
|
|
20
32
|
allowWheelScrub?: boolean;
|
|
21
33
|
className?: string;
|
|
22
34
|
}
|
|
@@ -45,6 +57,10 @@ function ScrubCursorIcon() {
|
|
|
45
57
|
);
|
|
46
58
|
}
|
|
47
59
|
|
|
60
|
+
/**
|
|
61
|
+
* A numeric input with stepper buttons and a scrub area. Supports min/max
|
|
62
|
+
* clamping, step size, locale-aware formatting, and keyboard/scroll input.
|
|
63
|
+
*/
|
|
48
64
|
export function NumberField({ label, className, onValueChange, ...props }: NumberFieldProps) {
|
|
49
65
|
const id = useId();
|
|
50
66
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { Popover } from "./popover";
|
|
2
|
+
export type { PopoverProps } from "./popover";
|
|
3
|
+
export { PopoverStyles } from "./popover";
|
|
4
|
+
|
|
5
|
+
export { PopoverPositioner, PopoverPopup, PopoverTitle, PopoverDescription, PopoverClose, PopoverArrow, PopoverViewport } from "./parts";
|
|
6
|
+
export type {
|
|
7
|
+
PopoverPositionerProps,
|
|
8
|
+
PopoverPopupProps,
|
|
9
|
+
PopoverTitleProps,
|
|
10
|
+
PopoverDescriptionProps,
|
|
11
|
+
PopoverCloseProps,
|
|
12
|
+
PopoverArrowProps,
|
|
13
|
+
PopoverViewportProps,
|
|
14
|
+
} from "./parts";
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Styled primitives for Popover.
|
|
3
|
+
*
|
|
4
|
+
* Use these when you need full control over the popover's behaviour
|
|
5
|
+
* (controlled state, custom positioning, anchor overrides) while keeping
|
|
6
|
+
* all agentic-ui styles applied.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* import { Popover } from '@base-ui/react/popover';
|
|
11
|
+
* import {
|
|
12
|
+
* PopoverPopup,
|
|
13
|
+
* PopoverTitle,
|
|
14
|
+
* PopoverDescription,
|
|
15
|
+
* PopoverClose,
|
|
16
|
+
* PopoverArrow,
|
|
17
|
+
* } from '@brijbyte/agentic-ui/popover';
|
|
18
|
+
*
|
|
19
|
+
* <Popover.Root>
|
|
20
|
+
* <Popover.Trigger render={<button>Open</button>} />
|
|
21
|
+
* <Popover.Portal>
|
|
22
|
+
* <Popover.Positioner side="bottom" sideOffset={8}>
|
|
23
|
+
* <PopoverPopup>
|
|
24
|
+
* <PopoverArrow />
|
|
25
|
+
* <PopoverTitle>Details</PopoverTitle>
|
|
26
|
+
* <PopoverDescription>Supporting text here.</PopoverDescription>
|
|
27
|
+
* <PopoverClose aria-label="Close" />
|
|
28
|
+
* </PopoverPopup>
|
|
29
|
+
* </Popover.Positioner>
|
|
30
|
+
* </Popover.Portal>
|
|
31
|
+
* </Popover.Root>
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
import { forwardRef } from "react";
|
|
35
|
+
import type { ComponentRef, ComponentPropsWithoutRef } from "react";
|
|
36
|
+
import { Popover as BasePopover } from "@base-ui/react/popover";
|
|
37
|
+
import styles from "./popover.module.css";
|
|
38
|
+
import { PopupArrow } from "../shared/PopupArrow";
|
|
39
|
+
|
|
40
|
+
type BasePositionerProps = ComponentPropsWithoutRef<typeof BasePopover.Positioner>;
|
|
41
|
+
type BasePopupProps = ComponentPropsWithoutRef<typeof BasePopover.Popup>;
|
|
42
|
+
type BaseTitleProps = ComponentPropsWithoutRef<typeof BasePopover.Title>;
|
|
43
|
+
type BaseDescriptionProps = ComponentPropsWithoutRef<typeof BasePopover.Description>;
|
|
44
|
+
type BaseCloseProps = ComponentPropsWithoutRef<typeof BasePopover.Close>;
|
|
45
|
+
type BaseArrowProps = ComponentPropsWithoutRef<typeof BasePopover.Arrow>;
|
|
46
|
+
type BaseViewportProps = ComponentPropsWithoutRef<typeof BasePopover.Viewport>;
|
|
47
|
+
|
|
48
|
+
export interface PopoverPositionerProps extends Omit<BasePositionerProps, "className"> {
|
|
49
|
+
className?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface PopoverPopupProps extends Omit<BasePopupProps, "className"> {
|
|
52
|
+
className?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface PopoverTitleProps extends Omit<BaseTitleProps, "className"> {
|
|
55
|
+
className?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface PopoverDescriptionProps extends Omit<BaseDescriptionProps, "className"> {
|
|
58
|
+
className?: string;
|
|
59
|
+
}
|
|
60
|
+
export interface PopoverCloseProps extends Omit<BaseCloseProps, "className"> {
|
|
61
|
+
className?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface PopoverArrowProps extends Omit<BaseArrowProps, "className"> {
|
|
64
|
+
className?: string;
|
|
65
|
+
}
|
|
66
|
+
export interface PopoverViewportProps extends Omit<BaseViewportProps, "className"> {
|
|
67
|
+
className?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const PopoverPositioner = forwardRef<ComponentRef<typeof BasePopover.Positioner>, PopoverPositionerProps>(function PopoverPositioner(
|
|
71
|
+
{ className, ...props },
|
|
72
|
+
ref,
|
|
73
|
+
) {
|
|
74
|
+
return <BasePopover.Positioner ref={ref} className={`${styles.positioner} ${className ?? ""}`} {...props} />;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
export const PopoverPopup = forwardRef<ComponentRef<typeof BasePopover.Popup>, PopoverPopupProps>(function PopoverPopup(
|
|
78
|
+
{ className, ...props },
|
|
79
|
+
ref,
|
|
80
|
+
) {
|
|
81
|
+
return <BasePopover.Popup ref={ref} className={`${styles.popup} ${className ?? ""}`} {...props} />;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
export const PopoverTitle = forwardRef<ComponentRef<typeof BasePopover.Title>, PopoverTitleProps>(function PopoverTitle(
|
|
85
|
+
{ className, ...props },
|
|
86
|
+
ref,
|
|
87
|
+
) {
|
|
88
|
+
return <BasePopover.Title ref={ref} className={`${styles.title} ${className ?? ""}`} {...props} />;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export const PopoverDescription = forwardRef<ComponentRef<typeof BasePopover.Description>, PopoverDescriptionProps>(
|
|
92
|
+
function PopoverDescription({ className, ...props }, ref) {
|
|
93
|
+
return <BasePopover.Description ref={ref} className={`${styles.description} ${className ?? ""}`} {...props} />;
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
export const PopoverClose = forwardRef<ComponentRef<typeof BasePopover.Close>, PopoverCloseProps>(function PopoverClose(
|
|
98
|
+
{ className, ...props },
|
|
99
|
+
ref,
|
|
100
|
+
) {
|
|
101
|
+
return <BasePopover.Close ref={ref} className={`${styles.close} ${className ?? ""}`} {...props} />;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
export const PopoverViewport = forwardRef<ComponentRef<typeof BasePopover.Viewport>, PopoverViewportProps>(function PopoverViewport(
|
|
105
|
+
{ className, ...props },
|
|
106
|
+
ref,
|
|
107
|
+
) {
|
|
108
|
+
return <BasePopover.Viewport ref={ref} className={`${styles.viewport} ${className ?? ""}`} {...props} />;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
export const PopoverArrow = forwardRef<ComponentRef<typeof BasePopover.Arrow>, PopoverArrowProps>(function PopoverArrow(
|
|
112
|
+
{ className, children, ...props },
|
|
113
|
+
ref,
|
|
114
|
+
) {
|
|
115
|
+
return (
|
|
116
|
+
<BasePopover.Arrow ref={ref} className={`${styles.arrow} ${className ?? ""}`} {...props}>
|
|
117
|
+
{children ?? <PopupArrow fillClass={styles["arrow-fill"]!} seamClass={styles["arrow-seam"]!} />}
|
|
118
|
+
</BasePopover.Arrow>
|
|
119
|
+
);
|
|
120
|
+
});
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.positioner {
|
|
3
|
+
z-index: var(--z-popover);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.popup {
|
|
7
|
+
/* Arrow colour tokens consumed by .arrow-fill / .arrow-stroke below */
|
|
8
|
+
--arrow-fill: var(--color-elevated);
|
|
9
|
+
--arrow-stroke: var(--color-line);
|
|
10
|
+
background-color: var(--color-elevated);
|
|
11
|
+
border: var(--border-width-base) solid var(--color-line);
|
|
12
|
+
border-radius: var(--radius-xl);
|
|
13
|
+
box-shadow: var(--shadow-popover);
|
|
14
|
+
padding: var(--space-4);
|
|
15
|
+
min-width: 220px;
|
|
16
|
+
max-width: 320px;
|
|
17
|
+
outline: none;
|
|
18
|
+
transition: opacity 200ms var(--easing-ease-out);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.popup[data-starting-style] {
|
|
22
|
+
opacity: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.popup[data-ending-style] {
|
|
26
|
+
opacity: 0;
|
|
27
|
+
transition: opacity 100ms var(--easing-ease-in);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Instant dismiss/click — skip the exit animation */
|
|
31
|
+
.popup[data-instant] {
|
|
32
|
+
transition-duration: 0ms;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@media (prefers-reduced-motion: reduce) {
|
|
36
|
+
.popup[data-starting-style],
|
|
37
|
+
.popup[data-ending-style] {
|
|
38
|
+
transition: none;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* ── Internals ──────────────────────────────────────────────────── */
|
|
43
|
+
|
|
44
|
+
.title {
|
|
45
|
+
font-family: var(--font-mono);
|
|
46
|
+
font-size: var(--font-size-md);
|
|
47
|
+
font-weight: var(--font-weight-semibold);
|
|
48
|
+
color: var(--color-primary);
|
|
49
|
+
line-height: var(--line-height-tight);
|
|
50
|
+
margin: 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.description {
|
|
54
|
+
font-family: var(--font-mono);
|
|
55
|
+
font-size: var(--font-size-sm);
|
|
56
|
+
color: var(--color-secondary);
|
|
57
|
+
line-height: var(--line-height-relaxed);
|
|
58
|
+
margin: 0;
|
|
59
|
+
margin-top: var(--space-1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.close {
|
|
63
|
+
position: absolute;
|
|
64
|
+
top: var(--space-3);
|
|
65
|
+
right: var(--space-3);
|
|
66
|
+
width: 22px;
|
|
67
|
+
height: 22px;
|
|
68
|
+
border-radius: var(--radius-sm);
|
|
69
|
+
border: none;
|
|
70
|
+
background: transparent;
|
|
71
|
+
color: var(--color-tertiary);
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
justify-content: center;
|
|
76
|
+
outline: none;
|
|
77
|
+
flex-shrink: 0;
|
|
78
|
+
transition:
|
|
79
|
+
background-color var(--duration-fast) var(--easing-standard),
|
|
80
|
+
color var(--duration-fast) var(--easing-standard),
|
|
81
|
+
transform 100ms var(--easing-ease-out);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.close:hover {
|
|
85
|
+
background-color: var(--color-hover);
|
|
86
|
+
color: var(--color-primary);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.close:active {
|
|
90
|
+
transform: scale(0.94);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.close:focus-visible {
|
|
94
|
+
box-shadow: var(--shadow-focus);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* ── Viewport ───────────────────────────────────────────────────── */
|
|
98
|
+
|
|
99
|
+
.viewport {
|
|
100
|
+
position: relative;
|
|
101
|
+
overflow: clip;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* [data-current] and [data-previous] are direct children injected by
|
|
105
|
+
base-ui's Viewport. Both get transition so the slide is smooth. */
|
|
106
|
+
.viewport [data-current],
|
|
107
|
+
.viewport [data-previous] {
|
|
108
|
+
transition:
|
|
109
|
+
transform 200ms var(--easing-ease-out),
|
|
110
|
+
opacity 150ms var(--easing-ease-out);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* Entering content */
|
|
114
|
+
.viewport [data-current][data-starting-style] {
|
|
115
|
+
opacity: 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Exiting content */
|
|
119
|
+
.viewport [data-previous][data-ending-style] {
|
|
120
|
+
opacity: 0;
|
|
121
|
+
transition:
|
|
122
|
+
transform 200ms var(--easing-ease-in),
|
|
123
|
+
opacity 150ms var(--easing-ease-in);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* Slide direction: previous exits opposite, current enters from direction */
|
|
127
|
+
.viewport[data-activation-direction~="down"] [data-current][data-starting-style] {
|
|
128
|
+
transform: translateY(-6px);
|
|
129
|
+
}
|
|
130
|
+
.viewport[data-activation-direction~="down"] [data-previous][data-ending-style] {
|
|
131
|
+
transform: translateY(6px);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.viewport[data-activation-direction~="up"] [data-current][data-starting-style] {
|
|
135
|
+
transform: translateY(6px);
|
|
136
|
+
}
|
|
137
|
+
.viewport[data-activation-direction~="up"] [data-previous][data-ending-style] {
|
|
138
|
+
transform: translateY(-6px);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.viewport[data-activation-direction~="right"] [data-current][data-starting-style] {
|
|
142
|
+
transform: translateX(-6px);
|
|
143
|
+
}
|
|
144
|
+
.viewport[data-activation-direction~="right"] [data-previous][data-ending-style] {
|
|
145
|
+
transform: translateX(6px);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.viewport[data-activation-direction~="left"] [data-current][data-starting-style] {
|
|
149
|
+
transform: translateX(6px);
|
|
150
|
+
}
|
|
151
|
+
.viewport[data-activation-direction~="left"] [data-previous][data-ending-style] {
|
|
152
|
+
transform: translateX(-6px);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@media (prefers-reduced-motion: reduce) {
|
|
156
|
+
.viewport [data-current],
|
|
157
|
+
.viewport [data-previous] {
|
|
158
|
+
transition: opacity 150ms var(--easing-ease-out);
|
|
159
|
+
transform: none !important;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* ── Arrow ──────────────────────────────────────────────────────── */
|
|
164
|
+
.arrow {
|
|
165
|
+
display: flex;
|
|
166
|
+
}
|
|
167
|
+
.arrow[data-side="top"] {
|
|
168
|
+
bottom: -8px;
|
|
169
|
+
rotate: 180deg;
|
|
170
|
+
}
|
|
171
|
+
.arrow[data-side="bottom"] {
|
|
172
|
+
top: -8px;
|
|
173
|
+
rotate: 0deg;
|
|
174
|
+
}
|
|
175
|
+
.arrow[data-side="left"] {
|
|
176
|
+
right: -13px;
|
|
177
|
+
rotate: 90deg;
|
|
178
|
+
}
|
|
179
|
+
.arrow[data-side="right"] {
|
|
180
|
+
left: -13px;
|
|
181
|
+
rotate: -90deg;
|
|
182
|
+
}
|
|
183
|
+
.arrow-fill {
|
|
184
|
+
fill: var(--color-elevated);
|
|
185
|
+
}
|
|
186
|
+
.arrow-seam {
|
|
187
|
+
fill: var(--color-line);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { ReactNode, ReactElement } from "react";
|
|
2
|
+
import { Popover as BasePopover } from "@base-ui/react/popover";
|
|
3
|
+
import styles from "./popover.module.css";
|
|
4
|
+
import { PopupArrow } from "../shared/PopupArrow";
|
|
5
|
+
|
|
6
|
+
export interface PopoverProps {
|
|
7
|
+
/** The trigger element — rendered as-is, receives the popover open/close handler. */
|
|
8
|
+
trigger: ReactElement;
|
|
9
|
+
/** Optional heading rendered at the top of the popup. */
|
|
10
|
+
title?: ReactNode;
|
|
11
|
+
/** Optional supporting text rendered below the title. */
|
|
12
|
+
description?: ReactNode;
|
|
13
|
+
/** Body content of the popover. */
|
|
14
|
+
children?: ReactNode;
|
|
15
|
+
/** Which side of the trigger to open on. @default "bottom" */
|
|
16
|
+
side?: "top" | "bottom" | "left" | "right";
|
|
17
|
+
/** Alignment relative to the trigger. @default "center" */
|
|
18
|
+
align?: "start" | "center" | "end";
|
|
19
|
+
/** Gap between trigger and popup in px. @default 8 */
|
|
20
|
+
sideOffset?: number;
|
|
21
|
+
/** Show a close (×) button in the top-right corner. @default false */
|
|
22
|
+
dismissible?: boolean;
|
|
23
|
+
/** Controlled open state. */
|
|
24
|
+
open?: boolean;
|
|
25
|
+
/** Uncontrolled default open state. */
|
|
26
|
+
defaultOpen?: boolean;
|
|
27
|
+
/** Called when the popover opens or closes. */
|
|
28
|
+
onOpenChange?: (open: boolean, eventDetails: unknown) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function XIcon() {
|
|
32
|
+
return (
|
|
33
|
+
<svg width="10" height="10" viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
|
34
|
+
<path d="M2 2L10 10M10 2L2 10" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" />
|
|
35
|
+
</svg>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Floating content panel anchored to a trigger. Scales from the trigger's
|
|
41
|
+
* edge and repositions automatically to avoid viewport overflow. Supports
|
|
42
|
+
* title, description, close button, and arbitrary body content.
|
|
43
|
+
*/
|
|
44
|
+
export function Popover({
|
|
45
|
+
trigger,
|
|
46
|
+
title,
|
|
47
|
+
description,
|
|
48
|
+
children,
|
|
49
|
+
side = "bottom",
|
|
50
|
+
align = "center",
|
|
51
|
+
sideOffset = 8,
|
|
52
|
+
dismissible = false,
|
|
53
|
+
onOpenChange,
|
|
54
|
+
...props
|
|
55
|
+
}: PopoverProps) {
|
|
56
|
+
return (
|
|
57
|
+
<BasePopover.Root onOpenChange={onOpenChange as never} {...props}>
|
|
58
|
+
<BasePopover.Trigger render={trigger} />
|
|
59
|
+
<BasePopover.Portal>
|
|
60
|
+
<BasePopover.Positioner className={styles.positioner} side={side} align={align} sideOffset={sideOffset} arrowPadding={8}>
|
|
61
|
+
<BasePopover.Popup className={styles.popup}>
|
|
62
|
+
<BasePopover.Arrow className={styles.arrow}>
|
|
63
|
+
<PopupArrow fillClass={styles["arrow-fill"]!} seamClass={styles["arrow-seam"]!} />
|
|
64
|
+
</BasePopover.Arrow>
|
|
65
|
+
{dismissible && (
|
|
66
|
+
<BasePopover.Close className={styles.close} aria-label="Close">
|
|
67
|
+
<XIcon />
|
|
68
|
+
</BasePopover.Close>
|
|
69
|
+
)}
|
|
70
|
+
{title && <BasePopover.Title className={styles.title}>{title}</BasePopover.Title>}
|
|
71
|
+
{description && <BasePopover.Description className={styles.description}>{description}</BasePopover.Description>}
|
|
72
|
+
{children}
|
|
73
|
+
</BasePopover.Popup>
|
|
74
|
+
</BasePopover.Positioner>
|
|
75
|
+
</BasePopover.Portal>
|
|
76
|
+
</BasePopover.Root>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export { styles as PopoverStyles };
|
|
@@ -5,15 +5,26 @@ export type ProgressStatus = "default" | "success" | "warning" | "error";
|
|
|
5
5
|
export type ProgressSize = "sm" | "md" | "lg";
|
|
6
6
|
|
|
7
7
|
export interface ProgressProps {
|
|
8
|
+
/** Current progress value. `null` for indeterminate state. */
|
|
8
9
|
value?: number | null;
|
|
10
|
+
/** Maximum value (the "complete" end). @default 100 */
|
|
9
11
|
max?: number;
|
|
12
|
+
/** Accessible label shown above the bar. */
|
|
10
13
|
label?: string;
|
|
14
|
+
/** Display the percentage next to the label. @default false */
|
|
11
15
|
showValue?: boolean;
|
|
16
|
+
/** Semantic colour for the progress fill. @default "default" */
|
|
12
17
|
status?: ProgressStatus;
|
|
18
|
+
/** Bar thickness. @default "md" */
|
|
13
19
|
size?: ProgressSize;
|
|
14
20
|
className?: string;
|
|
15
21
|
}
|
|
16
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Linear progress indicator with optional label and percentage display.
|
|
25
|
+
* Pass `value={null}` for an indeterminate animated state. Use `status`
|
|
26
|
+
* to apply semantic colour to the fill.
|
|
27
|
+
*/
|
|
17
28
|
export function Progress({ value = null, max = 100, label, showValue = false, status = "default", size = "md", className }: ProgressProps) {
|
|
18
29
|
const percentage = value != null ? Math.round((value / max) * 100) : null;
|
|
19
30
|
|