@dxos/react-ui 0.8.4-main.c4373fc → 0.8.4-main.c85a9c8dae

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 (396) hide show
  1. package/dist/lib/browser/chunk-2FKSMWNY.mjs +774 -0
  2. package/dist/lib/browser/chunk-2FKSMWNY.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +3929 -66
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +77 -55
  7. package/dist/lib/browser/testing/index.mjs.map +4 -4
  8. package/dist/lib/node-esm/chunk-ZNBLTSHI.mjs +776 -0
  9. package/dist/lib/node-esm/chunk-ZNBLTSHI.mjs.map +7 -0
  10. package/dist/lib/node-esm/index.mjs +3929 -66
  11. package/dist/lib/node-esm/index.mjs.map +4 -4
  12. package/dist/lib/node-esm/meta.json +1 -1
  13. package/dist/lib/node-esm/testing/index.mjs +77 -55
  14. package/dist/lib/node-esm/testing/index.mjs.map +4 -4
  15. package/dist/types/src/components/Avatars/Avatar.d.ts.map +1 -1
  16. package/dist/types/src/components/Avatars/Avatar.stories.d.ts.map +1 -1
  17. package/dist/types/src/components/Breadcrumb/Breadcrumb.d.ts.map +1 -1
  18. package/dist/types/src/components/{Buttons → Button}/Button.d.ts +1 -1
  19. package/dist/types/src/components/Button/Button.d.ts.map +1 -0
  20. package/dist/types/src/components/Button/Button.stories.d.ts.map +1 -0
  21. package/dist/types/src/components/{Buttons → Button}/IconButton.d.ts +2 -2
  22. package/dist/types/src/components/Button/IconButton.d.ts.map +1 -0
  23. package/dist/types/src/components/Button/IconButton.stories.d.ts.map +1 -0
  24. package/dist/types/src/components/Button/Toggle.d.ts.map +1 -0
  25. package/dist/types/src/components/Button/Toggle.stories.d.ts +16 -0
  26. package/dist/types/src/components/Button/Toggle.stories.d.ts.map +1 -0
  27. package/dist/types/src/components/{Buttons → Button}/ToggleGroup.d.ts +4 -4
  28. package/dist/types/src/components/Button/ToggleGroup.d.ts.map +1 -0
  29. package/dist/types/src/components/{Buttons → Button}/ToggleGroup.stories.d.ts +4 -4
  30. package/dist/types/src/components/Button/ToggleGroup.stories.d.ts.map +1 -0
  31. package/dist/types/src/components/Button/index.d.ts.map +1 -0
  32. package/dist/types/src/components/Card/Card.d.ts +121 -0
  33. package/dist/types/src/components/Card/Card.d.ts.map +1 -0
  34. package/dist/types/src/components/Card/Card.stories.d.ts +21 -0
  35. package/dist/types/src/components/Card/Card.stories.d.ts.map +1 -0
  36. package/dist/types/src/components/Card/index.d.ts +2 -0
  37. package/dist/types/src/components/Card/index.d.ts.map +1 -0
  38. package/dist/types/src/components/Clipboard/CopyButton.d.ts +1 -1
  39. package/dist/types/src/components/Clipboard/CopyButton.d.ts.map +1 -1
  40. package/dist/types/src/components/DensityProvider/DensityProvider.d.ts +1 -1
  41. package/dist/types/src/components/DensityProvider/DensityProvider.d.ts.map +1 -1
  42. package/dist/types/src/components/{Dialogs → Dialog}/AlertDialog.d.ts +12 -3
  43. package/dist/types/src/components/Dialog/AlertDialog.d.ts.map +1 -0
  44. package/dist/types/src/components/Dialog/AlertDialog.stories.d.ts.map +1 -0
  45. package/dist/types/src/components/Dialog/Dialog.d.ts +47 -0
  46. package/dist/types/src/components/Dialog/Dialog.d.ts.map +1 -0
  47. package/dist/types/src/components/{Dialogs → Dialog}/Dialog.stories.d.ts +9 -10
  48. package/dist/types/src/components/Dialog/Dialog.stories.d.ts.map +1 -0
  49. package/dist/types/src/components/Dialog/index.d.ts.map +1 -0
  50. package/dist/types/src/components/ElevationProvider/ElevationProvider.d.ts +1 -1
  51. package/dist/types/src/components/ElevationProvider/ElevationProvider.d.ts.map +1 -1
  52. package/dist/types/src/components/ErrorFallback/ErrorFallback.d.ts +11 -0
  53. package/dist/types/src/components/ErrorFallback/ErrorFallback.d.ts.map +1 -0
  54. package/dist/types/src/components/ErrorFallback/ErrorFallback.stories.d.ts +7 -0
  55. package/dist/types/src/components/ErrorFallback/ErrorFallback.stories.d.ts.map +1 -0
  56. package/dist/types/src/components/ErrorFallback/ErrorStack.d.ts +8 -0
  57. package/dist/types/src/components/ErrorFallback/ErrorStack.d.ts.map +1 -0
  58. package/dist/types/src/components/ErrorFallback/ThrowError.d.ts +9 -0
  59. package/dist/types/src/components/ErrorFallback/ThrowError.d.ts.map +1 -0
  60. package/dist/types/src/components/ErrorFallback/index.d.ts +5 -0
  61. package/dist/types/src/components/ErrorFallback/index.d.ts.map +1 -0
  62. package/dist/types/src/components/Icon/Icon.d.ts +1 -1
  63. package/dist/types/src/components/Icon/Icon.d.ts.map +1 -1
  64. package/dist/types/src/components/Icon/Icon.stories.d.ts +17 -0
  65. package/dist/types/src/components/Icon/Icon.stories.d.ts.map +1 -0
  66. package/dist/types/src/components/Image/Image.d.ts +14 -0
  67. package/dist/types/src/components/Image/Image.d.ts.map +1 -0
  68. package/dist/types/src/components/Image/Image.stories.d.ts +33 -0
  69. package/dist/types/src/components/Image/Image.stories.d.ts.map +1 -0
  70. package/dist/types/src/components/Image/index.d.ts +2 -0
  71. package/dist/types/src/components/Image/index.d.ts.map +1 -0
  72. package/dist/types/src/components/Input/Input.d.ts +5 -5
  73. package/dist/types/src/components/Input/Input.d.ts.map +1 -1
  74. package/dist/types/src/components/Input/Input.stories.d.ts +8 -8
  75. package/dist/types/src/components/Input/Input.stories.d.ts.map +1 -1
  76. package/dist/types/src/components/{Lists → List}/List.d.ts +1 -1
  77. package/dist/types/src/components/List/List.d.ts.map +1 -0
  78. package/dist/types/src/components/List/List.stories.d.ts.map +1 -0
  79. package/dist/types/src/components/List/ListDropIndicator.d.ts.map +1 -0
  80. package/dist/types/src/components/List/Tree.d.ts.map +1 -0
  81. package/dist/types/src/components/List/Tree.stories.d.ts.map +1 -0
  82. package/dist/types/src/components/List/TreeDropIndicator.d.ts.map +1 -0
  83. package/dist/types/src/components/List/Treegrid.d.ts.map +1 -0
  84. package/dist/types/src/components/List/Treegrid.stories.d.ts.map +1 -0
  85. package/dist/types/src/components/List/index.d.ts.map +1 -0
  86. package/dist/types/src/components/Main/Main.d.ts +17 -18
  87. package/dist/types/src/components/Main/Main.d.ts.map +1 -1
  88. package/dist/types/src/components/Main/Main.stories.d.ts +0 -3
  89. package/dist/types/src/components/Main/Main.stories.d.ts.map +1 -1
  90. package/dist/types/src/components/{Menus → Menu}/ContextMenu.d.ts +6 -6
  91. package/dist/types/src/components/Menu/ContextMenu.d.ts.map +1 -0
  92. package/dist/types/src/components/Menu/ContextMenu.stories.d.ts.map +1 -0
  93. package/dist/types/src/components/{Menus → Menu}/DropdownMenu.d.ts +3 -4
  94. package/dist/types/src/components/Menu/DropdownMenu.d.ts.map +1 -0
  95. package/dist/types/src/components/Menu/DropdownMenu.stories.d.ts.map +1 -0
  96. package/dist/types/src/components/Menu/index.d.ts.map +1 -0
  97. package/dist/types/src/components/Message/Message.d.ts +1 -1
  98. package/dist/types/src/components/Message/Message.d.ts.map +1 -1
  99. package/dist/types/src/components/Message/Message.stories.d.ts +3 -4
  100. package/dist/types/src/components/Message/Message.stories.d.ts.map +1 -1
  101. package/dist/types/src/components/Popover/Popover.d.ts +2 -1
  102. package/dist/types/src/components/Popover/Popover.d.ts.map +1 -1
  103. package/dist/types/src/components/ScrollArea/ScrollArea.d.ts +25 -26
  104. package/dist/types/src/components/ScrollArea/ScrollArea.d.ts.map +1 -1
  105. package/dist/types/src/components/ScrollArea/ScrollArea.stories.d.ts +46 -8
  106. package/dist/types/src/components/ScrollArea/ScrollArea.stories.d.ts.map +1 -1
  107. package/dist/types/src/components/ScrollContainer/ScrollContainer.d.ts +39 -0
  108. package/dist/types/src/components/ScrollContainer/ScrollContainer.d.ts.map +1 -0
  109. package/dist/types/src/components/ScrollContainer/ScrollContainer.stories.d.ts +24 -0
  110. package/dist/types/src/components/ScrollContainer/ScrollContainer.stories.d.ts.map +1 -0
  111. package/dist/types/src/components/ScrollContainer/index.d.ts +2 -0
  112. package/dist/types/src/components/ScrollContainer/index.d.ts.map +1 -0
  113. package/dist/types/src/components/Select/Select.d.ts +10 -10
  114. package/dist/types/src/components/Select/Select.d.ts.map +1 -1
  115. package/dist/types/src/components/Separator/Separator.d.ts +1 -1
  116. package/dist/types/src/components/Skeleton/Skeleton.d.ts +12 -0
  117. package/dist/types/src/components/Skeleton/Skeleton.d.ts.map +1 -0
  118. package/dist/types/src/components/Skeleton/Skeleton.stories.d.ts +17 -0
  119. package/dist/types/src/components/Skeleton/Skeleton.stories.d.ts.map +1 -0
  120. package/dist/types/src/components/Skeleton/index.d.ts +2 -0
  121. package/dist/types/src/components/Skeleton/index.d.ts.map +1 -0
  122. package/dist/types/src/components/Splitter/Splitter.d.ts +36 -0
  123. package/dist/types/src/components/Splitter/Splitter.d.ts.map +1 -0
  124. package/dist/types/src/components/Splitter/Splitter.stories.d.ts +7 -0
  125. package/dist/types/src/components/Splitter/Splitter.stories.d.ts.map +1 -0
  126. package/dist/types/src/components/Splitter/index.d.ts +2 -0
  127. package/dist/types/src/components/Splitter/index.d.ts.map +1 -0
  128. package/dist/types/src/components/Status/Status.stories.d.ts +4 -2
  129. package/dist/types/src/components/Status/Status.stories.d.ts.map +1 -1
  130. package/dist/types/src/components/Tag/Tag.d.ts +1 -1
  131. package/dist/types/src/components/Tag/Tag.d.ts.map +1 -1
  132. package/dist/types/src/components/Tag/Tag.stories.d.ts +0 -5
  133. package/dist/types/src/components/Tag/Tag.stories.d.ts.map +1 -1
  134. package/dist/types/src/components/ThemeProvider/ThemeProvider.d.ts +2 -2
  135. package/dist/types/src/components/ThemeProvider/ThemeProvider.d.ts.map +1 -1
  136. package/dist/types/src/components/ThemeProvider/ThemeProvider.stories.d.ts +12 -0
  137. package/dist/types/src/components/ThemeProvider/ThemeProvider.stories.d.ts.map +1 -0
  138. package/dist/types/src/components/ThemeProvider/TranslationsProvider.d.ts +1 -8
  139. package/dist/types/src/components/ThemeProvider/TranslationsProvider.d.ts.map +1 -1
  140. package/dist/types/src/components/ThemeProvider/index.d.ts +2 -1
  141. package/dist/types/src/components/ThemeProvider/index.d.ts.map +1 -1
  142. package/dist/types/src/components/Toast/Toast.d.ts +4 -4
  143. package/dist/types/src/components/Toast/Toast.d.ts.map +1 -1
  144. package/dist/types/src/components/Toolbar/Toolbar.d.ts +45 -13
  145. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  146. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  147. package/dist/types/src/components/Tooltip/Tooltip.d.ts.map +1 -1
  148. package/dist/types/src/components/Tooltip/Tooltip.stories.d.ts.map +1 -1
  149. package/dist/types/src/components/index.d.ts +13 -8
  150. package/dist/types/src/components/index.d.ts.map +1 -1
  151. package/dist/types/src/exemplars/generics.stories.d.ts +18 -0
  152. package/dist/types/src/exemplars/generics.stories.d.ts.map +1 -0
  153. package/dist/types/src/exemplars/slot.stories.d.ts +14 -0
  154. package/dist/types/src/exemplars/slot.stories.d.ts.map +1 -0
  155. package/dist/types/src/exemplars/tabster.stories.d.ts +8 -0
  156. package/dist/types/src/exemplars/tabster.stories.d.ts.map +1 -0
  157. package/dist/types/src/exemplars/virtualizer.stories.d.ts +11 -0
  158. package/dist/types/src/exemplars/virtualizer.stories.d.ts.map +1 -0
  159. package/dist/types/src/hooks/useDensityContext.d.ts +1 -1
  160. package/dist/types/src/hooks/useDensityContext.d.ts.map +1 -1
  161. package/dist/types/src/hooks/useElevationContext.d.ts +1 -1
  162. package/dist/types/src/hooks/useElevationContext.d.ts.map +1 -1
  163. package/dist/types/src/hooks/useVisualViewport.d.ts +2 -2
  164. package/dist/types/src/hooks/useVisualViewport.d.ts.map +1 -1
  165. package/dist/types/src/index.d.ts +2 -1
  166. package/dist/types/src/index.d.ts.map +1 -1
  167. package/dist/types/src/playground/Controls.stories.d.ts.map +1 -1
  168. package/dist/types/src/playground/Custom.stories.d.ts.map +1 -1
  169. package/dist/types/src/primitives/Column/Column.d.ts +32 -0
  170. package/dist/types/src/primitives/Column/Column.d.ts.map +1 -0
  171. package/dist/types/src/primitives/Column/Column.stories.d.ts +6 -0
  172. package/dist/types/src/primitives/Column/Column.stories.d.ts.map +1 -0
  173. package/dist/types/src/primitives/Column/index.d.ts +2 -0
  174. package/dist/types/src/primitives/Column/index.d.ts.map +1 -0
  175. package/dist/types/src/primitives/Flex/Flex.d.ts +8 -0
  176. package/dist/types/src/primitives/Flex/Flex.d.ts.map +1 -0
  177. package/dist/types/src/primitives/Flex/Flex.stories.d.ts +8 -0
  178. package/dist/types/src/primitives/Flex/Flex.stories.d.ts.map +1 -0
  179. package/dist/types/src/primitives/Flex/index.d.ts +2 -0
  180. package/dist/types/src/primitives/Flex/index.d.ts.map +1 -0
  181. package/dist/types/src/primitives/Grid/Grid.d.ts +9 -0
  182. package/dist/types/src/primitives/Grid/Grid.d.ts.map +1 -0
  183. package/dist/types/src/primitives/Grid/Grid.stories.d.ts +8 -0
  184. package/dist/types/src/primitives/Grid/Grid.stories.d.ts.map +1 -0
  185. package/dist/types/src/primitives/Grid/index.d.ts +2 -0
  186. package/dist/types/src/primitives/Grid/index.d.ts.map +1 -0
  187. package/dist/types/src/primitives/Panel/Panel.d.ts +34 -0
  188. package/dist/types/src/primitives/Panel/Panel.d.ts.map +1 -0
  189. package/dist/types/src/primitives/Panel/Panel.stories.d.ts +6 -0
  190. package/dist/types/src/primitives/Panel/Panel.stories.d.ts.map +1 -0
  191. package/dist/types/src/primitives/Panel/index.d.ts +2 -0
  192. package/dist/types/src/primitives/Panel/index.d.ts.map +1 -0
  193. package/dist/types/src/primitives/index.d.ts +5 -0
  194. package/dist/types/src/primitives/index.d.ts.map +1 -0
  195. package/dist/types/src/testing/decorators/index.d.ts +2 -1
  196. package/dist/types/src/testing/decorators/index.d.ts.map +1 -1
  197. package/dist/types/src/testing/decorators/withLayout.d.ts +15 -0
  198. package/dist/types/src/testing/decorators/withLayout.d.ts.map +1 -0
  199. package/dist/types/src/testing/decorators/withLayoutVariants.d.ts +12 -0
  200. package/dist/types/src/testing/decorators/withLayoutVariants.d.ts.map +1 -0
  201. package/dist/types/src/testing/decorators/withTheme.d.ts +3 -2
  202. package/dist/types/src/testing/decorators/withTheme.d.ts.map +1 -1
  203. package/dist/types/src/translations.d.ts +11 -0
  204. package/dist/types/src/translations.d.ts.map +1 -0
  205. package/dist/types/src/util/index.d.ts +1 -2
  206. package/dist/types/src/util/index.d.ts.map +1 -1
  207. package/dist/types/tsconfig.tsbuildinfo +1 -1
  208. package/package.json +41 -29
  209. package/src/components/Avatars/Avatar.stories.tsx +5 -6
  210. package/src/components/Avatars/Avatar.tsx +6 -13
  211. package/src/components/Avatars/AvatarGroup.stories.tsx +2 -2
  212. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +4 -4
  213. package/src/components/Breadcrumb/Breadcrumb.tsx +11 -37
  214. package/src/components/{Buttons → Button}/Button.stories.tsx +4 -4
  215. package/src/components/{Buttons → Button}/Button.tsx +7 -13
  216. package/src/components/{Buttons → Button}/IconButton.stories.tsx +4 -4
  217. package/src/components/{Buttons → Button}/IconButton.tsx +20 -14
  218. package/src/components/{Buttons → Button}/Toggle.stories.tsx +7 -6
  219. package/src/components/{Buttons → Button}/ToggleGroup.stories.tsx +2 -2
  220. package/src/components/Card/Card.stories.tsx +151 -0
  221. package/src/components/Card/Card.tsx +352 -0
  222. package/src/components/Card/index.ts +5 -0
  223. package/src/components/Clipboard/CopyButton.tsx +7 -7
  224. package/src/components/DensityProvider/DensityProvider.tsx +1 -1
  225. package/src/components/Dialog/AlertDialog.stories.tsx +69 -0
  226. package/src/components/{Dialogs → Dialog}/AlertDialog.tsx +116 -16
  227. package/src/components/Dialog/Dialog.stories.tsx +122 -0
  228. package/src/components/{Dialogs → Dialog}/Dialog.tsx +179 -49
  229. package/src/components/ElevationProvider/ElevationProvider.tsx +1 -1
  230. package/src/components/ErrorFallback/ErrorFallback.stories.tsx +50 -0
  231. package/src/components/ErrorFallback/ErrorFallback.tsx +70 -0
  232. package/src/components/ErrorFallback/ErrorStack.tsx +80 -0
  233. package/src/components/ErrorFallback/ThrowError.tsx +37 -0
  234. package/src/components/ErrorFallback/index.ts +9 -0
  235. package/src/components/Icon/Icon.stories.tsx +113 -0
  236. package/src/components/Icon/Icon.tsx +3 -3
  237. package/src/components/Image/Image.stories.tsx +86 -0
  238. package/src/components/Image/Image.tsx +223 -0
  239. package/src/components/Image/index.ts +5 -0
  240. package/src/components/Input/Input.stories.tsx +21 -40
  241. package/src/components/Input/Input.tsx +32 -68
  242. package/src/components/Link/Link.stories.tsx +2 -2
  243. package/src/components/Link/Link.tsx +2 -2
  244. package/src/components/{Lists → List}/List.stories.tsx +30 -33
  245. package/src/components/{Lists → List}/List.tsx +12 -17
  246. package/src/components/{Lists → List}/ListDropIndicator.tsx +7 -7
  247. package/src/components/{Lists → List}/Tree.stories.tsx +4 -4
  248. package/src/components/{Lists → List}/TreeDropIndicator.tsx +6 -6
  249. package/src/components/{Lists → List}/Treegrid.stories.tsx +3 -3
  250. package/src/components/{Lists → List}/Treegrid.tsx +10 -15
  251. package/src/components/Main/Main.stories.tsx +41 -23
  252. package/src/components/Main/Main.tsx +149 -92
  253. package/src/components/{Menus → Menu}/ContextMenu.stories.tsx +2 -2
  254. package/src/components/{Menus → Menu}/ContextMenu.tsx +9 -33
  255. package/src/components/{Menus → Menu}/DropdownMenu.stories.tsx +3 -3
  256. package/src/components/{Menus → Menu}/DropdownMenu.tsx +89 -68
  257. package/src/components/Message/Message.stories.tsx +26 -11
  258. package/src/components/Message/Message.tsx +46 -33
  259. package/src/components/Popover/Popover.stories.tsx +5 -5
  260. package/src/components/Popover/Popover.tsx +78 -56
  261. package/src/components/ScrollArea/ScrollArea.stories.tsx +166 -40
  262. package/src/components/ScrollArea/ScrollArea.tsx +84 -82
  263. package/src/components/ScrollArea/index.ts +1 -1
  264. package/src/components/ScrollContainer/ScrollContainer.stories.tsx +89 -0
  265. package/src/components/ScrollContainer/ScrollContainer.tsx +238 -0
  266. package/src/components/ScrollContainer/index.ts +5 -0
  267. package/src/components/Select/Select.stories.tsx +3 -3
  268. package/src/components/Select/Select.tsx +11 -27
  269. package/src/components/Separator/Separator.tsx +1 -1
  270. package/src/components/Skeleton/Skeleton.stories.tsx +52 -0
  271. package/src/components/Skeleton/Skeleton.tsx +26 -0
  272. package/src/components/Skeleton/index.ts +5 -0
  273. package/src/components/Splitter/Splitter.stories.tsx +83 -0
  274. package/src/components/Splitter/Splitter.tsx +138 -0
  275. package/src/components/Splitter/index.ts +5 -0
  276. package/src/components/Status/Status.stories.tsx +21 -17
  277. package/src/components/Status/Status.tsx +2 -2
  278. package/src/components/Tag/Tag.stories.tsx +6 -11
  279. package/src/components/Tag/Tag.tsx +3 -8
  280. package/src/components/ThemeProvider/ThemeProvider.stories.tsx +32 -0
  281. package/src/components/ThemeProvider/ThemeProvider.tsx +5 -6
  282. package/src/components/ThemeProvider/TranslationsProvider.tsx +1 -16
  283. package/src/components/ThemeProvider/index.ts +3 -3
  284. package/src/components/Toast/Toast.stories.tsx +3 -3
  285. package/src/components/Toast/Toast.tsx +10 -14
  286. package/src/components/Toolbar/Toolbar.stories.tsx +4 -6
  287. package/src/components/Toolbar/Toolbar.tsx +196 -9
  288. package/src/components/Tooltip/Tooltip.stories.tsx +16 -14
  289. package/src/components/Tooltip/Tooltip.tsx +25 -22
  290. package/src/components/index.ts +14 -9
  291. package/src/exemplars/generics.stories.tsx +49 -0
  292. package/src/exemplars/slot.stories.tsx +119 -0
  293. package/src/exemplars/tabster.stories.tsx +127 -0
  294. package/src/exemplars/virtualizer.stories.tsx +137 -0
  295. package/src/hooks/useDensityContext.ts +1 -1
  296. package/src/hooks/useElevationContext.ts +1 -1
  297. package/src/hooks/useSafeArea.ts +2 -2
  298. package/src/hooks/useVisualViewport.ts +3 -3
  299. package/src/index.ts +2 -1
  300. package/src/playground/Controls.stories.tsx +3 -10
  301. package/src/playground/Custom.stories.tsx +8 -10
  302. package/src/playground/Typography.stories.tsx +3 -3
  303. package/src/primitives/Column/Column.stories.tsx +72 -0
  304. package/src/primitives/Column/Column.tsx +156 -0
  305. package/src/primitives/Column/index.ts +5 -0
  306. package/src/primitives/Flex/Flex.stories.tsx +58 -0
  307. package/src/primitives/Flex/Flex.tsx +27 -0
  308. package/src/primitives/Flex/index.ts +5 -0
  309. package/src/primitives/Grid/Grid.stories.tsx +57 -0
  310. package/src/primitives/Grid/Grid.tsx +43 -0
  311. package/src/primitives/Grid/index.ts +5 -0
  312. package/src/primitives/Panel/Panel.stories.tsx +67 -0
  313. package/src/primitives/Panel/Panel.tsx +137 -0
  314. package/src/primitives/Panel/index.ts +5 -0
  315. package/src/primitives/index.ts +8 -0
  316. package/src/testing/decorators/index.ts +2 -1
  317. package/src/testing/decorators/withLayout.tsx +77 -0
  318. package/src/testing/decorators/withLayoutVariants.tsx +48 -0
  319. package/src/testing/decorators/withTheme.tsx +21 -18
  320. package/src/translations.ts +19 -0
  321. package/src/util/index.ts +2 -2
  322. package/dist/lib/browser/chunk-B7HPXBP2.mjs +0 -4514
  323. package/dist/lib/browser/chunk-B7HPXBP2.mjs.map +0 -7
  324. package/dist/lib/node-esm/chunk-6JCSY5Y7.mjs +0 -4516
  325. package/dist/lib/node-esm/chunk-6JCSY5Y7.mjs.map +0 -7
  326. package/dist/types/src/components/AnchoredOverflow/AnchoredOverflow.d.ts +0 -15
  327. package/dist/types/src/components/AnchoredOverflow/AnchoredOverflow.d.ts.map +0 -1
  328. package/dist/types/src/components/AnchoredOverflow/index.d.ts +0 -2
  329. package/dist/types/src/components/AnchoredOverflow/index.d.ts.map +0 -1
  330. package/dist/types/src/components/Buttons/Button.d.ts.map +0 -1
  331. package/dist/types/src/components/Buttons/Button.stories.d.ts.map +0 -1
  332. package/dist/types/src/components/Buttons/IconButton.d.ts.map +0 -1
  333. package/dist/types/src/components/Buttons/IconButton.stories.d.ts.map +0 -1
  334. package/dist/types/src/components/Buttons/Toggle.d.ts.map +0 -1
  335. package/dist/types/src/components/Buttons/Toggle.stories.d.ts +0 -13
  336. package/dist/types/src/components/Buttons/Toggle.stories.d.ts.map +0 -1
  337. package/dist/types/src/components/Buttons/ToggleGroup.d.ts.map +0 -1
  338. package/dist/types/src/components/Buttons/ToggleGroup.stories.d.ts.map +0 -1
  339. package/dist/types/src/components/Buttons/index.d.ts.map +0 -1
  340. package/dist/types/src/components/Dialogs/AlertDialog.d.ts.map +0 -1
  341. package/dist/types/src/components/Dialogs/AlertDialog.stories.d.ts.map +0 -1
  342. package/dist/types/src/components/Dialogs/Dialog.d.ts +0 -31
  343. package/dist/types/src/components/Dialogs/Dialog.d.ts.map +0 -1
  344. package/dist/types/src/components/Dialogs/Dialog.stories.d.ts.map +0 -1
  345. package/dist/types/src/components/Dialogs/index.d.ts.map +0 -1
  346. package/dist/types/src/components/Lists/List.d.ts.map +0 -1
  347. package/dist/types/src/components/Lists/List.stories.d.ts.map +0 -1
  348. package/dist/types/src/components/Lists/ListDropIndicator.d.ts.map +0 -1
  349. package/dist/types/src/components/Lists/Tree.d.ts.map +0 -1
  350. package/dist/types/src/components/Lists/Tree.stories.d.ts.map +0 -1
  351. package/dist/types/src/components/Lists/TreeDropIndicator.d.ts.map +0 -1
  352. package/dist/types/src/components/Lists/Treegrid.d.ts.map +0 -1
  353. package/dist/types/src/components/Lists/Treegrid.stories.d.ts.map +0 -1
  354. package/dist/types/src/components/Lists/index.d.ts.map +0 -1
  355. package/dist/types/src/components/Menus/ContextMenu.d.ts.map +0 -1
  356. package/dist/types/src/components/Menus/ContextMenu.stories.d.ts.map +0 -1
  357. package/dist/types/src/components/Menus/DropdownMenu.d.ts.map +0 -1
  358. package/dist/types/src/components/Menus/DropdownMenu.stories.d.ts.map +0 -1
  359. package/dist/types/src/components/Menus/index.d.ts.map +0 -1
  360. package/dist/types/src/testing/decorators/withSurfaceVariantsLayout.d.ts +0 -12
  361. package/dist/types/src/testing/decorators/withSurfaceVariantsLayout.d.ts.map +0 -1
  362. package/dist/types/src/util/ThemedClassName.d.ts +0 -5
  363. package/dist/types/src/util/ThemedClassName.d.ts.map +0 -1
  364. package/dist/types/src/util/domino.d.ts +0 -18
  365. package/dist/types/src/util/domino.d.ts.map +0 -1
  366. package/src/components/AnchoredOverflow/AnchoredOverflow.tsx +0 -59
  367. package/src/components/AnchoredOverflow/index.ts +0 -5
  368. package/src/components/Dialogs/AlertDialog.stories.tsx +0 -69
  369. package/src/components/Dialogs/Dialog.stories.tsx +0 -67
  370. package/src/testing/decorators/withSurfaceVariantsLayout.tsx +0 -51
  371. package/src/util/ThemedClassName.ts +0 -7
  372. package/src/util/domino.ts +0 -51
  373. /package/dist/types/src/components/{Buttons → Button}/Button.stories.d.ts +0 -0
  374. /package/dist/types/src/components/{Buttons → Button}/IconButton.stories.d.ts +0 -0
  375. /package/dist/types/src/components/{Buttons → Button}/Toggle.d.ts +0 -0
  376. /package/dist/types/src/components/{Buttons → Button}/index.d.ts +0 -0
  377. /package/dist/types/src/components/{Dialogs → Dialog}/AlertDialog.stories.d.ts +0 -0
  378. /package/dist/types/src/components/{Dialogs → Dialog}/index.d.ts +0 -0
  379. /package/dist/types/src/components/{Lists → List}/List.stories.d.ts +0 -0
  380. /package/dist/types/src/components/{Lists → List}/ListDropIndicator.d.ts +0 -0
  381. /package/dist/types/src/components/{Lists → List}/Tree.d.ts +0 -0
  382. /package/dist/types/src/components/{Lists → List}/Tree.stories.d.ts +0 -0
  383. /package/dist/types/src/components/{Lists → List}/TreeDropIndicator.d.ts +0 -0
  384. /package/dist/types/src/components/{Lists → List}/Treegrid.d.ts +0 -0
  385. /package/dist/types/src/components/{Lists → List}/Treegrid.stories.d.ts +0 -0
  386. /package/dist/types/src/components/{Lists → List}/index.d.ts +0 -0
  387. /package/dist/types/src/components/{Menus → Menu}/ContextMenu.stories.d.ts +0 -0
  388. /package/dist/types/src/components/{Menus → Menu}/DropdownMenu.stories.d.ts +0 -0
  389. /package/dist/types/src/components/{Menus → Menu}/index.d.ts +0 -0
  390. /package/src/components/{Buttons → Button}/Toggle.tsx +0 -0
  391. /package/src/components/{Buttons → Button}/ToggleGroup.tsx +0 -0
  392. /package/src/components/{Buttons → Button}/index.ts +0 -0
  393. /package/src/components/{Dialogs → Dialog}/index.ts +0 -0
  394. /package/src/components/{Lists → List}/Tree.tsx +0 -0
  395. /package/src/components/{Lists → List}/index.ts +0 -0
  396. /package/src/components/{Menus → Menu}/index.ts +0 -0
