@brijbyte/agentic-ui 0.0.3 → 0.0.5

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 (289) hide show
  1. package/README.md +280 -53
  2. package/dist/accordion/accordion.d.ts +14 -0
  3. package/dist/accordion/accordion.d.ts.map +1 -1
  4. package/dist/accordion/accordion.js +5 -0
  5. package/dist/accordion/accordion.js.map +1 -1
  6. package/dist/accordion/index.d.ts +2 -2
  7. package/dist/accordion/index.js +2 -2
  8. package/dist/accordion/parts.d.ts +6 -14
  9. package/dist/accordion/parts.d.ts.map +1 -1
  10. package/dist/accordion/parts.js +7 -6
  11. package/dist/accordion/parts.js.map +1 -1
  12. package/dist/alert-dialog/alert-dialog.d.ts +11 -0
  13. package/dist/alert-dialog/alert-dialog.d.ts.map +1 -1
  14. package/dist/alert-dialog/alert-dialog.js +5 -0
  15. package/dist/alert-dialog/alert-dialog.js.map +1 -1
  16. package/dist/alert-dialog/parts.d.ts +4 -12
  17. package/dist/alert-dialog/parts.d.ts.map +1 -1
  18. package/dist/alert-dialog/parts.js +17 -19
  19. package/dist/alert-dialog/parts.js.map +1 -1
  20. package/dist/badge/badge.d.ts +6 -0
  21. package/dist/badge/badge.d.ts.map +1 -1
  22. package/dist/badge/badge.js +4 -0
  23. package/dist/badge/badge.js.map +1 -1
  24. package/dist/button/button.d.ts +12 -1
  25. package/dist/button/button.d.ts.map +1 -1
  26. package/dist/button/button.js +5 -0
  27. package/dist/button/button.js.map +1 -1
  28. package/dist/card/card.d.ts +10 -0
  29. package/dist/card/card.d.ts.map +1 -1
  30. package/dist/card/card.js +7 -0
  31. package/dist/card/card.js.map +1 -1
  32. package/dist/checkbox/checkbox.d.ts +13 -0
  33. package/dist/checkbox/checkbox.d.ts.map +1 -1
  34. package/dist/checkbox/checkbox.js +4 -0
  35. package/dist/checkbox/checkbox.js.map +1 -1
  36. package/dist/checkbox/parts.d.ts +2 -5
  37. package/dist/checkbox/parts.d.ts.map +1 -1
  38. package/dist/checkbox/parts.js +3 -2
  39. package/dist/checkbox/parts.js.map +1 -1
  40. package/dist/collapsible/collapsible.d.ts +11 -0
  41. package/dist/collapsible/collapsible.d.ts.map +1 -1
  42. package/dist/collapsible/collapsible.js +5 -0
  43. package/dist/collapsible/collapsible.js.map +1 -1
  44. package/dist/collapsible/parts.d.ts +3 -9
  45. package/dist/collapsible/parts.d.ts.map +1 -1
  46. package/dist/collapsible/parts.js +7 -6
  47. package/dist/collapsible/parts.js.map +1 -1
  48. package/dist/context-menu/context-menu.d.ts +5 -0
  49. package/dist/context-menu/context-menu.d.ts.map +1 -1
  50. package/dist/context-menu/context-menu.js +4 -0
  51. package/dist/context-menu/context-menu.js.map +1 -1
  52. package/dist/context-menu/parts.d.ts +6 -18
  53. package/dist/context-menu/parts.d.ts.map +1 -1
  54. package/dist/context-menu/parts.js +16 -16
  55. package/dist/context-menu/parts.js.map +1 -1
  56. package/dist/dialog/dialog.d.ts +13 -1
  57. package/dist/dialog/dialog.d.ts.map +1 -1
  58. package/dist/dialog/dialog.js +6 -0
  59. package/dist/dialog/dialog.js.map +1 -1
  60. package/dist/dialog/parts.d.ts +6 -18
  61. package/dist/dialog/parts.d.ts.map +1 -1
  62. package/dist/dialog/parts.js +8 -9
  63. package/dist/dialog/parts.js.map +1 -1
  64. package/dist/drawer/drawer.d.ts +11 -0
  65. package/dist/drawer/drawer.d.ts.map +1 -1
  66. package/dist/drawer/drawer.js +5 -0
  67. package/dist/drawer/drawer.js.map +1 -1
  68. package/dist/drawer/parts.d.ts +7 -19
  69. package/dist/drawer/parts.d.ts.map +1 -1
  70. package/dist/drawer/parts.js +14 -13
  71. package/dist/drawer/parts.js.map +1 -1
  72. package/dist/index.css +1580 -1150
  73. package/dist/index.d.ts +20 -11
  74. package/dist/index.js +15 -2
  75. package/dist/input/input.d.ts +8 -0
  76. package/dist/input/input.d.ts.map +1 -1
  77. package/dist/input/input.js +5 -0
  78. package/dist/input/input.js.map +1 -1
  79. package/dist/menu/menu.css +3 -8
  80. package/dist/menu/menu.d.ts +11 -4
  81. package/dist/menu/menu.d.ts.map +1 -1
  82. package/dist/menu/menu.js +10 -24
  83. package/dist/menu/menu.js.map +1 -1
  84. package/dist/menu/menu.module.js +1 -1
  85. package/dist/menu/menu.module.js.map +1 -1
  86. package/dist/menu/parts.d.ts +6 -18
  87. package/dist/menu/parts.d.ts.map +1 -1
  88. package/dist/menu/parts.js +7 -6
  89. package/dist/menu/parts.js.map +1 -1
  90. package/dist/meter/circular-meter.d.ts +48 -0
  91. package/dist/meter/circular-meter.d.ts.map +1 -0
  92. package/dist/meter/circular-meter.js +86 -0
  93. package/dist/meter/circular-meter.js.map +1 -0
  94. package/dist/meter/index.d.ts +4 -0
  95. package/dist/meter/index.js +5 -0
  96. package/dist/meter/meter.css +152 -0
  97. package/dist/meter/meter.d.ts +58 -0
  98. package/dist/meter/meter.d.ts.map +1 -0
  99. package/dist/meter/meter.js +50 -0
  100. package/dist/meter/meter.js.map +1 -0
  101. package/dist/meter/meter.module.css.d.ts +2 -0
  102. package/dist/meter/meter.module.js +27 -0
  103. package/dist/meter/meter.module.js.map +1 -0
  104. package/dist/meter/meterState.js +18 -0
  105. package/dist/meter/meterState.js.map +1 -0
  106. package/dist/meter/parts.d.ts +25 -0
  107. package/dist/meter/parts.d.ts.map +1 -0
  108. package/dist/meter/parts.js +57 -0
  109. package/dist/meter/parts.js.map +1 -0
  110. package/dist/number-field/number-field.d.ts +16 -0
  111. package/dist/number-field/number-field.d.ts.map +1 -1
  112. package/dist/number-field/number-field.js +4 -0
  113. package/dist/number-field/number-field.js.map +1 -1
  114. package/dist/number-field/parts.d.ts +6 -18
  115. package/dist/number-field/parts.d.ts.map +1 -1
  116. package/dist/number-field/parts.js +7 -6
  117. package/dist/number-field/parts.js.map +1 -1
  118. package/dist/popover/index.d.ts +3 -0
  119. package/dist/popover/index.js +4 -0
  120. package/dist/popover/parts.d.ts +29 -0
  121. package/dist/popover/parts.d.ts.map +1 -0
  122. package/dist/popover/parts.js +93 -0
  123. package/dist/popover/parts.js.map +1 -0
  124. package/dist/popover/popover.css +173 -0
  125. package/dist/popover/popover.d.ts +49 -0
  126. package/dist/popover/popover.d.ts.map +1 -0
  127. package/dist/popover/popover.js +68 -0
  128. package/dist/popover/popover.js.map +1 -0
  129. package/dist/popover/popover.module.css.d.ts +2 -0
  130. package/dist/popover/popover.module.js +16 -0
  131. package/dist/popover/popover.module.js.map +1 -0
  132. package/dist/progress/parts.d.ts +2 -4
  133. package/dist/progress/parts.d.ts.map +1 -1
  134. package/dist/progress/parts.js +3 -6
  135. package/dist/progress/parts.js.map +1 -1
  136. package/dist/progress/progress.d.ts +11 -0
  137. package/dist/progress/progress.d.ts.map +1 -1
  138. package/dist/progress/progress.js +5 -0
  139. package/dist/progress/progress.js.map +1 -1
  140. package/dist/radio/index.d.ts +3 -0
  141. package/dist/radio/index.js +4 -0
  142. package/dist/radio/parts.d.ts +14 -0
  143. package/dist/radio/parts.d.ts.map +1 -0
  144. package/dist/radio/parts.js +43 -0
  145. package/dist/radio/parts.js.map +1 -0
  146. package/dist/radio/radio.css +84 -0
  147. package/dist/radio/radio.d.ts +31 -0
  148. package/dist/radio/radio.d.ts.map +1 -0
  149. package/dist/radio/radio.js +33 -0
  150. package/dist/radio/radio.js.map +1 -0
  151. package/dist/radio/radio.module.css.d.ts +2 -0
  152. package/dist/radio/radio.module.js +11 -0
  153. package/dist/radio/radio.module.js.map +1 -0
  154. package/dist/radio-group/index.d.ts +3 -0
  155. package/dist/radio-group/index.js +4 -0
  156. package/dist/radio-group/parts.d.ts +11 -0
  157. package/dist/radio-group/parts.d.ts.map +1 -0
  158. package/dist/radio-group/parts.js +32 -0
  159. package/dist/radio-group/parts.js.map +1 -0
  160. package/dist/radio-group/radio-group.css +17 -0
  161. package/dist/radio-group/radio-group.d.ts +37 -0
  162. package/dist/radio-group/radio-group.d.ts.map +1 -0
  163. package/dist/radio-group/radio-group.js +28 -0
  164. package/dist/radio-group/radio-group.js.map +1 -0
  165. package/dist/radio-group/radio-group.module.css.d.ts +2 -0
  166. package/dist/radio-group/radio-group.module.js +9 -0
  167. package/dist/radio-group/radio-group.module.js.map +1 -0
  168. package/dist/reset-scoped.css +112 -0
  169. package/dist/select/parts.d.ts +11 -32
  170. package/dist/select/parts.d.ts.map +1 -1
  171. package/dist/select/parts.js +10 -9
  172. package/dist/select/parts.js.map +1 -1
  173. package/dist/select/select.d.ts +14 -1
  174. package/dist/select/select.d.ts.map +1 -1
  175. package/dist/select/select.js +4 -0
  176. package/dist/select/select.js.map +1 -1
  177. package/dist/separator/separator.d.ts +4 -0
  178. package/dist/separator/separator.d.ts.map +1 -1
  179. package/dist/separator/separator.js +4 -0
  180. package/dist/separator/separator.js.map +1 -1
  181. package/dist/shared/PopupArrow.js +22 -0
  182. package/dist/shared/PopupArrow.js.map +1 -0
  183. package/dist/slider/parts.d.ts +6 -18
  184. package/dist/slider/parts.d.ts.map +1 -1
  185. package/dist/slider/parts.js +7 -6
  186. package/dist/slider/parts.js.map +1 -1
  187. package/dist/slider/slider.d.ts +18 -0
  188. package/dist/slider/slider.d.ts.map +1 -1
  189. package/dist/slider/slider.js +6 -0
  190. package/dist/slider/slider.js.map +1 -1
  191. package/dist/switch/parts.d.ts +2 -6
  192. package/dist/switch/parts.d.ts.map +1 -1
  193. package/dist/switch/parts.js +3 -2
  194. package/dist/switch/parts.js.map +1 -1
  195. package/dist/switch/switch.css +11 -2
  196. package/dist/switch/switch.d.ts +12 -0
  197. package/dist/switch/switch.d.ts.map +1 -1
  198. package/dist/switch/switch.js +4 -0
  199. package/dist/switch/switch.js.map +1 -1
  200. package/dist/switch/switch.module.js.map +1 -1
  201. package/dist/tabs/parts.d.ts +3 -9
  202. package/dist/tabs/parts.d.ts.map +1 -1
  203. package/dist/tabs/parts.js +4 -3
  204. package/dist/tabs/parts.js.map +1 -1
  205. package/dist/tabs/tabs.d.ts +8 -1
  206. package/dist/tabs/tabs.d.ts.map +1 -1
  207. package/dist/tabs/tabs.js +4 -0
  208. package/dist/tabs/tabs.js.map +1 -1
  209. package/dist/toast/parts.d.ts +5 -15
  210. package/dist/toast/parts.d.ts.map +1 -1
  211. package/dist/toast/parts.js +6 -5
  212. package/dist/toast/parts.js.map +1 -1
  213. package/dist/toast/toast.d.ts +11 -0
  214. package/dist/toast/toast.d.ts.map +1 -1
  215. package/dist/toast/toast.js +8 -0
  216. package/dist/toast/toast.js.map +1 -1
  217. package/dist/tooltip/parts.d.ts +3 -9
  218. package/dist/tooltip/parts.d.ts.map +1 -1
  219. package/dist/tooltip/parts.js +4 -3
  220. package/dist/tooltip/parts.js.map +1 -1
  221. package/dist/tooltip/tooltip.d.ts +9 -0
  222. package/dist/tooltip/tooltip.d.ts.map +1 -1
  223. package/dist/tooltip/tooltip.js +4 -0
  224. package/dist/tooltip/tooltip.js.map +1 -1
  225. package/dist/utils/resolveClassName.js +25 -0
  226. package/dist/utils/resolveClassName.js.map +1 -0
  227. package/package.json +25 -4
  228. package/src/accordion/accordion.tsx +14 -0
  229. package/src/accordion/index.ts +1 -1
  230. package/src/accordion/parts.tsx +10 -17
  231. package/src/alert-dialog/alert-dialog.tsx +11 -0
  232. package/src/alert-dialog/parts.tsx +23 -31
  233. package/src/badge/badge.tsx +6 -0
  234. package/src/button/button.tsx +12 -1
  235. package/src/card/card.tsx +10 -0
  236. package/src/checkbox/checkbox.tsx +13 -0
  237. package/src/checkbox/parts.tsx +5 -7
  238. package/src/collapsible/collapsible.tsx +11 -0
  239. package/src/collapsible/parts.tsx +10 -15
  240. package/src/context-menu/context-menu.tsx +5 -0
  241. package/src/context-menu/parts.tsx +34 -34
  242. package/src/dialog/dialog.tsx +13 -1
  243. package/src/dialog/parts.tsx +14 -27
  244. package/src/drawer/drawer.tsx +11 -0
  245. package/src/drawer/parts.tsx +30 -38
  246. package/src/index.ts +4 -0
  247. package/src/input/input.tsx +8 -0
  248. package/src/menu/menu.module.css +3 -10
  249. package/src/menu/menu.tsx +13 -26
  250. package/src/menu/parts.tsx +13 -24
  251. package/src/meter/circular-meter.tsx +114 -0
  252. package/src/meter/index.ts +9 -0
  253. package/src/meter/meter.module.css +162 -0
  254. package/src/meter/meter.tsx +86 -0
  255. package/src/meter/meterState.ts +29 -0
  256. package/src/meter/parts.tsx +73 -0
  257. package/src/number-field/number-field.tsx +16 -0
  258. package/src/number-field/parts.tsx +33 -24
  259. package/src/popover/index.ts +14 -0
  260. package/src/popover/parts.tsx +105 -0
  261. package/src/popover/popover.module.css +189 -0
  262. package/src/popover/popover.tsx +80 -0
  263. package/src/progress/parts.tsx +13 -6
  264. package/src/progress/progress.tsx +11 -0
  265. package/src/radio/index.ts +6 -0
  266. package/src/radio/parts.tsx +42 -0
  267. package/src/radio/radio.module.css +96 -0
  268. package/src/radio/radio.tsx +37 -0
  269. package/src/radio-group/index.ts +5 -0
  270. package/src/radio-group/parts.tsx +31 -0
  271. package/src/radio-group/radio-group.module.css +17 -0
  272. package/src/radio-group/radio-group.tsx +63 -0
  273. package/src/select/parts.tsx +34 -41
  274. package/src/select/select.tsx +14 -1
  275. package/src/separator/separator.tsx +4 -0
  276. package/src/shared/PopupArrow.tsx +41 -0
  277. package/src/slider/parts.tsx +13 -24
  278. package/src/slider/slider.tsx +18 -0
  279. package/src/styles/reset-scoped.css +112 -0
  280. package/src/switch/parts.tsx +5 -8
  281. package/src/switch/switch.module.css +11 -2
  282. package/src/switch/switch.tsx +12 -0
  283. package/src/tabs/parts.tsx +7 -12
  284. package/src/tabs/tabs.tsx +8 -1
  285. package/src/toast/parts.tsx +11 -20
  286. package/src/toast/toast.tsx +11 -0
  287. package/src/tooltip/parts.tsx +7 -12
  288. package/src/tooltip/tooltip.tsx +9 -0
  289. package/src/utils/resolveClassName.ts +24 -0
