@nordmanai/nif 0.2.2 → 0.2.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.
Files changed (358) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +6 -0
  3. package/dist/index.js +31353 -11545
  4. package/dist/styles.css +1 -1
  5. package/dist/ui/components/nm-ai/nm-ai.d.ts +4 -0
  6. package/dist/ui/components/nm-alert/nm-alert.d.ts +13 -0
  7. package/dist/ui/components/nm-button/nm-button.d.ts +4 -2
  8. package/dist/ui/components/nm-checkbox/nm-checkbox.d.ts +1 -1
  9. package/dist/ui/components/nm-chip/nm-chip.d.ts +5 -2
  10. package/dist/ui/components/nm-collapsible/nm-collapsible.d.ts +6 -0
  11. package/dist/ui/components/nm-combobox/nm-combobox.d.ts +1 -1
  12. package/dist/ui/components/nm-command/nm-command.d.ts +8 -1
  13. package/dist/ui/components/nm-composer/nm-composer.d.ts +47 -4
  14. package/dist/ui/components/nm-context-menu/nm-context-menu.d.ts +1 -1
  15. package/dist/ui/components/nm-dialog/nm-dialog.d.ts +1 -1
  16. package/dist/ui/components/nm-dropdown/nm-dropdown.d.ts +1 -1
  17. package/dist/ui/components/nm-dropdown-menu/nm-dropdown-menu.d.ts +11 -2
  18. package/dist/ui/components/nm-field/nm-field.d.ts +2 -0
  19. package/dist/ui/components/nm-hover-card/nm-hover-card.d.ts +1 -1
  20. package/dist/ui/components/nm-icon/animated/activity.d.ts +72 -0
  21. package/dist/ui/components/nm-icon/animated/archive.d.ts +31 -0
  22. package/dist/ui/components/nm-icon/animated/arrow-down.d.ts +128 -0
  23. package/dist/ui/components/nm-icon/animated/arrow-left.d.ts +128 -0
  24. package/dist/ui/components/nm-icon/animated/arrow-right.d.ts +128 -0
  25. package/dist/ui/components/nm-icon/animated/arrow-up-down.d.ts +119 -0
  26. package/dist/ui/components/nm-icon/animated/arrow-up.d.ts +128 -0
  27. package/dist/ui/components/nm-icon/animated/audio-lines.d.ts +99 -0
  28. package/dist/ui/components/nm-icon/animated/bookmark-plus.d.ts +35 -0
  29. package/dist/ui/components/nm-icon/animated/bookmark.d.ts +52 -0
  30. package/dist/ui/components/nm-icon/animated/bot.d.ts +90 -0
  31. package/dist/ui/components/nm-icon/animated/calendar-days.d.ts +87 -0
  32. package/dist/ui/components/nm-icon/animated/calendar.d.ts +36 -0
  33. package/dist/ui/components/nm-icon/animated/camera.d.ts +50 -0
  34. package/dist/ui/components/nm-icon/animated/chart-column-increasing.d.ts +37 -0
  35. package/dist/ui/components/nm-icon/animated/chart-column.d.ts +86 -0
  36. package/dist/ui/components/nm-icon/animated/chart-line.d.ts +51 -0
  37. package/dist/ui/components/nm-icon/animated/check-check.d.ts +50 -0
  38. package/dist/ui/components/nm-icon/animated/check.d.ts +25 -0
  39. package/dist/ui/components/nm-icon/animated/chevron-down.d.ts +39 -0
  40. package/dist/ui/components/nm-icon/animated/chevron-left.d.ts +39 -0
  41. package/dist/ui/components/nm-icon/animated/chevron-right.d.ts +39 -0
  42. package/dist/ui/components/nm-icon/animated/chevron-up.d.ts +39 -0
  43. package/dist/ui/components/nm-icon/animated/circle-alert.d.ts +23 -0
  44. package/dist/ui/components/nm-icon/animated/circle-check-big.d.ts +26 -0
  45. package/dist/ui/components/nm-icon/animated/circle-check.d.ts +26 -0
  46. package/dist/ui/components/nm-icon/animated/circle-plus.d.ts +44 -0
  47. package/dist/ui/components/nm-icon/animated/circle-question-mark.d.ts +22 -0
  48. package/dist/ui/components/nm-icon/animated/circle-x.d.ts +44 -0
  49. package/dist/ui/components/nm-icon/animated/clock-3.d.ts +44 -0
  50. package/dist/ui/components/nm-icon/animated/clock-4.d.ts +44 -0
  51. package/dist/ui/components/nm-icon/animated/clock.d.ts +44 -0
  52. package/dist/ui/components/nm-icon/animated/cloud-sun.d.ts +9 -0
  53. package/dist/ui/components/nm-icon/animated/cloud-upload.d.ts +45 -0
  54. package/dist/ui/components/nm-icon/animated/copy.d.ts +135 -0
  55. package/dist/ui/components/nm-icon/animated/download.d.ts +45 -0
  56. package/dist/ui/components/nm-icon/animated/ellipsis.d.ts +141 -0
  57. package/dist/ui/components/nm-icon/animated/external-link.d.ts +49 -0
  58. package/dist/ui/components/nm-icon/animated/eye-off.d.ts +23 -0
  59. package/dist/ui/components/nm-icon/animated/eye.d.ts +39 -0
  60. package/dist/ui/components/nm-icon/animated/fast-forward.d.ts +34 -0
  61. package/dist/ui/components/nm-icon/animated/file-clock.d.ts +48 -0
  62. package/dist/ui/components/nm-icon/animated/file-text.d.ts +56 -0
  63. package/dist/ui/components/nm-icon/animated/file.d.ts +67 -0
  64. package/dist/ui/components/nm-icon/animated/folder.d.ts +92 -0
  65. package/dist/ui/components/nm-icon/animated/gauge.d.ts +47 -0
  66. package/dist/ui/components/nm-icon/animated/globe.d.ts +21 -0
  67. package/dist/ui/components/nm-icon/animated/graduation-cap.d.ts +41 -0
  68. package/dist/ui/components/nm-icon/animated/headphones.d.ts +50 -0
  69. package/dist/ui/components/nm-icon/animated/image.d.ts +40 -0
  70. package/dist/ui/components/nm-icon/animated/index.d.ts +2 -0
  71. package/dist/ui/components/nm-icon/animated/info.d.ts +37 -0
  72. package/dist/ui/components/nm-icon/animated/key-round.d.ts +37 -0
  73. package/dist/ui/components/nm-icon/animated/layers.d.ts +61 -0
  74. package/dist/ui/components/nm-icon/animated/layout-dashboard.d.ts +131 -0
  75. package/dist/ui/components/nm-icon/animated/library-big.d.ts +29 -0
  76. package/dist/ui/components/nm-icon/animated/lightbulb.d.ts +51 -0
  77. package/dist/ui/components/nm-icon/animated/link.d.ts +193 -0
  78. package/dist/ui/components/nm-icon/animated/list-filter.d.ts +13 -0
  79. package/dist/ui/components/nm-icon/animated/list.d.ts +111 -0
  80. package/dist/ui/components/nm-icon/animated/loader-circle.d.ts +24 -0
  81. package/dist/ui/components/nm-icon/animated/lock.d.ts +94 -0
  82. package/dist/ui/components/nm-icon/animated/log-out.d.ts +45 -0
  83. package/dist/ui/components/nm-icon/animated/mail.d.ts +100 -0
  84. package/dist/ui/components/nm-icon/animated/map-pin.d.ts +67 -0
  85. package/dist/ui/components/nm-icon/animated/menu.d.ts +57 -0
  86. package/dist/ui/components/nm-icon/animated/message-circle-dashed.d.ts +34 -0
  87. package/dist/ui/components/nm-icon/animated/message-circle.d.ts +23 -0
  88. package/dist/ui/components/nm-icon/animated/message-square-plus.d.ts +49 -0
  89. package/dist/ui/components/nm-icon/animated/message-square-text.d.ts +104 -0
  90. package/dist/ui/components/nm-icon/animated/message-square.d.ts +23 -0
  91. package/dist/ui/components/nm-icon/animated/mic-off.d.ts +23 -0
  92. package/dist/ui/components/nm-icon/animated/mic.d.ts +37 -0
  93. package/dist/ui/components/nm-icon/animated/minimize.d.ts +139 -0
  94. package/dist/ui/components/nm-icon/animated/minus.d.ts +54 -0
  95. package/dist/ui/components/nm-icon/animated/moon.d.ts +62 -0
  96. package/dist/ui/components/nm-icon/animated/music.d.ts +22 -0
  97. package/dist/ui/components/nm-icon/animated/notebook-pen.d.ts +41 -0
  98. package/dist/ui/components/nm-icon/animated/panel-left.d.ts +29 -0
  99. package/dist/ui/components/nm-icon/animated/panel-right.d.ts +29 -0
  100. package/dist/ui/components/nm-icon/animated/paperclip.d.ts +49 -0
  101. package/dist/ui/components/nm-icon/animated/pause.d.ts +59 -0
  102. package/dist/ui/components/nm-icon/animated/pencil.d.ts +25 -0
  103. package/dist/ui/components/nm-icon/animated/pin-off.d.ts +44 -0
  104. package/dist/ui/components/nm-icon/animated/pin.d.ts +85 -0
  105. package/dist/ui/components/nm-icon/animated/plane-takeoff.d.ts +25 -0
  106. package/dist/ui/components/nm-icon/animated/play.d.ts +43 -0
  107. package/dist/ui/components/nm-icon/animated/plug-zap.d.ts +49 -0
  108. package/dist/ui/components/nm-icon/animated/plus.d.ts +95 -0
  109. package/dist/ui/components/nm-icon/animated/radio.d.ts +24 -0
  110. package/dist/ui/components/nm-icon/animated/refresh-cw.d.ts +55 -0
  111. package/dist/ui/components/nm-icon/animated/rocket.d.ts +48 -0
  112. package/dist/ui/components/nm-icon/animated/rotate-ccw.d.ts +51 -0
  113. package/dist/ui/components/nm-icon/animated/rotate-cw.d.ts +71 -0
  114. package/dist/ui/components/nm-icon/animated/save.d.ts +21 -0
  115. package/dist/ui/components/nm-icon/animated/search.d.ts +42 -0
  116. package/dist/ui/components/nm-icon/animated/send.d.ts +39 -0
  117. package/dist/ui/components/nm-icon/animated/settings.d.ts +39 -0
  118. package/dist/ui/components/nm-icon/animated/share-2.d.ts +122 -0
  119. package/dist/ui/components/nm-icon/animated/share.d.ts +40 -0
  120. package/dist/ui/components/nm-icon/animated/shield-check.d.ts +36 -0
  121. package/dist/ui/components/nm-icon/animated/shopping-bag.d.ts +35 -0
  122. package/dist/ui/components/nm-icon/animated/sliders-horizontal.d.ts +239 -0
  123. package/dist/ui/components/nm-icon/animated/sparkles.d.ts +205 -0
  124. package/dist/ui/components/nm-icon/animated/star.d.ts +48 -0
  125. package/dist/ui/components/nm-icon/animated/sun.d.ts +9 -0
  126. package/dist/ui/components/nm-icon/animated/terminal.d.ts +22 -0
  127. package/dist/ui/components/nm-icon/animated/trash-2.d.ts +61 -0
  128. package/dist/ui/components/nm-icon/animated/trash.d.ts +37 -0
  129. package/dist/ui/components/nm-icon/animated/trending-up.d.ts +40 -0
  130. package/dist/ui/components/nm-icon/animated/triangle-alert.d.ts +37 -0
  131. package/dist/ui/components/nm-icon/animated/trophy.d.ts +23 -0
  132. package/dist/ui/components/nm-icon/animated/type.d.ts +57 -0
  133. package/dist/ui/components/nm-icon/animated/upload.d.ts +49 -0
  134. package/dist/ui/components/nm-icon/animated/user-round.d.ts +33 -0
  135. package/dist/ui/components/nm-icon/animated/user.d.ts +33 -0
  136. package/dist/ui/components/nm-icon/animated/users-round.d.ts +93 -0
  137. package/dist/ui/components/nm-icon/animated/users.d.ts +93 -0
  138. package/dist/ui/components/nm-icon/animated/volume-2.d.ts +10 -0
  139. package/dist/ui/components/nm-icon/animated/wand-sparkles.d.ts +71 -0
  140. package/dist/ui/components/nm-icon/animated/x.d.ts +95 -0
  141. package/dist/ui/components/nm-icon/animated/zap.d.ts +65 -0
  142. package/dist/ui/components/nm-icon/animated/zoom-in.d.ts +35 -0
  143. package/dist/ui/components/nm-icon/animated/zoom-out.d.ts +53 -0
  144. package/dist/ui/components/nm-icon/nm-icon.d.ts +6 -3
  145. package/dist/ui/components/nm-icon/nm-lucide-extended.d.ts +3 -0
  146. package/dist/ui/components/nm-icon/nm-lucide.d.ts +7 -3
  147. package/dist/ui/components/nm-menubar/nm-menubar.d.ts +2 -2
  148. package/dist/ui/components/nm-panel/nm-panel.d.ts +5 -1
  149. package/dist/ui/components/nm-popover/nm-popover.d.ts +2 -2
  150. package/dist/ui/components/nm-progress/nm-progress.d.ts +1 -1
  151. package/dist/ui/components/nm-radio-group/nm-radio-group.d.ts +2 -2
  152. package/dist/ui/components/nm-select/nm-select.d.ts +1 -1
  153. package/dist/ui/components/nm-skeleton/nm-skeleton.d.ts +3 -0
  154. package/dist/ui/components/nm-slider/nm-slider.d.ts +1 -1
  155. package/dist/ui/components/nm-small-icon-button/nm-small-icon-button.d.ts +3 -3
  156. package/dist/ui/components/nm-spinner/nm-spinner.d.ts +3 -0
  157. package/dist/ui/components/nm-switch/nm-switch.d.ts +1 -1
  158. package/dist/ui/components/nm-tabs/nm-tabs.d.ts +6 -0
  159. package/dist/ui/components/nm-textarea/nm-textarea.d.ts +12 -2
  160. package/dist/ui/components/nm-toggle/nm-toggle.d.ts +1 -1
  161. package/dist/ui/components/nm-tooltip/nm-tooltip.d.ts +1 -1
  162. package/dist/ui/components/nm-voice-orb/nm-voice-orb.d.ts +18 -0
  163. package/dist/ui/components/nm-voice-orb/use-nm-voice-orb-theme.d.ts +2 -0
  164. package/dist/ui/config/nm-app-panel.d.ts +2 -0
  165. package/dist/ui/config/nm-button.d.ts +6 -0
  166. package/dist/ui/config/nm-composer.d.ts +11 -0
  167. package/dist/ui/config/nm-icons-extended.d.ts +1 -0
  168. package/dist/ui/config/nm-icons.d.ts +4 -1
  169. package/dist/ui/config/nm-voice-orb.d.ts +22 -0
  170. package/dist/ui/lib/nm-motion.d.ts +29 -0
  171. package/package.json +12 -8
  172. package/registry/index.json +5277 -73
  173. package/registry/ui/components/nm-ai/nm-ai.tsx +7 -4
  174. package/registry/ui/components/nm-alert/nm-alert.css +85 -0
  175. package/registry/ui/components/nm-alert/nm-alert.tsx +35 -0
  176. package/registry/ui/components/nm-button/nm-button.css +24 -0
  177. package/registry/ui/components/nm-button/nm-button.tsx +25 -11
  178. package/registry/ui/components/nm-checkbox/nm-checkbox.tsx +1 -1
  179. package/registry/ui/components/nm-chip/nm-chip.css +25 -0
  180. package/registry/ui/components/nm-chip/nm-chip.tsx +9 -3
  181. package/registry/ui/components/nm-collapsible/nm-collapsible.css +3 -0
  182. package/registry/ui/components/nm-collapsible/nm-collapsible.tsx +16 -0
  183. package/registry/ui/components/nm-combobox/nm-combobox.tsx +1 -1
  184. package/registry/ui/components/nm-command/nm-command.css +48 -0
  185. package/registry/ui/components/nm-command/nm-command.tsx +19 -5
  186. package/registry/ui/components/nm-composer/nm-composer.css +87 -3
  187. package/registry/ui/components/nm-composer/nm-composer.tsx +321 -33
  188. package/registry/ui/components/nm-context-menu/nm-context-menu.tsx +1 -1
  189. package/registry/ui/components/nm-dialog/nm-dialog.css +5 -6
  190. package/registry/ui/components/nm-dialog/nm-dialog.tsx +1 -1
  191. package/registry/ui/components/nm-dropdown/nm-dropdown.css +5 -0
  192. package/registry/ui/components/nm-dropdown/nm-dropdown.tsx +28 -249
  193. package/registry/ui/components/nm-dropdown-menu/nm-dropdown-menu.css +8 -0
  194. package/registry/ui/components/nm-dropdown-menu/nm-dropdown-menu.tsx +85 -6
  195. package/registry/ui/components/nm-field/nm-field.tsx +6 -1
  196. package/registry/ui/components/nm-hover-card/nm-hover-card.tsx +1 -1
  197. package/registry/ui/components/nm-icon/animated/activity.tsx +109 -0
  198. package/registry/ui/components/nm-icon/animated/archive.tsx +85 -0
  199. package/registry/ui/components/nm-icon/animated/arrow-down.tsx +149 -0
  200. package/registry/ui/components/nm-icon/animated/arrow-left.tsx +149 -0
  201. package/registry/ui/components/nm-icon/animated/arrow-right.tsx +149 -0
  202. package/registry/ui/components/nm-icon/animated/arrow-up-down.tsx +168 -0
  203. package/registry/ui/components/nm-icon/animated/arrow-up.tsx +149 -0
  204. package/registry/ui/components/nm-icon/animated/audio-lines.tsx +190 -0
  205. package/registry/ui/components/nm-icon/animated/bookmark-plus.tsx +65 -0
  206. package/registry/ui/components/nm-icon/animated/bookmark.tsx +77 -0
  207. package/registry/ui/components/nm-icon/animated/bot.tsx +167 -0
  208. package/registry/ui/components/nm-icon/animated/calendar-days.tsx +128 -0
  209. package/registry/ui/components/nm-icon/animated/calendar.tsx +83 -0
  210. package/registry/ui/components/nm-icon/animated/camera.tsx +85 -0
  211. package/registry/ui/components/nm-icon/animated/chart-column-increasing.tsx +150 -0
  212. package/registry/ui/components/nm-icon/animated/chart-column.tsx +179 -0
  213. package/registry/ui/components/nm-icon/animated/chart-line.tsx +88 -0
  214. package/registry/ui/components/nm-icon/animated/check-check.tsx +105 -0
  215. package/registry/ui/components/nm-icon/animated/check.tsx +70 -0
  216. package/registry/ui/components/nm-icon/animated/chevron-down.tsx +73 -0
  217. package/registry/ui/components/nm-icon/animated/chevron-left.tsx +73 -0
  218. package/registry/ui/components/nm-icon/animated/chevron-right.tsx +73 -0
  219. package/registry/ui/components/nm-icon/animated/chevron-up.tsx +73 -0
  220. package/registry/ui/components/nm-icon/animated/circle-alert.tsx +84 -0
  221. package/registry/ui/components/nm-icon/animated/circle-check-big.tsx +75 -0
  222. package/registry/ui/components/nm-icon/animated/circle-check.tsx +77 -0
  223. package/registry/ui/components/nm-icon/animated/circle-plus.tsx +93 -0
  224. package/registry/ui/components/nm-icon/animated/circle-question-mark.tsx +62 -0
  225. package/registry/ui/components/nm-icon/animated/circle-x.tsx +93 -0
  226. package/registry/ui/components/nm-icon/animated/clock-3.tsx +95 -0
  227. package/registry/ui/components/nm-icon/animated/clock-4.tsx +95 -0
  228. package/registry/ui/components/nm-icon/animated/clock.tsx +95 -0
  229. package/registry/ui/components/nm-icon/animated/cloud-sun.tsx +130 -0
  230. package/registry/ui/components/nm-icon/animated/cloud-upload.tsx +93 -0
  231. package/registry/ui/components/nm-icon/animated/copy.tsx +153 -0
  232. package/registry/ui/components/nm-icon/animated/download.tsx +93 -0
  233. package/registry/ui/components/nm-icon/animated/ellipsis.tsx +189 -0
  234. package/registry/ui/components/nm-icon/animated/external-link.tsx +97 -0
  235. package/registry/ui/components/nm-icon/animated/eye-off.tsx +63 -0
  236. package/registry/ui/components/nm-icon/animated/eye.tsx +82 -0
  237. package/registry/ui/components/nm-icon/animated/fast-forward.tsx +72 -0
  238. package/registry/ui/components/nm-icon/animated/file-clock.tsx +105 -0
  239. package/registry/ui/components/nm-icon/animated/file-text.tsx +102 -0
  240. package/registry/ui/components/nm-icon/animated/file.tsx +124 -0
  241. package/registry/ui/components/nm-icon/animated/folder.tsx +132 -0
  242. package/registry/ui/components/nm-icon/animated/gauge.tsx +84 -0
  243. package/registry/ui/components/nm-icon/animated/globe.tsx +61 -0
  244. package/registry/ui/components/nm-icon/animated/graduation-cap.tsx +92 -0
  245. package/registry/ui/components/nm-icon/animated/headphones.tsx +85 -0
  246. package/registry/ui/components/nm-icon/animated/image.tsx +88 -0
  247. package/registry/ui/components/nm-icon/animated/index.ts +251 -0
  248. package/registry/ui/components/nm-icon/animated/info.tsx +75 -0
  249. package/registry/ui/components/nm-icon/animated/key-round.tsx +67 -0
  250. package/registry/ui/components/nm-icon/animated/layers.tsx +116 -0
  251. package/registry/ui/components/nm-icon/animated/layout-dashboard.tsx +176 -0
  252. package/registry/ui/components/nm-icon/animated/library-big.tsx +89 -0
  253. package/registry/ui/components/nm-icon/animated/lightbulb.tsx +106 -0
  254. package/registry/ui/components/nm-icon/animated/link.tsx +210 -0
  255. package/registry/ui/components/nm-icon/animated/list-filter.tsx +78 -0
  256. package/registry/ui/components/nm-icon/animated/list.tsx +184 -0
  257. package/registry/ui/components/nm-icon/animated/loader-circle.tsx +68 -0
  258. package/registry/ui/components/nm-icon/animated/lock.tsx +152 -0
  259. package/registry/ui/components/nm-icon/animated/log-out.tsx +93 -0
  260. package/registry/ui/components/nm-icon/animated/mail.tsx +131 -0
  261. package/registry/ui/components/nm-icon/animated/map-pin.tsx +111 -0
  262. package/registry/ui/components/nm-icon/animated/menu.tsx +121 -0
  263. package/registry/ui/components/nm-icon/animated/message-circle-dashed.tsx +140 -0
  264. package/registry/ui/components/nm-icon/animated/message-circle.tsx +66 -0
  265. package/registry/ui/components/nm-icon/animated/message-square-plus.tsx +106 -0
  266. package/registry/ui/components/nm-icon/animated/message-square-text.tsx +153 -0
  267. package/registry/ui/components/nm-icon/animated/message-square.tsx +66 -0
  268. package/registry/ui/components/nm-icon/animated/mic-off.tsx +65 -0
  269. package/registry/ui/components/nm-icon/animated/mic.tsx +77 -0
  270. package/registry/ui/components/nm-icon/animated/minimize.tsx +164 -0
  271. package/registry/ui/components/nm-icon/animated/minus.tsx +84 -0
  272. package/registry/ui/components/nm-icon/animated/moon.tsx +98 -0
  273. package/registry/ui/components/nm-icon/animated/music.tsx +62 -0
  274. package/registry/ui/components/nm-icon/animated/notebook-pen.tsx +114 -0
  275. package/registry/ui/components/nm-icon/animated/panel-left.tsx +77 -0
  276. package/registry/ui/components/nm-icon/animated/panel-right.tsx +77 -0
  277. package/registry/ui/components/nm-icon/animated/paperclip.tsx +83 -0
  278. package/registry/ui/components/nm-icon/animated/pause.tsx +116 -0
  279. package/registry/ui/components/nm-icon/animated/pencil.tsx +70 -0
  280. package/registry/ui/components/nm-icon/animated/pin-off.tsx +102 -0
  281. package/registry/ui/components/nm-icon/animated/pin.tsx +124 -0
  282. package/registry/ui/components/nm-icon/animated/plane-takeoff.tsx +62 -0
  283. package/registry/ui/components/nm-icon/animated/play.tsx +74 -0
  284. package/registry/ui/components/nm-icon/animated/plug-zap.tsx +117 -0
  285. package/registry/ui/components/nm-icon/animated/plus.tsx +122 -0
  286. package/registry/ui/components/nm-icon/animated/radio.tsx +137 -0
  287. package/registry/ui/components/nm-icon/animated/refresh-cw.tsx +103 -0
  288. package/registry/ui/components/nm-icon/animated/rocket.tsx +93 -0
  289. package/registry/ui/components/nm-icon/animated/rotate-ccw.tsx +87 -0
  290. package/registry/ui/components/nm-icon/animated/rotate-cw.tsx +98 -0
  291. package/registry/ui/components/nm-icon/animated/save.tsx +58 -0
  292. package/registry/ui/components/nm-icon/animated/search.tsx +90 -0
  293. package/registry/ui/components/nm-icon/animated/send.tsx +87 -0
  294. package/registry/ui/components/nm-icon/animated/settings.tsx +92 -0
  295. package/registry/ui/components/nm-icon/animated/share-2.tsx +148 -0
  296. package/registry/ui/components/nm-icon/animated/share.tsx +76 -0
  297. package/registry/ui/components/nm-icon/animated/shield-check.tsx +70 -0
  298. package/registry/ui/components/nm-icon/animated/shopping-bag.tsx +71 -0
  299. package/registry/ui/components/nm-icon/animated/sliders-horizontal.tsx +243 -0
  300. package/registry/ui/components/nm-icon/animated/sparkles.tsx +227 -0
  301. package/registry/ui/components/nm-icon/animated/star.tsx +86 -0
  302. package/registry/ui/components/nm-icon/animated/sun.tsx +148 -0
  303. package/registry/ui/components/nm-icon/animated/terminal.tsx +71 -0
  304. package/registry/ui/components/nm-icon/animated/trash-2.tsx +136 -0
  305. package/registry/ui/components/nm-icon/animated/trash.tsx +94 -0
  306. package/registry/ui/components/nm-icon/animated/trending-up.tsx +75 -0
  307. package/registry/ui/components/nm-icon/animated/triangle-alert.tsx +75 -0
  308. package/registry/ui/components/nm-icon/animated/trophy.tsx +62 -0
  309. package/registry/ui/components/nm-icon/animated/type.tsx +106 -0
  310. package/registry/ui/components/nm-icon/animated/upload.tsx +94 -0
  311. package/registry/ui/components/nm-icon/animated/user-round.tsx +84 -0
  312. package/registry/ui/components/nm-icon/animated/user.tsx +84 -0
  313. package/registry/ui/components/nm-icon/animated/users-round.tsx +156 -0
  314. package/registry/ui/components/nm-icon/animated/users.tsx +156 -0
  315. package/registry/ui/components/nm-icon/animated/volume-2.tsx +119 -0
  316. package/registry/ui/components/nm-icon/animated/wand-sparkles.tsx +111 -0
  317. package/registry/ui/components/nm-icon/animated/x.tsx +122 -0
  318. package/registry/ui/components/nm-icon/animated/zap.tsx +86 -0
  319. package/registry/ui/components/nm-icon/animated/zoom-in.tsx +66 -0
  320. package/registry/ui/components/nm-icon/animated/zoom-out.tsx +85 -0
  321. package/registry/ui/components/nm-icon/nm-icon.css +0 -1
  322. package/registry/ui/components/nm-icon/nm-icon.tsx +20 -5
  323. package/registry/ui/components/nm-icon/nm-lucide-extended.tsx +284 -0
  324. package/registry/ui/components/nm-icon/nm-lucide.tsx +55 -5
  325. package/registry/ui/components/nm-input/nm-input.css +1 -0
  326. package/registry/ui/components/nm-menubar/nm-menubar.tsx +2 -2
  327. package/registry/ui/components/nm-panel/nm-panel.css +7 -0
  328. package/registry/ui/components/nm-panel/nm-panel.tsx +19 -2
  329. package/registry/ui/components/nm-popover/nm-popover.tsx +4 -3
  330. package/registry/ui/components/nm-progress/nm-progress.tsx +1 -1
  331. package/registry/ui/components/nm-radio-group/nm-radio-group.tsx +2 -2
  332. package/registry/ui/components/nm-select/nm-select.tsx +1 -1
  333. package/registry/ui/components/nm-skeleton/nm-skeleton.css +17 -0
  334. package/registry/ui/components/nm-skeleton/nm-skeleton.tsx +7 -0
  335. package/registry/ui/components/nm-slider/nm-slider.tsx +1 -1
  336. package/registry/ui/components/nm-small-icon-button/nm-small-icon-button.tsx +3 -3
  337. package/registry/ui/components/nm-spinner/nm-spinner.css +22 -0
  338. package/registry/ui/components/nm-spinner/nm-spinner.tsx +7 -0
  339. package/registry/ui/components/nm-switch/nm-switch.css +20 -3
  340. package/registry/ui/components/nm-switch/nm-switch.tsx +1 -1
  341. package/registry/ui/components/nm-tabs/nm-tabs.css +4 -0
  342. package/registry/ui/components/nm-tabs/nm-tabs.tsx +19 -0
  343. package/registry/ui/components/nm-textarea/nm-textarea.css +200 -0
  344. package/registry/ui/components/nm-textarea/nm-textarea.tsx +417 -3
  345. package/registry/ui/components/nm-toggle/nm-toggle.tsx +1 -1
  346. package/registry/ui/components/nm-toolbar/nm-toolbar.tsx +4 -1
  347. package/registry/ui/components/nm-tooltip/nm-tooltip.tsx +1 -1
  348. package/registry/ui/components/nm-voice-orb/nm-voice-orb.css +17 -0
  349. package/registry/ui/components/nm-voice-orb/nm-voice-orb.tsx +112 -0
  350. package/registry/ui/components/nm-voice-orb/use-nm-voice-orb-theme.ts +34 -0
  351. package/registry/ui/components/nordman-icon-button/nordman-icon-button.tsx +7 -3
  352. package/registry/ui/config/nm-button.ts +6 -0
  353. package/registry/ui/config/nm-composer.ts +12 -0
  354. package/registry/ui/config/nm-icons-extended.ts +141 -0
  355. package/registry/ui/config/nm-icons.ts +6 -2
  356. package/registry/ui/config/nm-voice-orb.ts +50 -0
  357. package/registry/ui/lib/nm-motion.ts +30 -0
  358. package/registry/ui/styles/tokens.css +7 -0