@@ -0,0 +1,50 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React from 'react';
7
+
8
+ import { ErrorBoundary } from '@dxos/react-error-boundary';
9
+
10
+ import { withLayout, withTheme } from '../../testing';
11
+
12
+ import { ErrorFallback } from './ErrorFallback';
13
+ import { ThrowError } from './ThrowError';
14
+
15
+ const BasicStory = () => {
16
+ return (
17
+ <ErrorBoundary name='story' FallbackComponent={ErrorFallback}>
18
+ <ThrowError />
19
+ </ErrorBoundary>
20
+ );
21
+ };
22
+
23
+ const StringErrorStory = () => {
24
+ return <ErrorFallback error='This is a string error message' data={{ context: 'story' }} />;
25
+ };
26
+
27
+ const meta: Meta = {
28
+ title: 'ui/react-ui-core/components/ErrorFallback',
29
+ component: ErrorFallback,
30
+ decorators: [withTheme(), withLayout({ layout: 'column' })],
31
+ parameters: {
32
+ layout: 'fullscreen',
33
+ },
34
+ };
35
+
36
+ export default meta;
37
+
38
+ type Story = StoryObj<typeof meta>;
39
+
40
+ export const Default: Story = {
41
+ render: BasicStory,
42
+ play: async () => {
43
+ // This story intentionally renders an ErrorBoundary fallback; clear the smoke-test error flag.
44
+ (window as any).__ERROR_BOUNDARY_ERRORS__ = [];
45
+ },
46
+ };
47
+
48
+ export const StringError: Story = {
49
+ render: StringErrorStory,
50
+ };
@@ -0,0 +1,70 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import React, { type PropsWithChildren } from 'react';
6
+ import { type FallbackProps } from 'react-error-boundary';
7
+
8
+ import { safeStringify } from '@dxos/util';
9
+
10
+ import { ErrorStack } from './ErrorStack';
11
+
12
+ export type ErrorFallbackProps = PropsWithChildren<Pick<FallbackProps, 'error'> & { title?: string; data?: any }>;
13
+
14
+ /**
15
+ * Themed fallback component for `ErrorBoundary`.
16
+ */
17
+ export const ErrorFallback = ({ children, error, title, data }: ErrorFallbackProps) => {
18
+ const isDev = process.env.NODE_ENV === 'development';
19
+ const message = error instanceof Error ? error.message : String(error);
20
+
21
+ return (
22
+ <div role='alert' data-testid='error-boundary-fallback' className='flex flex-col p-4 gap-4 overflow-auto'>
23
+ <h1 className='text-lg text-info-text'>{title ?? 'Runtime Error'}</h1>
24
+ <p>{message}</p>
25
+
26
+ {isDev && error instanceof Error && (
27
+ <Section
28
+ title='Stack'
29
+ onClick={() => {
30
+ const text = error instanceof Error ? (error.stack ?? error.message) : String(error);
31
+ void navigator.clipboard.writeText(text);
32
+ }}
33
+ >
34
+ <ErrorStack error={error} />
35
+ </Section>
36
+ )}
37
+
38
+ {data && (
39
+ <Section
40
+ title='Data'
41
+ onClick={() => {
42
+ void navigator.clipboard.writeText(JSON.stringify(data, undefined, 2));
43
+ }}
44
+ >
45
+ <pre className='overflow-x-auto text-xs'>{safeStringify(data, undefined, 2)}</pre>
46
+ </Section>
47
+ )}
48
+
49
+ {children}
50
+ </div>
51
+ );
52
+ };
53
+
54
+ const Section = ({ children, title, onClick }: PropsWithChildren<{ title?: string; onClick?: () => void }>) => {
55
+ return (
56
+ <div className='flex flex-col gap-1'>
57
+ {onClick && (
58
+ <button
59
+ type='button'
60
+ onClick={onClick}
61
+ className='flex items-center gap-1 text-xs text-subdued hover:text-primary-500 transition-colors'
62
+ title={`Copy ${title}`}
63
+ >
64
+ <h2 className='text-xs uppercase text-subdued'>{title}</h2>
65
+ </button>
66
+ )}
67
+ {children}
68
+ </div>
69
+ );
70
+ };
@@ -0,0 +1,80 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import ErrorStackParser from 'error-stack-parser';
6
+ import React from 'react';
7
+
8
+ import { mx } from '@dxos/ui-theme';
9
+
10
+ type LocalFrame = { href: string; fileName: string };
11
+
12
+ /**
13
+ * Renders a parsed error stack trace with tree connector symbols and clickable vscode:// links for local frames.
14
+ */
15
+ export const ErrorStack = ({ error }: { error: Error }) => {
16
+ const frames = ErrorStackParser.parse(error);
17
+
18
+ return (
19
+ <div className='font-mono text-sm'>
20
+ {frames.map((frame, i) => {
21
+ const isLast = i === frames.length - 1;
22
+ const local = frame.fileName
23
+ ? parseLocalFrame(frame.fileName, frame.lineNumber, frame.columnNumber)
24
+ : undefined;
25
+ const name = frame.functionName ?? '<anonymous>';
26
+ return (
27
+ <div
28
+ key={i}
29
+ className={mx(
30
+ 'grid grid-cols-[16px_1fr_auto_auto] items-stretch gap-x-2',
31
+ local && 'cursor-pointer hover:bg-hover-surface',
32
+ )}
33
+ >
34
+ {/* Tree connector: vertical line full-height + horizontal branch at midpoint */}
35
+ <div className='relative'>
36
+ <div
37
+ className={mx(
38
+ 'absolute left-1/2 -translate-x-1/2 w-px bg-neutral-500',
39
+ isLast ? 'top-0 h-1/2' : 'inset-y-0',
40
+ )}
41
+ />
42
+ <div className='absolute top-1/2 -translate-y-1/2 left-1/2 right-0 h-px bg-neutral-500' />
43
+ </div>
44
+ {local ? (
45
+ <a href={local.href} className='truncate self-center'>
46
+ {name}
47
+ </a>
48
+ ) : (
49
+ <span className='text-subdued truncate self-center'>{name}</span>
50
+ )}
51
+ <span className='text-xs text-subdued truncate self-center'>{local?.fileName ?? ''}</span>
52
+ <span className='text-xs text-subdued text-right self-center'>
53
+ {local ? `${frame.lineNumber}:${frame.columnNumber}` : ''}
54
+ </span>
55
+ </div>
56
+ );
57
+ })}
58
+ </div>
59
+ );
60
+ };
61
+
62
+ /**
63
+ * Parses a Vite `/@fs/` URL into a `vscode://` deep link and short filename.
64
+ * Returns undefined if the URL cannot be resolved to a local file.
65
+ */
66
+ const parseLocalFrame = (fileUrl: string, line?: number, col?: number): LocalFrame | undefined => {
67
+ try {
68
+ const { pathname } = new URL(fileUrl);
69
+ if (!pathname.startsWith('/@fs/')) {
70
+ return undefined;
71
+ }
72
+ const localPath = pathname.slice(4); // /@fs/Users/... → /Users/...
73
+ return {
74
+ href: `vscode://file/${localPath}:${line ?? 1}:${col ?? 1}`,
75
+ fileName: pathname.split('/').pop() ?? localPath,
76
+ };
77
+ } catch {
78
+ return undefined;
79
+ }
80
+ };
@@ -0,0 +1,37 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { useEffect, useState } from 'react';
6
+
7
+ export type ThrowErrorProps = {
8
+ error?: () => Error;
9
+ delay?: number;
10
+ };
11
+
12
+ /**
13
+ * Use this to debug the error boundary.
14
+ */
15
+ export const ThrowError = ({ delay = 1_000, ...props }: ThrowErrorProps) => {
16
+ const [error, setError] = useState<Error>();
17
+ useEffect(() => {
18
+ if (delay < 0) {
19
+ return;
20
+ }
21
+
22
+ const t = setTimeout(() => {
23
+ setError(generator({ delay, ...props }));
24
+ }, delay);
25
+ return () => clearTimeout(t);
26
+ }, [delay, generator]);
27
+
28
+ if (error) {
29
+ throw error;
30
+ }
31
+
32
+ return null;
33
+ };
34
+
35
+ const generator = ({ error, delay }: ThrowErrorProps) => {
36
+ return error?.() ?? new Error(`Error generated after ${delay}ms`);
37
+ };
@@ -0,0 +1,9 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export { ErrorBoundary, type ErrorBoundaryProps, type FallbackProps } from '@dxos/react-error-boundary';
6
+
7
+ export * from './ErrorFallback';
8
+ export * from './ErrorStack';
9
+ export * from './ThrowError';
@@ -0,0 +1,113 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { IconBase, type IconProps, type IconWeight } from '@phosphor-icons/react';
6
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
7
+ import React, { type FC, type ReactElement, type SVGProps, forwardRef } from 'react';
8
+
9
+ import { getSize, mx } from '@dxos/ui-theme';
10
+
11
+ import { withTheme } from '../../testing';
12
+
13
+ import { Icon } from './Icon';
14
+
15
+ /**
16
+ * Create icon from serializable data.
17
+ * https://github.com/phosphor-icons/react#custom-icons
18
+ * https://github.com/phosphor-icons/core/tree/main/assets
19
+ */
20
+ const createIcon = ({
21
+ name,
22
+ weights,
23
+ }: {
24
+ name: string;
25
+ weights: Record<string, SVGProps<SVGPathElement>[]>;
26
+ }): FC<IconProps> => {
27
+ const CustomIcon = forwardRef<SVGSVGElement, IconProps>((props, ref) => (
28
+ <IconBase
29
+ ref={ref}
30
+ {...props}
31
+ weights={
32
+ new Map<IconWeight, ReactElement>(
33
+ Object.entries(weights).map(
34
+ ([key, paths]) =>
35
+ [
36
+ key,
37
+ <>
38
+ {paths.map((props, i) => (
39
+ <path key={`${key}-${i}`} {...props} />
40
+ ))}
41
+ </>,
42
+ ] as [IconWeight, ReactElement],
43
+ ),
44
+ )
45
+ }
46
+ />
47
+ ));
48
+
49
+ CustomIcon.displayName = name;
50
+ return CustomIcon;
51
+ };
52
+
53
+ const DefaultStory = ({ CustomIcon }: { CustomIcon: FC<IconProps> }) => {
54
+ return (
55
+ <div className='grid grid-cols-2 gap-8'>
56
+ <CustomIcon weight={'regular'} className={mx(getSize(16))} />
57
+ <Icon icon='ph--github-logo--regular' classNames={mx(getSize(16))} />
58
+ </div>
59
+ );
60
+ };
61
+
62
+ const meta = {
63
+ title: 'ui/react-ui-core/components/Icon',
64
+ render: DefaultStory,
65
+ decorators: [withTheme()],
66
+ parameters: {
67
+ layout: 'centered',
68
+ },
69
+ } satisfies Meta<typeof DefaultStory>;
70
+
71
+ export default meta;
72
+
73
+ type Story = StoryObj<typeof meta>;
74
+
75
+ export const Default: Story = {
76
+ args: {
77
+ CustomIcon: createIcon({
78
+ name: 'GithubLogo',
79
+ weights: {
80
+ // https://github.com/phosphor-icons/core/tree/main/assets
81
+ // <path d="M119.83,56A52,52,0,0,0,76,32a51.92,51.92,0,0,0-3.49,44.7A49.28,49.28,0,0,0,64,104v8a48,48,0,0,0,48,48h48a48,48,0,0,0,48-48v-8a49.28,49.28,0,0,0-8.51-27.3A51.92,51.92,0,0,0,196,32a52,52,0,0,0-43.83,24Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
82
+ // <path d="M104,232V192a32,32,0,0,1,32-32h0a32,32,0,0,1,32,32v40" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
83
+ // <path d="M104,208H72a32,32,0,0,1-32-32A32,32,0,0,0,8,144" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
84
+ regular: [
85
+ {
86
+ d: 'M119.83,56A52,52,0,0,0,76,32a51.92,51.92,0,0,0-3.49,44.7A49.28,49.28,0,0,0,64,104v8a48,48,0,0,0,48,48h48a48,48,0,0,0,48-48v-8a49.28,49.28,0,0,0-8.51-27.3A51.92,51.92,0,0,0,196,32a52,52,0,0,0-43.83,24Z',
87
+ fill: 'none',
88
+ stroke: 'currentColor',
89
+ strokeLinecap: 'round',
90
+ strokeLinejoin: 'round',
91
+ strokeWidth: '16',
92
+ },
93
+ {
94
+ d: 'M104,232V192a32,32,0,0,1,32-32h0a32,32,0,0,1,32,32v40',
95
+ fill: 'none',
96
+ stroke: 'currentColor',
97
+ strokeLinecap: 'round',
98
+ strokeLinejoin: 'round',
99
+ strokeWidth: '16',
100
+ },
101
+ {
102
+ d: 'M104,208H72a32,32,0,0,1-32-32A32,32,0,0,0,8,144',
103
+ fill: 'none',
104
+ stroke: 'currentColor',
105
+ strokeLinecap: 'round',
106
+ strokeLinejoin: 'round',
107
+ strokeWidth: '16',
108
+ },
109
+ ],
110
+ },
111
+ }),
112
+ },
113
+ };
@@ -5,7 +5,7 @@
5
5
  import { type Primitive } from '@radix-ui/react-primitive';