package/src/menu/menu.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { ReactNode, ReactElement } from "react";
2
2
  import { Menu as BaseMenu } from "@base-ui/react/menu";
3
3
  import styles from "./menu.module.css";
4
+ import { PopupArrow } from "../shared/PopupArrow";
4
5
 
5
6
  // ─── Types ──────────────────────────────────────────────────────────────────
6
7
 
@@ -28,40 +29,21 @@ export type MenuEntry = MenuItemDef | MenuSeparatorDef | MenuGroupDef;
28
29
  export interface MenuProps {
29
30
  /** The button/element that opens the menu. */
30
31
  trigger: ReactElement;
32
+ /** Array of menu entries: items, separators, and groups. */
31
33
  items: MenuEntry[];
32
- /** @default "bottom" */
34
+ /** Which edge of the trigger the menu appears on. @default "bottom" */
33
35
  side?: "top" | "bottom" | "left" | "right";
34
- /** @default "start" */
36
+ /** Alignment along the side axis. @default "start" */
35
37
  align?: "start" | "center" | "end";
36
- /** @default 4 */
38
+ /** Gap between the trigger and the menu popup in pixels. @default 4 */
37
39
  sideOffset?: number;
38
- /** controlled open state */
40
+ /** Controlled open state. */
39
41
  open?: boolean;
42
+ /** Called when the menu opens or closes. */
40
43
  onOpenChange?: (open: boolean) => void;
41
44
  className?: string;
42
45
  }