@@ -1,6 +1,420 @@
1
- import type { ComponentProps } from 'react';
1
+ import {
2
+ forwardRef,
3
+ useCallback,
4
+ useEffect,
5
+ useId,
6
+ useRef,
7
+ useState,
8
+ type ChangeEvent,
9
+ type ComponentProps,
10
+ type ReactNode,
11
+ } from 'react';
12
+ import { createPortal } from 'react-dom';
2
13
  import { nmClass } from '../../lib/nm-class.ts';
14
+ import { NmIcon } from '../nm-icon/nm-icon.tsx';
15
+ import { NmProse } from '../nm-prose/nm-prose.tsx';
16
+ import { NmSegmented } from '../nm-segmented/nm-segmented.tsx';
17
+ import { createLogger } from '../../kit-logger.ts';
3
18
 
4
- export function NmTextarea({ className, ...props }: ComponentProps<'textarea'>) {
5
- return <textarea data-slot="textarea" className={nmClass('nm-textarea', className)} {...props} />;
19
+ const logger = createLogger('interface-nm-textarea');
20
+
21
+ export type NmTextareaVariant = 'default' | 'panel';
22
+
23
+ export type NmTextareaProps = ComponentProps<'textarea'> & {
24
+ variant?: NmTextareaVariant;
25
+ openInPanel?: boolean;
26
+ panelTitle?: string;
27
+ panelButtonLabel?: string;
28
+ panelDefaultTab?: 'code' | 'preview';
29
+ renderMarkdown?: (markdown: string) => ReactNode;
30
+ };
31
+
32
+ function renderInline(text: string): ReactNode[] {
33
+ const parts: ReactNode[] = [];
34
+ const regex = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`|\[[^\]]+\]\([^)]+\))/g;
35
+ let lastIndex = 0;
36
+ let match: RegExpExecArray | null;
37
+
38
+ while ((match = regex.exec(text)) !== null) {
39
+ if (match.index > lastIndex) {
40
+ parts.push(text.slice(lastIndex, match.index));
41
+ }
42
+ const token = match[0];
43
+ if (token.startsWith('**') && token.endsWith('**')) {
44
+ parts.push(<strong key={match.index}>{token.slice(2, -2)}</strong>);
45
+ } else if (token.startsWith('*') && token.endsWith('*')) {
46
+ parts.push(<em key={match.index}>{token.slice(1, -1)}</em>);
47
+ } else if (token.startsWith('`') && token.endsWith('`')) {
48
+ parts.push(<code key={match.index}>{token.slice(1, -1)}</code>);
49
+ } else if (token.startsWith('[') && token.includes('](')) {
50
+ const closeBracket = token.indexOf('](');
51
+ const label = token.slice(1, closeBracket);
52
+ const url = token.slice(closeBracket + 2, -1);
53
+ parts.push(
54
+ <a key={match.index} href={url} target="_blank" rel="noreferrer">
55
+ {label}
56
+ </a>,
57
+ );
58
+ }
59
+ lastIndex = regex.lastIndex;
60
+ }
61
+ if (lastIndex < text.length) {
62
+ parts.push(text.slice(lastIndex));
63
+ }
64
+ return parts.length > 0 ? parts : [text];
65
+ }
66
+
67
+ function parseMarkdown(md: string): ReactNode {
68
+ if (!md.trim()) {
69
+ return <p className="nm-textarea-panel__empty">Nothing to preview</p>;
70
+ }
71
+
72
+ const lines = md.split('\n');
73
+ const nodes: ReactNode[] = [];
74
+ let i = 0;
75
+
76
+ while (i < lines.length) {
77
+ const line = lines[i];
78
+
79
+ if (line.startsWith('```')) {
80
+ const codeLines: string[] = [];
81
+ i++;
82
+ while (i < lines.length && !lines[i].startsWith('```')) {
83
+ codeLines.push(lines[i]);
84
+ i++;
85
+ }
86
+ i++;
87
+ nodes.push(
88
+ <pre key={`code-${i}`}>
89
+ <code>{codeLines.join('\n')}</code>
90
+ </pre>,
91
+ );
92
+ continue;
93
+ }
94
+
95
+ if (line.startsWith('#### ')) {
96
+ nodes.push(<h4 key={`h4-${i}`}>{renderInline(line.slice(5))}</h4>);
97
+ i++;
98
+ continue;
99
+ }
100
+ if (line.startsWith('### ')) {
101
+ nodes.push(<h3 key={`h3-${i}`}>{renderInline(line.slice(4))}</h3>);
102
+ i++;
103
+ continue;
104
+ }
105
+ if (line.startsWith('## ')) {
106
+ nodes.push(<h2 key={`h2-${i}`}>{renderInline(line.slice(3))}</h2>);
107
+ i++;
108
+ continue;
109
+ }
110
+ if (line.startsWith('# ')) {
111
+ nodes.push(<h1 key={`h1-${i}`}>{renderInline(line.slice(2))}</h1>);
112
+ i++;
113
+ continue;
114
+ }
115
+
116
+ if (/^(\*\*\*|---|___)$/.test(line.trim())) {
117
+ nodes.push(<hr key={`hr-${i}`} />);
118
+ i++;
119
+ continue;
120
+ }
121
+
122
+ if (line.startsWith('> ')) {
123
+ const quoteLines: string[] = [line.slice(2)];
124
+ i++;
125
+ while (i < lines.length && lines[i].startsWith('> ')) {
126
+ quoteLines.push(lines[i].slice(2));
127
+ i++;
128
+ }
129
+ nodes.push(
130
+ <blockquote key={`quote-${i}`}>
131
+ {quoteLines.map((ql, idx) => (
132
+ <p key={idx}>{renderInline(ql)}</p>
133
+ ))}
134
+ </blockquote>,
135
+ );
136
+ continue;
137
+ }
138
+
139
+ if (/^[-*]\s+/.test(line)) {
140
+ const items: string[] = [line.replace(/^[-*]\s+/, '')];
141
+ i++;
142
+ while (i < lines.length && /^[-*]\s+/.test(lines[i])) {
143
+ items.push(lines[i].replace(/^[-*]\s+/, ''));
144
+ i++;
145
+ }
146
+ nodes.push(
147
+ <ul key={`ul-${i}`}>
148
+ {items.map((item, idx) => (
149
+ <li key={idx}>{renderInline(item)}</li>
150
+ ))}
151
+ </ul>,
152
+ );
153
+ continue;
154
+ }
155
+
156
+ if (/^\d+\.\s+/.test(line)) {
157
+ const items: string[] = [line.replace(/^\d+\.\s+/, '')];
158
+ i++;
159
+ while (i < lines.length && /^\d+\.\s+/.test(lines[i])) {
160
+ items.push(lines[i].replace(/^\d+\.\s+/, ''));
161
+ i++;
162
+ }
163
+ nodes.push(
164
+ <ol key={`ol-${i}`}>
165
+ {items.map((item, idx) => (
166
+ <li key={idx}>{renderInline(item)}</li>
167
+ ))}
168
+ </ol>,
169
+ );
170
+ continue;
171
+ }
172
+
173
+ if (!line.trim()) {
174
+ i++;
175
+ continue;
176
+ }
177
+
178
+ const paraLines: string[] = [line];
179
+ i++;
180
+ while (
181
+ i < lines.length &&
182
+ lines[i].trim() &&
183
+ !lines[i].startsWith('#') &&
184
+ !lines[i].startsWith('```') &&
185
+ !lines[i].startsWith('> ') &&
186
+ !/^[-*]\s+/.test(lines[i]) &&
187
+ !/^\d+\.\s+/.test(lines[i]) &&
188
+ !/^(\*\*\*|---|___)$/.test(lines[i].trim())
189
+ ) {
190
+ paraLines.push(lines[i]);
191
+ i++;
192
+ }
193
+ nodes.push(<p key={`p-${i}`}>{renderInline(paraLines.join(' '))}</p>);
194
+ }
195
+
196
+ return nodes;
197
+ }
198
+
199
+ const PANEL_TABS = [
200
+ { id: 'code', label: 'Code', icon: 'code' as const },
201
+ { id: 'preview', label: 'Preview', icon: 'presentation' as const },
202
+ ] as const;
203
+
204
+ export const NmTextarea = forwardRef<HTMLTextAreaElement, NmTextareaProps>(
205
+ function NmTextarea(
206
+ {
207
+ className,
208
+ variant = 'default',
209
+ openInPanel = false,
210
+ panelTitle,
211
+ panelButtonLabel = 'Open in panel',
212
+ panelDefaultTab = 'code',
213
+ renderMarkdown,
214
+ value,
215
+ defaultValue,
216
+ onChange,
217
+ ...props
218
+ },
219
+ forwardedRef,
220
+ ) {
221
+ const isPanelEnabled = variant === 'panel' || openInPanel;
222
+ const [panelOpen, setPanelOpen] = useState(false);
223
+ const [tab, setTab] = useState<'code' | 'preview'>(panelDefaultTab);
224
+ const [copied, setCopied] = useState(false);
225
+ const triggerRef = useRef<HTMLButtonElement>(null);
226
+ const internalTextareaRef = useRef<HTMLTextAreaElement | null>(null);
227
+ const [uncontrolledValue, setUncontrolledValue] = useState(() => String(defaultValue ?? ''));
228
+
229
+ const isControlled = value !== undefined;
230
+ const currentValue = isControlled ? String(value) : uncontrolledValue;
231
+
232
+ const handleTextareaChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
233
+ if (!isControlled) {
234
+ setUncontrolledValue(event.target.value);
235
+ }
236
+ onChange?.(event);
237
+ };
238
+
239
+ const handlePanelTextChange = (nextText: string) => {
240
+ if (!isControlled) {
241
+ setUncontrolledValue(nextText);
242
+ }
243
+ if (internalTextareaRef.current) {
244
+ internalTextareaRef.current.value = nextText;
245
+ }
246
+ if (onChange) {
247
+ const syntheticEvent = {
248
+ target: { value: nextText, name: props.name },
249
+ currentTarget: { value: nextText, name: props.name },
250
+ } as ChangeEvent<HTMLTextAreaElement>;
251
+ onChange(syntheticEvent);
252
+ }
253
+ };
254
+
255
+ const handleCopy = () => {
256
+ if (typeof navigator !== 'undefined' && navigator.clipboard) {
257
+ void navigator.clipboard.writeText(currentValue);
258
+ setCopied(true);
259
+ setTimeout(() => setCopied(false), 1500);
260
+ }
261
+ };
262
+
263
+ const handleClose = useCallback(() => {
264
+ setPanelOpen(false);
265
+ triggerRef.current?.focus();
266
+ }, []);
267
+
268
+ useEffect(() => {
269
+ if (!panelOpen) return;
270
+ const onKeyDown = (e: KeyboardEvent) => {
271
+ if (e.key === 'Escape') {
272
+ e.preventDefault();
273
+ handleClose();
274
+ }
275
+ };
276
+ window.addEventListener('keydown', onKeyDown);
277
+ return () => window.removeEventListener('keydown', onKeyDown);
278
+ }, [panelOpen, handleClose]);
279
+
280
+ const titleId = useId();
281
+ const lineCount = currentValue ? currentValue.split('\n').length : 0;
282
+ const charCount = currentValue.length;
283
+
284
+ const textareaElement = (
285
+ <textarea
286
+ className={nmClass('nm-textarea', className)}
287
+ ref={(node) => {
288
+ internalTextareaRef.current = node;
289
+ if (typeof forwardedRef === 'function') {
290
+ forwardedRef(node);
291
+ } else if (forwardedRef) {
292
+ forwardedRef.current = node;
293
+ }
294
+ }}
295
+ data-slot="textarea"
296
+ value={value}
297
+ defaultValue={defaultValue}
298
+ onChange={handleTextareaChange}
299
+ {...props}
300
+ />
301
+ );
302
+
303
+ if (!isPanelEnabled) {
304
+ return textareaElement;
305
+ }
306
+
307
+ const panelContent = panelOpen && typeof document !== 'undefined' ? (
308
+ createPortal(
309
+ <>
310
+ <div
311
+ className="nm-textarea-panel__backdrop"
312
+ onClick={handleClose}
313
+ aria-hidden="true"
314
+ />
315
+ <aside
316
+ role="dialog"
317
+ aria-modal="true"
318
+ aria-labelledby={panelTitle ? titleId : undefined}
319
+ aria-label={panelTitle ? undefined : 'Markdown Editor'}
320
+ className="nm-textarea-panel"
321
+ >
322
+ <header className="nm-textarea-panel__header">
323
+ <div className="nm-textarea-panel__title-group">
324
+ {panelTitle ? (
325
+ <h3 id={titleId} className="nm-textarea-panel__title">
326
+ {panelTitle}
327
+ </h3>
328
+ ) : null}
329
+ <NmSegmented
330
+ label="Editor view"
331
+ tone="pills"
332
+ items={PANEL_TABS}
333
+ active={tab}
334
+ onSelect={(next) => setTab(next as 'code' | 'preview')}
335
+ />
336
+ </div>
337
+ <div className="nm-textarea-panel__actions">
338
+ <button
339
+ type="button"
340
+ className="nm-textarea-panel__action-btn"
341
+ aria-label={copied ? 'Copied' : 'Copy markdown'}
342
+ title={copied ? 'Copied' : 'Copy markdown'}
343
+ onClick={handleCopy}
344
+ >
345
+ <NmIcon id={copied ? 'check' : 'copy'} />
346
+ </button>
347
+ <button
348
+ type="button"
349
+ className="nm-textarea-panel__action-btn"
350
+ aria-label="Close panel"
351
+ title="Close panel"
352
+ onClick={handleClose}
353
+ >
354
+ <NmIcon id="x" />
355
+ </button>
356
+ </div>
357
+ </header>
358
+
359
+ <div className="nm-textarea-panel__body">
360
+ {tab === 'code' ? (
361
+ <textarea
362
+ className="nm-textarea-panel__editor"
363
+ value={currentValue}
364
+ placeholder={props.placeholder ?? 'Enter markdown content...'}
365
+ aria-label="Markdown source"
366
+ autoFocus
367
+ onChange={(e) => handlePanelTextChange(e.target.value)}
368
+ />
369
+ ) : (
370
+ <div className="nm-textarea-panel__preview">
371
+ <NmProse>
372
+ {renderMarkdown ? renderMarkdown(currentValue) : parseMarkdown(currentValue)}
373
+ </NmProse>
374
+ </div>
375
+ )}
376
+ </div>
377
+
378
+ <footer className="nm-textarea-panel__footer">
379
+ <span>
380
+ {lineCount} {lineCount === 1 ? 'line' : 'lines'} · {charCount} characters
381
+ </span>
382
+ <button
383
+ type="button"
384
+ className="nm-textarea-panel__done"
385
+ onClick={handleClose}
386
+ >
387
+ Done
388
+ </button>
389
+ </footer>
390
+ </aside>
391
+ </>,
392
+ document.body,
393
+ )
394
+ ) : null;
395
+
396
+ logger.debug('render', variant, { panelOpen });
397
+
398
+ return (
399
+ <div className="nm-textarea-wrap">
400
+ {textareaElement}
401
+ <button
402
+ ref={triggerRef}
403
+ type="button"
404
+ className="nm-textarea__panel-trigger"
405
+ aria-label={panelButtonLabel}
406
+ aria-expanded={panelOpen}
407
+ title={panelButtonLabel}
408
+ onClick={() => setPanelOpen(true)}
409
+ >
410
+ <NmIcon id="panel" />
411
+ </button>
412
+ {panelContent}
413
+ </div>
414
+ );
415
+ },
416
+ );
417
+
418
+ export function NmTextareaPanel(props: NmTextareaProps) {
419
+ return <NmTextarea variant="panel" {...props} />;
6
420
  }