6
6
  import React, { type ComponentPropsWithRef, forwardRef, memo } from 'react';
7
7
 
8
- import { type Size } from '@dxos/react-ui-types';
8
+ import { type Size } from '@dxos/ui-types';
9
9
 
10
10
  import { useIconHref, useThemeContext } from '../../hooks';
11
11
  import { type ThemedClassName } from '../../util';
@@ -16,11 +16,11 @@ export type IconProps = ThemedClassName<ComponentPropsWithRef<typeof Primitive.s
16
16
  };
17
17
 
18
18
  export const Icon = memo(
19
- forwardRef<SVGSVGElement, IconProps>(({ icon, classNames, size, ...props }, forwardedRef) => {
19
+ forwardRef<SVGSVGElement, IconProps>(({ icon, classNames, size = 4, ...props }, forwardedRef) => {
20
20
  const { tx } = useThemeContext();
21
21
  const href = useIconHref(icon);
22
22
  return (
23
- <svg {...props} className={tx('icon.root', 'icon', { size }, classNames)} ref={forwardedRef}>
23
+ <svg {...props} className={tx('icon.root', { size }, classNames)} ref={forwardedRef}>
24
24
  <use href={href} />
25
25
  </svg>
26
26
  );
@@ -0,0 +1,86 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React, { useMemo } from 'react';
7
+
8
+ import { faker } from '@dxos/random';
9
+ import { withTheme } from '@dxos/react-ui/testing';
10
+
11
+ import { Image } from './Image';
12
+
13
+ const seed = Math.random();
14
+
15
+ faker.seed(seed);
16
+
17
+ const meta = {
18
+ title: 'ui/react-ui-mosaic/Image',
19
+ component: Image,
20
+ render: (args) => (
21
+ <div className='absolute inset-0 flex justify-center items-center'>
22
+ <Image {...args} />
23
+ </div>
24
+ ),
25
+ decorators: [withTheme()],
26
+ parameters: {
27
+ layout: 'centered',
28
+ },
29
+ } satisfies Meta<typeof Image>;
30
+
31
+ export default meta;
32
+
33
+ type Story = StoryObj<typeof meta>;
34
+
35
+ export const Default: Story = {
36
+ args: {
37
+ src: faker.image.url(),
38
+ },
39
+ };
40
+
41
+ const classNames = 'h-[12rem] w-[18rem]';
42
+
43
+ /**
44
+ * Access to image at 'https://dxos.network/dxos-logotype-blue.png'
45
+ * from origin 'http://localhost:9009' has been blocked by CORS policy:
46
+ * No 'Access-Control-Allow-Origin' header is present on the requested resource.
47
+ */
48
+ export const Cors: Story = {
49
+ args: {
50
+ src: 'https://dxos.network/dxos-logotype-blue.png',
51
+ classNames,
52
+ },
53
+ };
54
+
55
+ export const Corners: Story = {
56
+ args: {
57
+ src: 'https://picsum.photos/seed/picsum/200/200',
58
+ classNames,
59
+ },
60
+ };
61
+
62
+ export const SVG: Story = {
63
+ args: {
64
+ src: 'https://dxos.network/bg-kube.svg',
65
+ classNames,
66
+ },
67
+ };
68
+
69
+ export const Many: Story = {
70
+ args: {
71
+ src: 'https://dxos.network/bg-kube.svg',
72
+ },
73
+ render: () => {
74
+ const images = useMemo(
75
+ () => Array.from({ length: 9 }, (_, i) => `https://picsum.photos/seed/${seed + i}/500/500`),
76
+ [],
77
+ );
78
+ return (
79
+ <div className='w-[60rem] grid grid-cols-3 grid-rows-3 gap-8'>
80
+ {images.map((src, i) => (
81
+ <Image key={i} src={src} classNames={classNames} />
82
+ ))}
83
+ </div>
84
+ );
85
+ },
86
+ };
@@ -0,0 +1,223 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { type SyntheticEvent, useCallback, useRef, useState } from 'react';
6
+
7
+ import { type ThemedClassName } from '@dxos/react-ui';
8
+ import { mx } from '@dxos/ui-theme';
9
+
10
+ const cache = new Map<string, string>();
11
+
12
+ export type ImageProps = ThemedClassName<
13
+ {
14
+ src: string;
15
+ alt?: string;
16
+ crossOrigin?: 'anonymous' | 'use-credentials' | '';
17
+ } & ColorOptions
18
+ >;
19
+
20
+ // TODO(burdon): Option for neutral background color.
21
+ export const Image = ({
22
+ classNames,
23
+ src,
24
+ alt = '',
25
+ crossOrigin = 'anonymous',
26
+ sampleSize = 64,
27
+ contrast = 0.9,
28
+ }: ImageProps) => {
29
+ const [crossOriginState, setCrossOriginState] = useState<ImageProps['crossOrigin']>(crossOrigin);
30
+ const [dominantColor, setDominantColor] = useState<string | undefined>(undefined);
31
+ const [imageLoaded, setImageLoaded] = useState<boolean>(false);
32
+ const canvasRef = useRef<HTMLCanvasElement>(null);
33
+
34
+ // CORS not supported by server.
35
+ const handleImageError = (): void => {
36
+ setCrossOriginState(undefined);
37
+ };
38
+
39
+ const handleImageLoad = useCallback(
40
+ ({ target }: SyntheticEvent<HTMLImageElement>): void => {
41
+ const rgb = cache.get(src);
42
+ if (rgb) {
43
+ setDominantColor(rgb);
44
+ setImageLoaded(true);
45
+ return;
46
+ }
47
+
48
+ const img = target as HTMLImageElement;
49
+ if (!canvasRef.current) {
50
+ return;
51
+ }
52
+
53
+ try {
54
+ const color = extractDominantColor(canvasRef.current, img, { sampleSize, contrast });
55
+ if (color) {
56
+ const rgb = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
57
+ cache.set(src, rgb);
58
+ setDominantColor(rgb);
59
+ }
60
+ } catch {
61
+ setCrossOriginState(undefined);
62
+ }
63
+
64
+ setImageLoaded(true);
65
+ },
66
+ [sampleSize, contrast, src],
67
+ );
68
+
69
+ return (
70
+ <div
71
+ className={mx(`relative flex w-full justify-center overflow-hidden transition-all duration-700`, classNames)}
72
+ style={{
73
+ backgroundColor: dominantColor,
74
+ }}
75
+ >
76
+ {/* Hidden canvas for color extraction. */}
77
+ <canvas ref={canvasRef} style={{ display: 'none' }} aria-hidden='true' />
78
+
79
+ {/* Background gradient overlay for smooth transition. */}
80
+ <div
81
+ className='absolute inset-0 pointer-events-none'
82
+ style={{
83
+ background: dominantColor
84
+ ? `radial-gradient(circle at center, transparent 30%, ${dominantColor} 100%)`
85
+ : undefined,
86
+ transition: 'opacity 0.7s ease-in-out',
87
+ opacity: 0.5,
88
+ }}
89
+ />
90
+
91
+ <img
92
+ src={src}
93
+ alt={alt}
94
+ crossOrigin={crossOriginState}
95
+ onError={handleImageError}
96
+ onLoad={handleImageLoad}
97
+ className='z-10 object-contain transition-opacity duration-500'
98
+ style={{
99
+ opacity: imageLoaded ? 1 : 0,
100
+ }}
101
+ />
102
+ </div>
103
+ );
104
+ };
105
+
106
+ type ColorOptions = {
107
+ sampleSize?: number;
108
+ contrast?: number;
109
+ };
110
+
111
+ /**
112
+ * Get dominant color from image (esp. from corners).
113
+ */
114
+ const extractDominantColor = (
115
+ canvas: HTMLCanvasElement,
116
+ img: HTMLImageElement,
117
+ { sampleSize = 64, contrast = 0.95 }: ColorOptions,
118
+ ): [number, number, number] | null => {
119
+ const ctx = canvas.getContext('2d');
120
+ if (!ctx) {
121
+ return null;
122
+ }
123
+
124
+ // Draw the image scaled down.
125
+ canvas.width = sampleSize;
126
+ canvas.height = sampleSize;
127
+ ctx.drawImage(img, 0, 0, sampleSize, sampleSize);
128
+
129
+ // Get image data.
130
+ const imageData = ctx.getImageData(0, 0, sampleSize, sampleSize);
131
+ const pixels = imageData.data;
132
+
133
+ // Check for transparent background.
134
+ if (isTransparent(pixels, sampleSize)) {
135
+ return null;
136
+ }
137
+
138
+ let r = 0;
139
+ let g = 0;
140
+ let b = 0;
141
+ let totalWeight = 0;
142
+
143
+ // Define corner sampling areas (e.g., 25% of each dimension from each corner).
144
+ const cornerSize = Math.floor(sampleSize * 0.125);
145
+
146
+ // Sample only pixels in corner areas.
147
+ for (let y = 0; y < sampleSize; y++) {
148
+ for (let x = 0; x < sampleSize; x++) {
149
+ // Check if pixel is in any corner area.
150
+ const isInTopLeft = x < cornerSize && y < cornerSize;
151
+ const isInTopRight = x >= sampleSize - cornerSize && y < cornerSize;
152
+ const isInBottomLeft = x < cornerSize && y >= sampleSize - cornerSize;
153
+ const isInBottomRight = x >= sampleSize - cornerSize && y >= sampleSize - cornerSize;
154
+ if (!isInTopLeft && !isInTopRight && !isInBottomLeft && !isInBottomRight) {
155
+ continue; // Skip pixels not in corner areas.
156
+ }
157
+
158
+ const i = (y * sampleSize + x) * 4;
159
+ const red = pixels[i];
160
+ const green = pixels[i + 1];
161
+ const blue = pixels[i + 2];
162
+ const alpha = pixels[i + 3];
163
+
164
+ // Skip transparent pixels.
165
+ if (alpha === 0) continue;
166
+
167
+ // Calculate saturation to weight vibrant colors more.
168
+ const max = Math.max(red, green, blue);
169
+ const min = Math.min(red, green, blue);
170
+ const saturation = max === 0 ? 0 : (max - min) / max;
171
+ const weight = 1 + saturation * 2;
172
+
173
+ r += red * weight;
174
+ g += green * weight;
175
+ b += blue * weight;
176
+ totalWeight += weight;
177
+ }
178
+ }
179
+
180
+ if (totalWeight > 0) {
181
+ // Slightly darken the color for better contrast.
182
+ r = Math.round(Math.round(r / totalWeight) * contrast);
183
+ g = Math.round(Math.round(g / totalWeight) * contrast);
184
+ b = Math.round(Math.round(b / totalWeight) * contrast);
185
+ return [r, g, b];
186
+ }
187
+
188
+ return null;
189
+ };
190
+
191
+ /**
192
+ * Detects if an image has a transparent background by examining edge pixels.
193
+ * @param pixels - Image pixel data from canvas
194
+ * @param sampleSize - Size of the sampled image
195
+ * @param threshold - Percentage threshold for considering background transparent (default: 0.5)
196
+ * @returns True if the image has a transparent background
197
+ */
198
+ const isTransparent = (pixels: Uint8ClampedArray, sampleSize: number, threshold: number = 0.5): boolean => {
199
+ let edgeTransparentPixels = 0;
200
+ const edgePixels = sampleSize * 4 - 4; // Perimeter minus corners counted twice.
201
+
202
+ for (let x = 0; x < sampleSize; x++) {
203
+ // Top edge.
204
+ const topIndex = x * 4;
205
+ if (pixels[topIndex + 3] === 0) edgeTransparentPixels++;
206
+
207
+ // Bottom edge.
208
+ const bottomIndex = ((sampleSize - 1) * sampleSize + x) * 4;
209
+ if (pixels[bottomIndex + 3] === 0) edgeTransparentPixels++;
210
+ }
211
+
212
+ for (let y = 1; y < sampleSize - 1; y++) {
213
+ // Left edge.
214
+ const leftIndex = y * sampleSize * 4;
215
+ if (pixels[leftIndex + 3] === 0) edgeTransparentPixels++;
216
+
217
+ // Right edge.
218
+ const rightIndex = (y * sampleSize + sampleSize - 1) * 4;
219
+ if (pixels[rightIndex + 3] === 0) edgeTransparentPixels++;
220
+ }
221
+
222
+ return edgeTransparentPixels / edgePixels > threshold;
223
+ };