43
46
 
44
- // ─── Arrow SVG ─────────────────────────────────────────────────────────────
45
-
46
- function ArrowSvg() {
47
- return (
48
- <svg width="20" height="10" viewBox="0 0 20 10" fill="none" aria-hidden>
49
- <path
50
- d="M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z"
51
- className={styles["arrow-fill"]}
52
- />
53
- <path
54
- d="M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z"
55
- className={styles["arrow-stroke"]}
56
- />
57
- <path
58
- d="M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z"
59
- className={styles["arrow-stroke"]}
60
- />
61
- </svg>
62
- );
63
- }
64
-
65
47
  // ─── Item renderer ──────────────────────────────────────────────────────────
66
48
 
67
49
  function renderEntry(entry: MenuEntry, index: number): ReactNode {
@@ -88,6 +70,11 @@ function renderEntry(entry: MenuEntry, index: number): ReactNode {
88
70
 
89
71
  // ─── High-level Menu ────────────────────────────────────────────────────────
90
72
 
73
+ /**
74
+ * A dropdown list of actions with full keyboard navigation. Supports
75
+ * separators, groups, and keyboard shortcuts. Items can be disabled
76
+ * individually.
77
+ */
91
78
  export function Menu({ trigger, items, side = "bottom", align = "start", sideOffset = 4, className, onOpenChange, ...props }: MenuProps) {
92
79
  return (
93
80
  <BaseMenu.Root onOpenChange={onOpenChange as never} {...props}>
@@ -96,7 +83,7 @@ export function Menu({ trigger, items, side = "bottom", align = "start", sideOff
96
83
  <BaseMenu.Positioner className={`${styles.positioner} ${className ?? ""}`} side={side} align={align} sideOffset={sideOffset}>
97
84
  <BaseMenu.Popup className={styles.popup}>
98
85
  <BaseMenu.Arrow className={styles.arrow}>
99
- <ArrowSvg />
86
+ <PopupArrow fillClass={styles["arrow-fill"]!} seamClass={styles["arrow-seam"]!} />
100
87
  </BaseMenu.Arrow>
101
88
  {items.map((entry, i) => renderEntry(entry, i))}
102
89
  </BaseMenu.Popup>
@@ -26,6 +26,7 @@ import { forwardRef } from "react";
26
26
  import type { ComponentRef, ComponentPropsWithoutRef } from "react";
27
27
  import { Menu as BaseMenu } from "@base-ui/react/menu";
28
28
  import styles from "./menu.module.css";
29
+ import { resolveClassName, cx } from "../utils/resolveClassName";
29
30
 
30
31
  type BasePositionerProps = ComponentPropsWithoutRef<typeof BaseMenu.Positioner>;
31
32
  type BasePopupProps = ComponentPropsWithoutRef<typeof BaseMenu.Popup>;
@@ -34,56 +35,44 @@ type BaseSeparatorProps = ComponentPropsWithoutRef<typeof BaseMenu.Separator>;
34
35
  type BaseGroupLabelProps = ComponentPropsWithoutRef<typeof BaseMenu.GroupLabel>;
35
36
  type BaseArrowProps = ComponentPropsWithoutRef<typeof BaseMenu.Arrow>;
36
37
 
37
- export interface MenuPositionerProps extends Omit<BasePositionerProps, "className"> {
38
- className?: string;
39
- }
40
- export interface MenuPopupProps extends Omit<BasePopupProps, "className"> {
41
- className?: string;
42
- }
43
- export interface MenuItemProps extends Omit<BaseItemProps, "className"> {
44
- className?: string;
45
- }
46
- export interface MenuSeparatorProps extends Omit<BaseSeparatorProps, "className"> {
47
- className?: string;
48
- }
49
- export interface MenuGroupLabelProps extends Omit<BaseGroupLabelProps, "className"> {
50
- className?: string;
51
- }
52
- export interface MenuArrowProps extends Omit<BaseArrowProps, "className"> {
53
- className?: string;
54
- }
38
+ export interface MenuPositionerProps extends BasePositionerProps {}
39
+ export interface MenuPopupProps extends BasePopupProps {}
40
+ export interface MenuItemProps extends BaseItemProps {}
41
+ export interface MenuSeparatorProps extends BaseSeparatorProps {}
42
+ export interface MenuGroupLabelProps extends BaseGroupLabelProps {}
43
+ export interface MenuArrowProps extends BaseArrowProps {}
55
44
 
56
45
  export const MenuPositioner = forwardRef<ComponentRef<typeof BaseMenu.Positioner>, MenuPositionerProps>(function MenuPositioner(
57
46
  { className, ...props },
58
47
  ref,
59
48
  ) {
60
- return <BaseMenu.Positioner ref={ref} className={`${styles.positioner} ${className ?? ""}`} {...props} />;
49
+ return <BaseMenu.Positioner ref={ref} className={(state) => cx(styles.positioner, resolveClassName(className, state))} {...props} />;
61
50
  });
62
51
 
63
52
  export const MenuPopup = forwardRef<ComponentRef<typeof BaseMenu.Popup>, MenuPopupProps>(function MenuPopup({ className, ...props }, ref) {
64
- return <BaseMenu.Popup ref={ref} className={`${styles.popup} ${className ?? ""}`} {...props} />;
53
+ return <BaseMenu.Popup ref={ref} className={(state) => cx(styles.popup, resolveClassName(className, state))} {...props} />;
65
54
  });
66
55
 
67
56
  export const MenuItem = forwardRef<ComponentRef<typeof BaseMenu.Item>, MenuItemProps>(function MenuItem({ className, ...props }, ref) {
68
- return <BaseMenu.Item ref={ref} className={`${styles.item} ${className ?? ""}`} {...props} />;
57
+ return <BaseMenu.Item ref={ref} className={(state) => cx(styles.item, resolveClassName(className, state))} {...props} />;
69
58
  });
70
59
 
71
60
  export const MenuSeparator = forwardRef<ComponentRef<typeof BaseMenu.Separator>, MenuSeparatorProps>(function MenuSeparator(
72
61
  { className, ...props },
73
62
  ref,
74
63
  ) {
75
- return <BaseMenu.Separator ref={ref} className={`${styles.separator} ${className ?? ""}`} {...props} />;
64
+ return <BaseMenu.Separator ref={ref} className={(state) => cx(styles.separator, resolveClassName(className, state))} {...props} />;
76
65
  });
77
66
 
78
67
  export const MenuGroupLabel = forwardRef<ComponentRef<typeof BaseMenu.GroupLabel>, MenuGroupLabelProps>(function MenuGroupLabel(
79
68
  { className, ...props },
80
69
  ref,
81
70
  ) {
82
- return <BaseMenu.GroupLabel ref={ref} className={`${styles["group-label"]} ${className ?? ""}`} {...props} />;
71
+ return <BaseMenu.GroupLabel ref={ref} className={(state) => cx(styles["group-label"], resolveClassName(className, state))} {...props} />;
83
72
  });
84
73
 
85
74
  export const MenuArrow = forwardRef<ComponentRef<typeof BaseMenu.Arrow>, MenuArrowProps>(function MenuArrow({ className, ...props }, ref) {
86
- return <BaseMenu.Arrow ref={ref} className={`${styles.arrow} ${className ?? ""}`} {...props} />;
75
+ return <BaseMenu.Arrow ref={ref} className={(state) => cx(styles.arrow, resolveClassName(className, state))} {...props} />;
87
76
  });
88
77
 
89
78
  /** Convenience class for keyboard shortcut hints inside a MenuItem. */
@@ -0,0 +1,114 @@
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 CircularMeterSize = "sm" | "md" | "lg";
7
+
8
+ export interface CircularMeterProps {
9
+ /** Current value. Must be between `min` and `max`. */
10
+ value: number;
11
+ /** @default 0 */
12
+ min?: number;
13
+ /** @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 stroke 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 shown below the value inside the circle. */
27
+ label?: ReactNode;
28
+ /** Show the formatted value inside the circle. @default false */
29
+ showValue?: boolean;
30
+ /** `Intl.NumberFormatOptions` for value formatting. */
31
+ format?: Intl.NumberFormatOptions;
32
+ size?: CircularMeterSize;
33
+ className?: string;
34
+ }
35
+
36
+ const SIZES: Record<CircularMeterSize, { diameter: number; strokeWidth: number }> = {
37
+ sm: { diameter: 56, strokeWidth: 5 },
38
+ md: { diameter: 80, strokeWidth: 7 },
39
+ lg: { diameter: 112, strokeWidth: 9 },
40
+ };
41
+
42
+ export function CircularMeter({
43
+ value,
44
+ min = 0,
45
+ max = 100,
46
+ low,
47
+ high,
48
+ optimum = "high",
49
+ label,
50
+ showValue = false,
51
+ format,
52
+ size = "md",
53
+ className = "",
54
+ }: CircularMeterProps) {
55
+ const clampedValue = Math.min(Math.max(value, min), max);
56
+ const state = getMeterState(clampedValue, min, max, low, high, optimum);
57
+
58
+ const { diameter, strokeWidth } = SIZES[size];
59
+ const radius = (diameter - strokeWidth) / 2;
60
+ const circumference = 2 * Math.PI * radius;
61
+ const dashOffset = circumference * (1 - (clampedValue - min) / (max - min));
62
+ const center = diameter / 2;
63
+
64
+ return (
65
+ <BaseMeter.Root
66
+ value={clampedValue}
67
+ min={min}
68
+ max={max}
69
+ {...(format !== undefined && { format })}
70
+ className={`${styles["circular-root"]} ${styles[`circular-${size}`]} ${className}`}
71
+ >
72
+ <div style={{ position: "relative", width: diameter, height: diameter }}>
73
+ <svg
74
+ width={diameter}
75
+ height={diameter}
76
+ viewBox={`0 0 ${diameter} ${diameter}`}
77
+ className={styles["circular-svg"]}
78
+ aria-hidden="true"
79
+ >
80
+ {/* Background track ring */}
81
+ <circle cx={center} cy={center} r={radius} strokeWidth={strokeWidth} className={styles["circular-track"]} />
82
+ {/* Value arc — origin at 12 o'clock */}
83
+ <circle
84
+ cx={center}
85
+ cy={center}
86
+ r={radius}
87
+ strokeWidth={strokeWidth}
88
+ strokeDasharray={circumference}
89
+ strokeDashoffset={dashOffset}
90
+ className={styles["circular-indicator"]}
91
+ data-meter-state={state}
92
+ style={{ transform: "rotate(-90deg)", transformOrigin: "center" }}
93
+ />
94
+ </svg>
95
+
96
+ {(showValue || label != null) && (
97
+ <div
98
+ style={{
99
+ position: "absolute",
100
+ inset: 0,
101
+ display: "flex",
102
+ flexDirection: "column",
103
+ alignItems: "center",
104
+ justifyContent: "center",
105
+ }}
106
+ >
107
+ {showValue && <BaseMeter.Value className={styles["circular-value"]} />}
108
+ {label != null && <BaseMeter.Label className={styles["circular-label"]}>{label}</BaseMeter.Label>}
109
+ </div>
110
+ )}
111
+ </div>
112
+ </BaseMeter.Root>
113
+ );
114
+ }
@@ -0,0 +1,9 @@
1
+ export { Meter } from "./meter";
2
+ export type { MeterProps, MeterSize } from "./meter";
3
+ export { MeterStyles } from "./meter";
4
+
5
+ export { CircularMeter } from "./circular-meter";
6
+ export type { CircularMeterProps, CircularMeterSize } from "./circular-meter";
7
+
8
+ export { MeterTrack, MeterIndicator, MeterLabel, MeterValue } from "./parts";
9
+ export type { MeterTrackProps, MeterIndicatorProps, MeterLabelProps, MeterValueProps } from "./parts";
@@ -0,0 +1,162 @@
1
+ @layer components {
2
+ .root {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: var(--space-1-5);
6
+ width: 100%;
7
+ }
8
+
9
+ /* ── Label row ──────────────────────────────────────────────────── */
10
+
11
+ .label-row {
12
+ display: flex;
13
+ align-items: center;
14
+ justify-content: space-between;
15
+ font-family: var(--font-mono);
16
+ font-size: var(--font-size-xs);
17
+ color: var(--color-secondary);
18
+ }
19
+
20
+ .label {
21
+ font-family: var(--font-mono);
22
+ font-size: var(--font-size-xs);
23
+ color: var(--color-secondary);
24
+ }
25
+
26
+ .value {
27
+ font-family: var(--font-mono);
28
+ font-size: var(--font-size-xs);
29
+ color: var(--color-secondary);
30
+ }
31
+
32
+ /* ── Track (the outer bar) ──────────────────────────────────────── */
33
+
34
+ .track {
35
+ position: relative;
36
+ width: 100%;
37
+ background-color: var(--color-surface-3);
38
+ border-radius: var(--radius-full);
39
+ overflow: hidden;
40
+ }
41
+
42
+ .track-sm {
43
+ height: 4px;
44
+ }
45
+ .track-md {
46
+ height: 6px;
47
+ }
48
+ .track-lg {
49
+ height: 8px;
50
+ }
51
+
52
+ /* ── Indicator (the fill) ───────────────────────────────────────── */
53
+
54
+ .indicator {
55
+ height: 100%;
56
+ border-radius: var(--radius-full);
57
+ background-color: var(--color-accent);
58
+ transition: width var(--duration-slower) var(--easing-standard);
59
+ }
60
+
61
+ /* Threshold-based colours — driven by data attributes set in JS */
62
+ .indicator[data-meter-state="optimum"] {
63
+ background-color: var(--color-success-solid);
64
+ }
65
+ .indicator[data-meter-state="suboptimal"] {
66
+ background-color: var(--color-warning-solid);
67
+ }
68
+ .indicator[data-meter-state="critical"] {
69
+ background-color: var(--color-error-solid);
70
+ }
71
+
72
+ /* ── Tick marks for low / high thresholds ───────────────────────── */
73
+
74
+ .tick {
75
+ position: absolute;
76
+ top: 0;
77
+ bottom: 0;
78
+ width: 1px;
79
+ background-color: var(--color-canvas);
80
+ opacity: 0.6;
81
+ pointer-events: none;
82
+ }
83
+
84
+ /* ── Circular gauge ─────────────────────────────────────────────── */
85
+
86
+ .circular-root {
87
+ display: inline-flex;
88
+ flex-direction: column;
89
+ align-items: center;
90
+ gap: var(--space-1-5);
91
+ }
92
+
93
+ .circular-svg {
94
+ overflow: visible;
95
+ }
96
+
97
+ /* Track ring */
98
+ .circular-track {
99
+ fill: none;
100
+ stroke: var(--color-surface-3);
101
+ }
102
+
103
+ /* Value arc */
104
+ .circular-indicator {
105
+ fill: none;
106
+ stroke: var(--color-accent);
107
+ stroke-linecap: round;
108
+ transition: stroke-dashoffset var(--duration-slower) var(--easing-standard);
109
+ }
110
+
111
+ .circular-indicator[data-meter-state="optimum"] {
112
+ stroke: var(--color-success-solid);
113
+ }
114
+ .circular-indicator[data-meter-state="suboptimal"] {
115
+ stroke: var(--color-warning-solid);
116
+ }
117
+ .circular-indicator[data-meter-state="critical"] {
118
+ stroke: var(--color-error-solid);
119
+ }
120
+
121
+ /* Centre text stack */
122
+ .circular-center {
123
+ display: flex;
124
+ flex-direction: column;
125
+ align-items: center;
126
+ justify-content: center;
127
+ gap: var(--space-0-5);
128
+ }
129
+
130
+ .circular-value {
131
+ font-family: var(--font-mono);
132
+ font-weight: var(--font-weight-semibold);
133
+ color: var(--color-primary);
134
+ line-height: 1;
135
+ }
136
+
137
+ .circular-label {
138
+ font-family: var(--font-mono);
139
+ color: var(--color-secondary);
140
+ line-height: 1;
141
+ }
142
+
143
+ /* Size variants */
144
+ .circular-sm .circular-value {
145
+ font-size: var(--font-size-sm);
146
+ }
147
+ .circular-sm .circular-label {
148
+ font-size: var(--font-size-xs);
149
+ }
150
+ .circular-md .circular-value {
151
+ font-size: var(--font-size-xl);
152
+ }
153
+ .circular-md .circular-label {
154
+ font-size: var(--font-size-xs);
155
+ }
156
+ .circular-lg .circular-value {
157
+ font-size: var(--font-size-2xl);
158
+ }
159
+ .circular-lg .circular-label {
160
+ font-size: var(--font-size-sm);
161
+ }
162
+ }
@@ -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,73 @@
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
+ import { resolveClassName, cx } from "../utils/resolveClassName";
25
+
26
+ type BaseTrackProps = ComponentPropsWithoutRef<typeof BaseMeter.Track>;
27
+ type BaseIndicatorProps = ComponentPropsWithoutRef<typeof BaseMeter.Indicator>;
28
+ type BaseLabelProps = ComponentPropsWithoutRef<typeof BaseMeter.Label>;
29
+ type BaseValueProps = ComponentPropsWithoutRef<typeof BaseMeter.Value>;
30
+
31
+ export interface MeterTrackProps extends BaseTrackProps {
32
+ size?: "sm" | "md" | "lg";
33
+ }
34
+ export interface MeterIndicatorProps extends BaseIndicatorProps {
35
+ /** Threshold state — controls fill colour. Set automatically by the high-level Meter wrapper. */
36
+ "data-meter-state"?: "optimum" | "suboptimal" | "critical";
37
+ }
38
+ export interface MeterLabelProps extends BaseLabelProps {}
39
+ export interface MeterValueProps extends BaseValueProps {}
40
+
41
+ export const MeterTrack = forwardRef<ComponentRef<typeof BaseMeter.Track>, MeterTrackProps>(function MeterTrack(
42
+ { className, size = "md", ...props },
43
+ ref,
44
+ ) {
45
+ return (
46
+ <BaseMeter.Track
47
+ ref={ref}
48
+ className={(state) => cx(styles.track, styles[`track-${size}`], resolveClassName(className, state))}
49
+ {...props}
50
+ />
51
+ );
52
+ });
53
+
54
+ export const MeterIndicator = forwardRef<ComponentRef<typeof BaseMeter.Indicator>, MeterIndicatorProps>(function MeterIndicator(
55
+ { className, ...props },
56
+ ref,
57
+ ) {
58
+ return <BaseMeter.Indicator ref={ref} className={(state) => cx(styles.indicator, resolveClassName(className, state))} {...props} />;
59
+ });
60
+
61
+ export const MeterLabel = forwardRef<ComponentRef<typeof BaseMeter.Label>, MeterLabelProps>(function MeterLabel(
62
+ { className, ...props },
63
+ ref,
64
+ ) {
65
+ return <BaseMeter.Label ref={ref} className={(state) => cx(styles.label, resolveClassName(className, state))} {...props} />;
66
+ });
67
+
68
+ export const MeterValue = forwardRef<ComponentRef<typeof BaseMeter.Value>, MeterValueProps>(function MeterValue(
69
+ { className, ...props },
70
+ ref,
71
+ ) {
72
+ return <BaseMeter.Value ref={ref} className={(state) => cx(styles.value, resolveClassName(className, state))} {...props} />;
73
+ });
@@ -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