@@ -1,4 +1,4 @@
1
- import { Toggle as TogglePrimitive } from '@base-ui/react/toggle';
1
+ import { Toggle as TogglePrimitive } from '../../lib/nm-motion.ts';
2
2
  import { nmClass } from '../../lib/nm-class.ts';
3
3
 
4
4
  export const NM_TOGGLE_VARIANTS = ['default', 'outline'] as const;
@@ -4,6 +4,7 @@ import { nmClass } from '../../lib/nm-class.ts';
4
4
  import type { NmIconId } from '../../config/nm-icons.ts';
5
5
  import { NM_BUTTON_TONES, nmButtonClassName, type NmButtonTone } from '../../config/nm-button.ts';
6
6
  import { nmIconButtonClassName, type NmIconButtonTone } from '../../config/nm-icon-button.ts';
7
+ import { AnimateIcon } from '../../lib/nm-motion.ts';
7
8
  import { NmIcon } from '../nm-icon/nm-icon.tsx';
8
9
 
9
10
  /** Base UI Toolbar, in the shadcn composition, styled by NIF tokens. One tab stop, arrow keys move between controls. */
@@ -40,7 +41,7 @@ export function NmToolbarButton({
40
41
  }: Omit<ToolbarPrimitive.Button.Props, 'children'> & { icon?: NmIconId; label: string; tone?: NmIconButtonTone | NmButtonTone; pressed?: boolean; children?: ReactNode }) {
41
42
  const withText = children != null;
42
43
  const buttonTone = (Object.values(NM_BUTTON_TONES) as string[]).includes(tone ?? '') ? (tone as NmButtonTone) : 'muted';
43
- return (
44
+ const button = (
44
45
  <ToolbarPrimitive.Button
45
46
  data-slot="toolbar-button"
46
47
  aria-label={label}
@@ -52,6 +53,8 @@ export function NmToolbarButton({
52
53
  {withText ? <span className="nm-button__label">{children}</span> : null}
53
54
  </ToolbarPrimitive.Button>
54
55
  );
56
+ // Always wrapped, so the control keeps its place in the tree when its icon is added or removed.
57
+ return <AnimateIcon animateOnHover={Boolean(icon)} asChild>{button}</AnimateIcon>;
55
58
  }
56
59
 
57
60
  /** A field in the bar that takes the free width, such as an address. */
@@ -1,4 +1,4 @@
1
- import { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip';
1
+ import { Tooltip as TooltipPrimitive } from '../../lib/nm-motion.ts';
2
2
  import { nmClass } from '../../lib/nm-class.ts';
3
3
 
4
4
  export function NmTooltipProvider({ delay = 0, ...props }: TooltipPrimitive.Provider.Props) {
@@ -0,0 +1,17 @@
1
+ .nm-voice-orb {
2
+ --orb-ui-size: var(--nm-icon-control);
3
+ flex-shrink: 0;
4
+ border-radius: var(--nm-control-radius);
5
+ overflow: hidden;
6
+ }
7
+
8
+ .nm-voice-orb .orb-ui__content {
9
+ overflow: hidden;
10
+ border-radius: inherit;
11
+ }
12
+
13
+ .nm-voice-orb:focus-visible {
14
+ outline: var(--nm-rule) solid var(--nm-fg);
15
+ outline-offset: var(--nm-space-2);
16
+ }
17
+
@@ -0,0 +1,112 @@
1
+ import { useMemo, type ReactNode } from 'react';
2
+ import { Orb, type OrbAdapter, type OrbSignal, type OrbTheme } from 'orb-ui';
3
+ import {
4
+ NM_VOICE_ORB_CLASSES,
5
+ NM_VOICE_ORB_SIZE,
6
+ toOrbUiState,
7
+ type NmVoiceOrbAgentState,
8
+ } from '../../config/nm-voice-orb.ts';
9
+ import { useNmVoiceOrbTheme } from './use-nm-voice-orb-theme.ts';
10
+ import { createLogger } from '../../kit-logger.ts';
11
+
12
+ const logger = createLogger('interface-nm-voice-orb');
13
+
14
+ export type NmVoiceOrbProps = {
15
+ adapter?: OrbAdapter;
16
+ signal?: OrbSignal;
17
+ agentState?: NmVoiceOrbAgentState;
18
+ theme?: OrbTheme;
19
+ size?: number;
20
+ inputVolume?: number;
21
+ outputVolume?: number;
22
+ getInputVolume?: () => number;
23
+ getOutputVolume?: () => number;
24
+ label?: string;
25
+ testId?: string;
26
+ onClick?: () => void;
27
+ };
28
+
29
+ function clamp01(value: number | undefined): number {
30
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
31
+ return 0;
32
+ }
33
+ return Math.min(1, Math.max(0, value));
34
+ }
35
+
36
+ function ensureMatchMedia(): void {
37
+ if (typeof window === 'undefined' || typeof window.matchMedia === 'function') {
38
+ return;
39
+ }
40
+ Object.defineProperty(window, 'matchMedia', {
41
+ writable: true,
42
+ configurable: true,
43
+ value: (query: string) => ({
44
+ matches: false,
45
+ media: query,
46
+ onchange: null,
47
+ addListener: () => {},
48
+ removeListener: () => {},
49
+ addEventListener: () => {},
50
+ removeEventListener: () => {},
51
+ dispatchEvent: () => false,
52
+ }),
53
+ });
54
+ }
55
+
56
+ export function NmVoiceOrb(props: NmVoiceOrbProps): ReactNode {
57
+ ensureMatchMedia();
58
+ const appearanceTheme = useNmVoiceOrbTheme();
59
+
60
+ const {
61
+ signal,
62
+ adapter,
63
+ inputVolume: propInputVolume,
64
+ outputVolume: propOutputVolume,
65
+ getInputVolume,
66
+ getOutputVolume,
67
+ } = props;
68
+ const mappedState = toOrbUiState(props.agentState);
69
+ const fallbackSignal = useMemo<OrbSignal | undefined>(() => {
70
+ if (signal) {
71
+ return signal;
72
+ }
73
+ if (adapter) {
74
+ return undefined;
75
+ }
76
+ const inputVolume = clamp01(propInputVolume ?? getInputVolume?.() ?? 0);
77
+ const outputVolume = clamp01(
78
+ propOutputVolume ?? getOutputVolume?.() ?? (mappedState === 'speaking' ? 0.72 : 0),
79
+ );
80
+ return {
81
+ state: mappedState,
82
+ inputVolume,
83
+ outputVolume,
84
+ };
85
+ }, [
86
+ mappedState,
87
+ adapter,
88
+ getInputVolume,
89
+ getOutputVolume,
90
+ propInputVolume,
91
+ propOutputVolume,
92
+ signal,
93
+ ]);
94
+
95
+ logger.debug('render', 'orb-ui', { state: mappedState });
96
+
97
+ return (
98
+ <Orb
99
+ adapter={props.adapter}
100
+ signal={fallbackSignal}
101
+ state={props.agentState != null ? mappedState : undefined}
102
+ theme={props.theme ?? appearanceTheme}
103
+ size={props.size ?? NM_VOICE_ORB_SIZE}
104
+ className={NM_VOICE_ORB_CLASSES.root}
105
+ aria-label={props.label ?? 'Avsluta röstläge'}
106
+ onStart={props.onClick}
107
+ onStop={props.onClick}
108
+ data-testid={props.testId ?? 'voice-orb'}
109
+ data-agent-state={props.agentState ?? mappedState}
110
+ />
111
+ );
112
+ }
@@ -0,0 +1,34 @@
1
+ import { useEffect, useMemo, useState } from 'react';
2
+ import { defineOrbTheme, type OrbTheme } from 'orb-ui';
3
+ import { deriveCloudOrbAppearance } from '../../lib/appearance/cloudOrbAppearance.ts';
4
+ import { NM_APPEARANCE_APPLIED_EVENT } from '../../config/nm-appearance.ts';
5
+ import { NM_ORB_UI_THEME } from '../../config/nm-voice-orb.ts';
6
+
7
+ function readAppearanceChannel(name: string, fallback: string): string {
8
+ if (typeof document === 'undefined') {
9
+ return fallback;
10
+ }
11
+ const value = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
12
+ return value.length > 0 ? value : fallback;
13
+ }
14
+
15
+ export function useNmVoiceOrbTheme(): OrbTheme {
16
+ const [revision, setRevision] = useState(0);
17
+
18
+ useEffect(() => {
19
+ const sync = () => setRevision((value) => value + 1);
20
+ window.addEventListener(NM_APPEARANCE_APPLIED_EVENT, sync);
21
+ return () => window.removeEventListener(NM_APPEARANCE_APPLIED_EVENT, sync);
22
+ }, []);
23
+
24
+ return useMemo(() => {
25
+ void revision;
26
+ const accent = readAppearanceChannel('--nm-accent', '#D2BB9C');
27
+ const canvas = readAppearanceChannel('--nm-canvas', '#000000');
28
+ const fg = readAppearanceChannel('--nm-fg', '#FFFFFF');
29
+ return defineOrbTheme({
30
+ ...NM_ORB_UI_THEME,
31
+ appearance: deriveCloudOrbAppearance(accent, canvas, fg),
32
+ });
33
+ }, [revision]);
34
+ }
@@ -7,6 +7,7 @@ import {
7
7
  } from '../../config/nm-icon-button.ts';
8
8
  import { NORDMAN_CHROME_ICONS, type NordmanChromeIconId } from '../../config/nordman-chrome.ts';
9
9
  import { createLogger } from '../../kit-logger.ts';
10
+ import { AnimateIcon, Button as MotionButton } from '../../lib/nm-motion.ts';
10
11
  import { NmIcon } from '../nm-icon/nm-icon.tsx';
11
12
 
12
13
  const logger = createLogger('interface-nordman-icon-button');
@@ -174,11 +175,14 @@ export const NordmanIconButton = forwardRef<HTMLButtonElement, NordmanIconButton
174
175
  </a>
175
176
  );
176
177
  }
177
- return (
178
- <button {...rest} {...shared} ref={ref} type={type ?? 'button'}>
178
+ const button = (
179
+ <MotionButton {...(rest as object)} {...shared} ref={ref} type={type ?? 'button'} hoverScale={rest.disabled ? 1 : 1.08} tapScale={rest.disabled ? 1 : 0.92}>
179
180
  {face}
180
- </button>
181
+ </MotionButton>
181
182
  );
183
+ // The icon plays its animation when the button is hovered.
184
+ // Always wrapped: a control that swaps its glyph for an image must stay the same element.
185
+ return <AnimateIcon animateOnHover={glyph != null} asChild>{button}</AnimateIcon>;
182
186
  },
183
187
  );
184
188
 
@@ -4,6 +4,10 @@ export const NM_BUTTON_TONES = {
4
4
  muted: 'muted',
5
5
  panel: 'panel',
6
6
  danger: 'danger',
7
+ /** No fill until hovered. For actions that sit inside other content. */
8
+ ghost: 'ghost',
9
+ /** Text with an underline on hover. */
10
+ link: 'link',
7
11
  } as const;
8
12
 
9
13
  export type NmButtonTone = (typeof NM_BUTTON_TONES)[keyof typeof NM_BUTTON_TONES];
@@ -24,6 +28,8 @@ export const NM_BUTTON_CLASSES = {
24
28
  muted: 'nm-button--muted',
25
29
  panel: 'nm-button--panel',
26
30
  danger: 'nm-button--danger',
31
+ ghost: 'nm-button--ghost',
32
+ link: 'nm-button--link',
27
33
  default: 'nm-button--default',
28
34
  compact: 'nm-button--compact',
29
35
  chip: 'nm-button--chip',