tldraw 3.16.0-canary.aa1aff3ffe55 → 3.16.0-canary.cf24aedcd577

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 (228) hide show
  1. package/dist-cjs/index.d.ts +129 -5
  2. package/dist-cjs/index.js +14 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/shapes/arrow/ArrowShapeUtil.js +3 -3
  5. package/dist-cjs/lib/shapes/arrow/ArrowShapeUtil.js.map +2 -2
  6. package/dist-cjs/lib/shapes/draw/DrawShapeUtil.js +3 -3
  7. package/dist-cjs/lib/shapes/draw/DrawShapeUtil.js.map +2 -2
  8. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js +12 -12
  9. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js.map +2 -2
  10. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js +2 -2
  11. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js.map +2 -2
  12. package/dist-cjs/lib/shapes/geo/components/GeoShapeBody.js +2 -1
  13. package/dist-cjs/lib/shapes/geo/components/GeoShapeBody.js.map +2 -2
  14. package/dist-cjs/lib/shapes/highlight/HighlightShapeUtil.js +5 -1
  15. package/dist-cjs/lib/shapes/highlight/HighlightShapeUtil.js.map +2 -2
  16. package/dist-cjs/lib/shapes/line/LineShapeUtil.js +5 -1
  17. package/dist-cjs/lib/shapes/line/LineShapeUtil.js.map +2 -2
  18. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js +4 -4
  19. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js.map +2 -2
  20. package/dist-cjs/lib/shapes/shared/ShapeFill.js +5 -5
  21. package/dist-cjs/lib/shapes/shared/ShapeFill.js.map +2 -2
  22. package/dist-cjs/lib/shapes/shared/usePrefersReducedMotion.js +10 -1
  23. package/dist-cjs/lib/shapes/shared/usePrefersReducedMotion.js.map +2 -2
  24. package/dist-cjs/lib/shapes/text/TextShapeUtil.js +2 -2
  25. package/dist-cjs/lib/shapes/text/TextShapeUtil.js.map +2 -2
  26. package/dist-cjs/lib/tools/SelectTool/childStates/Translating.js.map +2 -2
  27. package/dist-cjs/lib/ui/TldrawUi.js +14 -0
  28. package/dist-cjs/lib/ui/TldrawUi.js.map +3 -3
  29. package/dist-cjs/lib/ui/components/AccessibilityMenu.js +35 -0
  30. package/dist-cjs/lib/ui/components/AccessibilityMenu.js.map +7 -0
  31. package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenu.js +12 -3
  32. package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenu.js.map +2 -2
  33. package/dist-cjs/lib/ui/components/DefaultMenuPanel.js +3 -2
  34. package/dist-cjs/lib/ui/components/DefaultMenuPanel.js.map +2 -2
  35. package/dist-cjs/lib/ui/components/MainMenu/DefaultMainMenuContent.js +3 -3
  36. package/dist-cjs/lib/ui/components/MainMenu/DefaultMainMenuContent.js.map +2 -2
  37. package/dist-cjs/lib/ui/components/MobileStylePanel.js +4 -2
  38. package/dist-cjs/lib/ui/components/MobileStylePanel.js.map +2 -2
  39. package/dist-cjs/lib/ui/components/NavigationPanel/DefaultNavigationPanel.js +1 -1
  40. package/dist-cjs/lib/ui/components/NavigationPanel/DefaultNavigationPanel.js.map +2 -2
  41. package/dist-cjs/lib/ui/components/PageMenu/DefaultPageMenu.js +2 -1
  42. package/dist-cjs/lib/ui/components/PageMenu/DefaultPageMenu.js.map +2 -2
  43. package/dist-cjs/lib/ui/components/SharePanel/PeopleMenuItem.js +3 -2
  44. package/dist-cjs/lib/ui/components/SharePanel/PeopleMenuItem.js.map +2 -2
  45. package/dist-cjs/lib/ui/components/SharePanel/UserPresenceColorPicker.js +2 -2
  46. package/dist-cjs/lib/ui/components/SharePanel/UserPresenceColorPicker.js.map +2 -2
  47. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js +2 -0
  48. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js.map +2 -2
  49. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js +171 -140
  50. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js.map +2 -2
  51. package/dist-cjs/lib/ui/components/StylePanel/DoubleDropdownPicker.js +3 -3
  52. package/dist-cjs/lib/ui/components/StylePanel/DoubleDropdownPicker.js.map +2 -2
  53. package/dist-cjs/lib/ui/components/StylePanel/DropdownPicker.js +26 -25
  54. package/dist-cjs/lib/ui/components/StylePanel/DropdownPicker.js.map +3 -3
  55. package/dist-cjs/lib/ui/components/Toolbar/DefaultToolbar.js +66 -21
  56. package/dist-cjs/lib/ui/components/Toolbar/DefaultToolbar.js.map +3 -3
  57. package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js +189 -80
  58. package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js.map +3 -3
  59. package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js +5 -4
  60. package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js.map +2 -2
  61. package/dist-cjs/lib/ui/components/menu-items.js +6 -0
  62. package/dist-cjs/lib/ui/components/menu-items.js.map +2 -2
  63. package/dist-cjs/lib/ui/components/primitives/TldrawUiButtonPicker.js +5 -16
  64. package/dist-cjs/lib/ui/components/primitives/TldrawUiButtonPicker.js.map +3 -3
  65. package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js +1 -1
  66. package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js.map +2 -2
  67. package/dist-cjs/lib/ui/components/primitives/TldrawUiPopover.js +3 -2
  68. package/dist-cjs/lib/ui/components/primitives/TldrawUiPopover.js.map +3 -3
  69. package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js +30 -7
  70. package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js.map +2 -2
  71. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +291 -0
  72. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +7 -0
  73. package/dist-cjs/lib/ui/components/primitives/layout.js +76 -0
  74. package/dist-cjs/lib/ui/components/primitives/layout.js.map +7 -0
  75. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuGroup.js +30 -7
  76. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuGroup.js.map +2 -2
  77. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +152 -2
  78. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
  79. package/dist-cjs/lib/ui/context/TldrawUiContextProvider.js +3 -2
  80. package/dist-cjs/lib/ui/context/TldrawUiContextProvider.js.map +2 -2
  81. package/dist-cjs/lib/ui/context/actions.js +15 -0
  82. package/dist-cjs/lib/ui/context/actions.js.map +2 -2
  83. package/dist-cjs/lib/ui/context/events.js.map +2 -2
  84. package/dist-cjs/lib/ui/hooks/useTools.js +76 -9
  85. package/dist-cjs/lib/ui/hooks/useTools.js.map +2 -2
  86. package/dist-cjs/lib/ui/hooks/useTranslation/TLUiTranslationKey.js.map +1 -1
  87. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js +3 -0
  88. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js.map +2 -2
  89. package/dist-cjs/lib/ui/version.js +3 -3
  90. package/dist-cjs/lib/ui/version.js.map +1 -1
  91. package/dist-esm/index.d.mts +129 -5
  92. package/dist-esm/index.mjs +25 -1
  93. package/dist-esm/index.mjs.map +2 -2
  94. package/dist-esm/lib/shapes/arrow/ArrowShapeUtil.mjs +4 -3
  95. package/dist-esm/lib/shapes/arrow/ArrowShapeUtil.mjs.map +2 -2
  96. package/dist-esm/lib/shapes/draw/DrawShapeUtil.mjs +4 -3
  97. package/dist-esm/lib/shapes/draw/DrawShapeUtil.mjs.map +2 -2
  98. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs +13 -12
  99. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs.map +2 -2
  100. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs +3 -2
  101. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs.map +2 -2
  102. package/dist-esm/lib/shapes/geo/components/GeoShapeBody.mjs +2 -1
  103. package/dist-esm/lib/shapes/geo/components/GeoShapeBody.mjs.map +2 -2
  104. package/dist-esm/lib/shapes/highlight/HighlightShapeUtil.mjs +6 -1
  105. package/dist-esm/lib/shapes/highlight/HighlightShapeUtil.mjs.map +2 -2
  106. package/dist-esm/lib/shapes/line/LineShapeUtil.mjs +6 -1
  107. package/dist-esm/lib/shapes/line/LineShapeUtil.mjs.map +2 -2
  108. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs +5 -4
  109. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs.map +2 -2
  110. package/dist-esm/lib/shapes/shared/ShapeFill.mjs +6 -5
  111. package/dist-esm/lib/shapes/shared/ShapeFill.mjs.map +2 -2
  112. package/dist-esm/lib/shapes/shared/usePrefersReducedMotion.mjs +10 -1
  113. package/dist-esm/lib/shapes/shared/usePrefersReducedMotion.mjs.map +2 -2
  114. package/dist-esm/lib/shapes/text/TextShapeUtil.mjs +3 -2
  115. package/dist-esm/lib/shapes/text/TextShapeUtil.mjs.map +2 -2
  116. package/dist-esm/lib/tools/SelectTool/childStates/Translating.mjs.map +2 -2
  117. package/dist-esm/lib/ui/TldrawUi.mjs +16 -2
  118. package/dist-esm/lib/ui/TldrawUi.mjs.map +3 -3
  119. package/dist-esm/lib/ui/components/AccessibilityMenu.mjs +19 -0
  120. package/dist-esm/lib/ui/components/AccessibilityMenu.mjs.map +7 -0
  121. package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenu.mjs +12 -3
  122. package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenu.mjs.map +2 -2
  123. package/dist-esm/lib/ui/components/DefaultMenuPanel.mjs +3 -2
  124. package/dist-esm/lib/ui/components/DefaultMenuPanel.mjs.map +2 -2
  125. package/dist-esm/lib/ui/components/MainMenu/DefaultMainMenuContent.mjs +3 -5
  126. package/dist-esm/lib/ui/components/MainMenu/DefaultMainMenuContent.mjs.map +2 -2
  127. package/dist-esm/lib/ui/components/MobileStylePanel.mjs +5 -2
  128. package/dist-esm/lib/ui/components/MobileStylePanel.mjs.map +2 -2
  129. package/dist-esm/lib/ui/components/NavigationPanel/DefaultNavigationPanel.mjs +1 -1
  130. package/dist-esm/lib/ui/components/NavigationPanel/DefaultNavigationPanel.mjs.map +2 -2
  131. package/dist-esm/lib/ui/components/PageMenu/DefaultPageMenu.mjs +2 -1
  132. package/dist-esm/lib/ui/components/PageMenu/DefaultPageMenu.mjs.map +2 -2
  133. package/dist-esm/lib/ui/components/SharePanel/PeopleMenuItem.mjs +3 -2
  134. package/dist-esm/lib/ui/components/SharePanel/PeopleMenuItem.mjs.map +2 -2
  135. package/dist-esm/lib/ui/components/SharePanel/UserPresenceColorPicker.mjs +2 -2
  136. package/dist-esm/lib/ui/components/SharePanel/UserPresenceColorPicker.mjs.map +2 -2
  137. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanel.mjs +3 -1
  138. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanel.mjs.map +2 -2
  139. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs +171 -140
  140. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs.map +2 -2
  141. package/dist-esm/lib/ui/components/StylePanel/DoubleDropdownPicker.mjs +3 -3
  142. package/dist-esm/lib/ui/components/StylePanel/DoubleDropdownPicker.mjs.map +2 -2
  143. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs +26 -25
  144. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs.map +2 -2
  145. package/dist-esm/lib/ui/components/Toolbar/DefaultToolbar.mjs +56 -21
  146. package/dist-esm/lib/ui/components/Toolbar/DefaultToolbar.mjs.map +2 -2
  147. package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs +192 -81
  148. package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs.map +3 -3
  149. package/dist-esm/lib/ui/components/Toolbar/ToggleToolLockedButton.mjs +5 -4
  150. package/dist-esm/lib/ui/components/Toolbar/ToggleToolLockedButton.mjs.map +2 -2
  151. package/dist-esm/lib/ui/components/menu-items.mjs +6 -0
  152. package/dist-esm/lib/ui/components/menu-items.mjs.map +2 -2
  153. package/dist-esm/lib/ui/components/primitives/TldrawUiButtonPicker.mjs +6 -6
  154. package/dist-esm/lib/ui/components/primitives/TldrawUiButtonPicker.mjs.map +2 -2
  155. package/dist-esm/lib/ui/components/primitives/TldrawUiContextualToolbar.mjs +1 -1
  156. package/dist-esm/lib/ui/components/primitives/TldrawUiContextualToolbar.mjs.map +2 -2
  157. package/dist-esm/lib/ui/components/primitives/TldrawUiPopover.mjs +3 -2
  158. package/dist-esm/lib/ui/components/primitives/TldrawUiPopover.mjs.map +2 -2
  159. package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs +30 -7
  160. package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs.map +2 -2
  161. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +261 -0
  162. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +7 -0
  163. package/dist-esm/lib/ui/components/primitives/layout.mjs +46 -0
  164. package/dist-esm/lib/ui/components/primitives/layout.mjs.map +7 -0
  165. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuGroup.mjs +30 -7
  166. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuGroup.mjs.map +2 -2
  167. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +160 -4
  168. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
  169. package/dist-esm/lib/ui/context/TldrawUiContextProvider.mjs +3 -2
  170. package/dist-esm/lib/ui/context/TldrawUiContextProvider.mjs.map +2 -2
  171. package/dist-esm/lib/ui/context/actions.mjs +15 -0
  172. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  173. package/dist-esm/lib/ui/context/events.mjs.map +2 -2
  174. package/dist-esm/lib/ui/hooks/useTools.mjs +83 -10
  175. package/dist-esm/lib/ui/hooks/useTools.mjs.map +2 -2
  176. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +3 -0
  177. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs.map +2 -2
  178. package/dist-esm/lib/ui/version.mjs +3 -3
  179. package/dist-esm/lib/ui/version.mjs.map +1 -1
  180. package/package.json +3 -3
  181. package/src/index.ts +20 -0
  182. package/src/lib/shapes/arrow/ArrowShapeUtil.tsx +4 -3
  183. package/src/lib/shapes/draw/DrawShapeUtil.tsx +4 -3
  184. package/src/lib/shapes/frame/FrameShapeUtil.tsx +13 -14
  185. package/src/lib/shapes/geo/GeoShapeUtil.tsx +3 -2
  186. package/src/lib/shapes/geo/components/GeoShapeBody.tsx +2 -2
  187. package/src/lib/shapes/highlight/HighlightShapeUtil.tsx +7 -1
  188. package/src/lib/shapes/line/LineShapeUtil.tsx +6 -1
  189. package/src/lib/shapes/note/NoteShapeUtil.tsx +9 -4
  190. package/src/lib/shapes/shared/ShapeFill.tsx +6 -5
  191. package/src/lib/shapes/shared/usePrefersReducedMotion.tsx +11 -1
  192. package/src/lib/shapes/text/TextShapeUtil.tsx +3 -2
  193. package/src/lib/tools/SelectTool/childStates/Translating.ts +0 -1
  194. package/src/lib/ui/TldrawUi.tsx +17 -2
  195. package/src/lib/ui/components/AccessibilityMenu.tsx +20 -0
  196. package/src/lib/ui/components/ActionsMenu/DefaultActionsMenu.tsx +15 -3
  197. package/src/lib/ui/components/DefaultMenuPanel.tsx +4 -3
  198. package/src/lib/ui/components/MainMenu/DefaultMainMenuContent.tsx +4 -4
  199. package/src/lib/ui/components/MobileStylePanel.tsx +8 -5
  200. package/src/lib/ui/components/NavigationPanel/DefaultNavigationPanel.tsx +1 -1
  201. package/src/lib/ui/components/PageMenu/DefaultPageMenu.tsx +3 -2
  202. package/src/lib/ui/components/SharePanel/PeopleMenuItem.tsx +4 -3
  203. package/src/lib/ui/components/SharePanel/UserPresenceColorPicker.tsx +3 -3
  204. package/src/lib/ui/components/StylePanel/DefaultStylePanel.tsx +3 -1
  205. package/src/lib/ui/components/StylePanel/DefaultStylePanelContent.tsx +146 -107
  206. package/src/lib/ui/components/StylePanel/DoubleDropdownPicker.tsx +3 -3
  207. package/src/lib/ui/components/StylePanel/DropdownPicker.tsx +7 -6
  208. package/src/lib/ui/components/Toolbar/DefaultToolbar.tsx +55 -23
  209. package/src/lib/ui/components/Toolbar/OverflowingToolbar.tsx +212 -61
  210. package/src/lib/ui/components/Toolbar/ToggleToolLockedButton.tsx +14 -11
  211. package/src/lib/ui/components/menu-items.tsx +8 -0
  212. package/src/lib/ui/components/primitives/TldrawUiButtonPicker.tsx +40 -37
  213. package/src/lib/ui/components/primitives/TldrawUiContextualToolbar.tsx +1 -1
  214. package/src/lib/ui/components/primitives/TldrawUiPopover.tsx +4 -2
  215. package/src/lib/ui/components/primitives/TldrawUiToolbar.tsx +51 -12
  216. package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +343 -0
  217. package/src/lib/ui/components/primitives/layout.tsx +107 -0
  218. package/src/lib/ui/components/primitives/menus/TldrawUiMenuGroup.tsx +30 -7
  219. package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +218 -3
  220. package/src/lib/ui/context/TldrawUiContextProvider.tsx +23 -20
  221. package/src/lib/ui/context/actions.tsx +15 -0
  222. package/src/lib/ui/context/events.tsx +2 -0
  223. package/src/lib/ui/hooks/useTools.tsx +118 -10
  224. package/src/lib/ui/hooks/useTranslation/TLUiTranslationKey.ts +3 -0
  225. package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +3 -0
  226. package/src/lib/ui/version.ts +3 -3
  227. package/src/lib/ui.css +199 -84
  228. package/tldraw.css +201 -84
@@ -1,6 +1,8 @@
1
1
  import classnames from 'classnames'
2
2
  import { Toolbar as _Toolbar } from 'radix-ui'
3
3
  import React from 'react'
4
+ import { TldrawUiColumn, TldrawUiGrid, TldrawUiRow } from './layout'
5
+ import { TldrawUiTooltip } from './TldrawUiTooltip'
4
6
 
5
7
  /** @public */
6
8
  export interface TLUiToolbarProps extends React.HTMLAttributes<HTMLDivElement> {
@@ -8,20 +10,42 @@ export interface TLUiToolbarProps extends React.HTMLAttributes<HTMLDivElement> {
8
10
  className?: string
9
11
  dir?: 'ltr' | 'rtl'
10
12
  label: string
13
+ orientation?: 'horizontal' | 'vertical' | 'grid'
14
+ tooltipSide?: 'top' | 'right' | 'bottom' | 'left'
15
+ }
16
+
17
+ const LayoutByOrientation = {
18
+ horizontal: TldrawUiRow,
19
+ vertical: TldrawUiColumn,
20
+ grid: TldrawUiGrid,
11
21
  }
12
22
 
13
23
  /** @public @react */
14
24
  export const TldrawUiToolbar = React.forwardRef<HTMLDivElement, TLUiToolbarProps>(
15
- ({ children, className, label, ...props }: TLUiToolbarProps, ref) => {
25
+ (
26
+ {
27
+ children,
28
+ className,
29
+ label,
30
+ orientation = 'horizontal',
31
+ tooltipSide,
32
+ ...props
33
+ }: TLUiToolbarProps,
34
+ ref
35
+ ) => {
36
+ const Layout = LayoutByOrientation[orientation]
16
37
  return (
17
- <_Toolbar.Root
18
- ref={ref}
19
- {...props}
20
- className={classnames('tlui-toolbar-container', className)}
21
- aria-label={label}
22
- >
23
- {children}
24
- </_Toolbar.Root>
38
+ <Layout asChild tooltipSide={tooltipSide}>
39
+ <_Toolbar.Root
40
+ ref={ref}
41
+ {...props}
42
+ className={classnames('tlui-toolbar', className)}
43
+ aria-label={label}
44
+ orientation={orientation === 'grid' ? 'horizontal' : orientation}
45
+ >
46
+ {children}
47
+ </_Toolbar.Root>
48
+ </Layout>
25
49
  )
26
50
  }
27
51
  )
@@ -34,23 +58,30 @@ export interface TLUiToolbarButtonProps extends React.HTMLAttributes<HTMLButtonE
34
58
  disabled?: boolean
35
59
  isActive?: boolean
36
60
  type: 'icon' | 'tool' | 'menu'
61
+ tooltip?: string
37
62
  }
38
63
 
39
64
  /** @public @react */
40
65
  export const TldrawUiToolbarButton = React.forwardRef<HTMLButtonElement, TLUiToolbarButtonProps>(
41
- ({ asChild, children, type, isActive, ...props }: TLUiToolbarButtonProps, ref) => {
42
- return (
66
+ ({ asChild, children, type, isActive, tooltip, ...props }: TLUiToolbarButtonProps, ref) => {
67
+ const button = (
43
68
  <_Toolbar.Button
44
69
  ref={ref}
45
70
  asChild={asChild}
46
71
  draggable={false}
47
72
  data-isactive={isActive}
48
73
  {...props}
74
+ // The tooltip takes care of this.
75
+ title={undefined}
49
76
  className={classnames('tlui-button', `tlui-button__${type}`, props.className)}
50
77
  >
51
78
  {children}
52
79
  </_Toolbar.Button>
53
80
  )
81
+
82
+ const tooltipContent = tooltip || props.title
83
+
84
+ return <TldrawUiTooltip content={tooltipContent}>{button}</TldrawUiTooltip>
54
85
  }
55
86
  )
56
87
 
@@ -93,6 +124,7 @@ export interface TLUiToolbarToggleItemProps extends React.HTMLAttributes<HTMLBut
93
124
  className?: string
94
125
  type: 'icon' | 'tool'
95
126
  value: string
127
+ tooltip?: string
96
128
  }
97
129
 
98
130
  /** @public @react */
@@ -101,11 +133,14 @@ export const TldrawUiToolbarToggleItem = ({
101
133
  className,
102
134
  type,
103
135
  value,
136
+ tooltip,
104
137
  ...props
105
138
  }: TLUiToolbarToggleItemProps) => {
106
- return (
139
+ const toggleItem = (
107
140
  <_Toolbar.ToggleItem
108
141
  {...props}
142
+ // The tooltip takes care of this.
143
+ title={undefined}
109
144
  className={classnames(
110
145
  'tlui-button',
111
146
  `tlui-button__${type}`,
@@ -117,4 +152,8 @@ export const TldrawUiToolbarToggleItem = ({
117
152
  {children}
118
153
  </_Toolbar.ToggleItem>
119
154
  )
155
+
156
+ const tooltipContent = tooltip || props.title
157
+
158
+ return <TldrawUiTooltip content={tooltipContent}>{toggleItem}</TldrawUiTooltip>
120
159
  }
@@ -0,0 +1,343 @@
1
+ import { assert, Editor, uniqueId, useMaybeEditor, Vec } from '@tldraw/editor'
2
+ import { Tooltip as _Tooltip } from 'radix-ui'
3
+ import React, { createContext, forwardRef, useContext, useEffect, useRef, useState } from 'react'
4
+ import { usePrefersReducedMotion } from '../../../shapes/shared/usePrefersReducedMotion'
5
+ import { useTldrawUiOrientation } from './layout'
6
+
7
+ const DEFAULT_TOOLTIP_DELAY_MS = 700
8
+
9
+ /** @public */
10
+ export interface TldrawUiTooltipProps {
11
+ children: React.ReactNode
12
+ content?: string | React.ReactNode
13
+ side?: 'top' | 'right' | 'bottom' | 'left'
14
+ sideOffset?: number
15
+ disabled?: boolean
16
+ }
17
+
18
+ // Singleton tooltip manager
19
+ class TooltipManager {
20
+ private static instance: TooltipManager | null = null
21
+ private currentTooltipId: string | null = null
22
+ private currentContent: string | React.ReactNode = ''
23
+ private currentSide: 'top' | 'right' | 'bottom' | 'left' = 'bottom'
24
+ private currentSideOffset: number = 5
25
+ private destroyTimeoutId: number | null = null
26
+ private subscribers: Set<() => void> = new Set()
27
+ private activeElement: HTMLElement | null = null
28
+ private editor: Editor | null = null
29
+
30
+ static getInstance(): TooltipManager {
31
+ if (!TooltipManager.instance) {
32
+ TooltipManager.instance = new TooltipManager()
33
+ }
34
+ return TooltipManager.instance
35
+ }
36
+
37
+ setEditor(editor: Editor | null) {
38
+ this.editor = editor
39
+ }
40
+
41
+ subscribe(callback: () => void): () => void {
42
+ this.subscribers.add(callback)
43
+ return () => this.subscribers.delete(callback)
44
+ }
45
+
46
+ private notify() {
47
+ this.subscribers.forEach((callback) => callback())
48
+ }
49
+
50
+ showTooltip(
51
+ tooltipId: string,
52
+ content: string | React.ReactNode,
53
+ element: HTMLElement,
54
+ side: 'top' | 'right' | 'bottom' | 'left' = 'bottom',
55
+ sideOffset: number = 5
56
+ ) {
57
+ // Clear any existing destroy timeout
58
+ if (this.destroyTimeoutId) {
59
+ clearTimeout(this.destroyTimeoutId)
60
+ this.destroyTimeoutId = null
61
+ }
62
+
63
+ // Update current tooltip
64
+ this.currentTooltipId = tooltipId
65
+ this.currentContent = content
66
+ this.currentSide = side
67
+ this.currentSideOffset = sideOffset
68
+ this.activeElement = element
69
+
70
+ this.notify()
71
+ }
72
+
73
+ hideTooltip(tooltipId: string, instant: boolean = false) {
74
+ const hide = () => {
75
+ // Only hide if this is the current tooltip
76
+ if (this.currentTooltipId === tooltipId) {
77
+ this.currentTooltipId = null
78
+ this.currentContent = ''
79
+ this.activeElement = null
80
+ this.destroyTimeoutId = null
81
+ this.notify()
82
+ }
83
+ }
84
+
85
+ if (instant) {
86
+ hide()
87
+ } else if (this.editor) {
88
+ // Start destroy timeout (1 second)
89
+ this.destroyTimeoutId = this.editor.timers.setTimeout(hide, 300)
90
+ }
91
+ }
92
+
93
+ hideAllTooltips() {
94
+ this.currentTooltipId = null
95
+ this.currentContent = ''
96
+ this.activeElement = null
97
+ this.destroyTimeoutId = null
98
+ this.notify()
99
+ }
100
+
101
+ getCurrentTooltipData() {
102
+ return {
103
+ id: this.currentTooltipId,
104
+ content: this.currentContent,
105
+ side: this.currentSide,
106
+ sideOffset: this.currentSideOffset,
107
+ element: this.activeElement,
108
+ }
109
+ }
110
+ }
111
+
112
+ export const tooltipManager = TooltipManager.getInstance()
113
+
114
+ // Context for the tooltip singleton
115
+ const TooltipSingletonContext = createContext<boolean>(false)
116
+
117
+ /** @public */
118
+ export interface TldrawUiTooltipProviderProps {
119
+ children: React.ReactNode
120
+ }
121
+
122
+ /** @public @react */
123
+ export function TldrawUiTooltipProvider({ children }: TldrawUiTooltipProviderProps) {
124
+ return (
125
+ <_Tooltip.Provider skipDelayDuration={700}>
126
+ <TooltipSingletonContext.Provider value={true}>
127
+ {children}
128
+ <TooltipSingleton />
129
+ </TooltipSingletonContext.Provider>
130
+ </_Tooltip.Provider>
131
+ )
132
+ }
133
+
134
+ // The singleton tooltip component that renders once
135
+ function TooltipSingleton() {
136
+ const editor = useMaybeEditor()
137
+ const [, forceUpdate] = useState({})
138
+ const [isOpen, setIsOpen] = useState(false)
139
+ const triggerRef = useRef<HTMLDivElement>(null)
140
+ const previousPositionRef = useRef<{ x: number; y: number } | null>(null)
141
+ const prefersReducedMotion = usePrefersReducedMotion()
142
+ const [shouldAnimate, setShouldAnimate] = useState(false)
143
+ const isFirstShowRef = useRef(true)
144
+ const showTimeoutRef = useRef<number | null>(null)
145
+
146
+ // Set editor in tooltip manager
147
+ useEffect(() => {
148
+ tooltipManager.setEditor(editor)
149
+ }, [editor])
150
+
151
+ // Subscribe to tooltip manager updates
152
+ useEffect(() => {
153
+ const unsubscribe = tooltipManager.subscribe(() => {
154
+ forceUpdate({})
155
+ })
156
+ return unsubscribe
157
+ }, [])
158
+
159
+ const tooltipData = tooltipManager.getCurrentTooltipData()
160
+
161
+ // Update open state and trigger position
162
+ useEffect(() => {
163
+ const shouldBeOpen = Boolean(tooltipData.id && tooltipData.element)
164
+
165
+ // Clear any existing show timeout
166
+ if (showTimeoutRef.current) {
167
+ clearTimeout(showTimeoutRef.current)
168
+ showTimeoutRef.current = null
169
+ }
170
+
171
+ if (shouldBeOpen && tooltipData.element && triggerRef.current) {
172
+ // Position the invisible trigger element over the active element
173
+ const activeRect = tooltipData.element.getBoundingClientRect()
174
+ const trigger = triggerRef.current
175
+
176
+ const newPosition = {
177
+ x: activeRect.left + activeRect.width / 2,
178
+ y: activeRect.top + activeRect.height / 2,
179
+ }
180
+
181
+ // Determine if we should animate
182
+ let shouldAnimateCheck = false
183
+ if (previousPositionRef.current) {
184
+ const isNearPrevious = Vec.DistMin(previousPositionRef.current, newPosition, 200)
185
+ // Only animate if the distance is less than 200px (nearby tooltips)
186
+ shouldAnimateCheck =
187
+ !prefersReducedMotion &&
188
+ isNearPrevious &&
189
+ Math.abs(newPosition.y - previousPositionRef.current.y) < 50
190
+ }
191
+ // Don't animate on initial show (previousPositionRef.current is null)
192
+
193
+ setShouldAnimate(isFirstShowRef.current ? false : shouldAnimateCheck)
194
+ previousPositionRef.current = newPosition
195
+
196
+ trigger.style.position = 'fixed'
197
+ trigger.style.left = `${activeRect.left}px`
198
+ trigger.style.top = `${activeRect.top}px`
199
+ trigger.style.width = `${activeRect.width}px`
200
+ trigger.style.height = `${activeRect.height}px`
201
+ trigger.style.pointerEvents = 'none'
202
+ trigger.style.zIndex = '9999'
203
+
204
+ // Handle delay for first show
205
+ if (isFirstShowRef.current && editor) {
206
+ showTimeoutRef.current = editor.timers.setTimeout(() => {
207
+ setIsOpen(true)
208
+ isFirstShowRef.current = false
209
+ }, editor.options.tooltipDelayMs)
210
+ } else {
211
+ // Subsequent tooltips show immediately
212
+ setIsOpen(true)
213
+ }
214
+ } else if (!shouldBeOpen) {
215
+ // Hide tooltip immediately
216
+ setIsOpen(false)
217
+ // Reset position tracking when tooltip closes
218
+ previousPositionRef.current = null
219
+ setShouldAnimate(false)
220
+ // Reset first show state after tooltip is hidden
221
+ isFirstShowRef.current = true
222
+ }
223
+ }, [tooltipData.id, tooltipData.element, editor, prefersReducedMotion])
224
+
225
+ if (!tooltipData.id) {
226
+ return null
227
+ }
228
+
229
+ return (
230
+ <_Tooltip.Root open={isOpen} delayDuration={0}>
231
+ <_Tooltip.Trigger asChild>
232
+ <div ref={triggerRef} />
233
+ </_Tooltip.Trigger>
234
+ <_Tooltip.Content
235
+ className="tlui-tooltip"
236
+ data-should-animate={shouldAnimate}
237
+ side={tooltipData.side}
238
+ sideOffset={tooltipData.sideOffset}
239
+ avoidCollisions
240
+ collisionPadding={8}
241
+ dir="ltr"
242
+ >
243
+ {tooltipData.content}
244
+ <_Tooltip.Arrow className="tlui-tooltip__arrow" />
245
+ </_Tooltip.Content>
246
+ </_Tooltip.Root>
247
+ )
248
+ }
249
+
250
+ /** @public @react */
251
+ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProps>(
252
+ ({ children, content, side, sideOffset = 5, disabled = false }, ref) => {
253
+ const editor = useMaybeEditor()
254
+ const tooltipId = useRef<string>(uniqueId())
255
+ const hasProvider = useContext(TooltipSingletonContext)
256
+
257
+ const orientationCtx = useTldrawUiOrientation()
258
+ const sideToUse = side ?? orientationCtx.tooltipSide
259
+
260
+ useEffect(() => {
261
+ const currentTooltipId = tooltipId.current
262
+ return () => {
263
+ if (hasProvider) {
264
+ tooltipManager.hideTooltip(currentTooltipId, true)
265
+ }
266
+ }
267
+ }, [hasProvider])
268
+
269
+ // Don't show tooltip if disabled, no content, or UI labels are disabled
270
+ if (disabled || !content) {
271
+ return <>{children}</>
272
+ }
273
+
274
+ // Fallback to old behavior if no provider
275
+ if (!hasProvider) {
276
+ return (
277
+ <_Tooltip.Root
278
+ delayDuration={editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS}
279
+ disableHoverableContent
280
+ >
281
+ <_Tooltip.Trigger asChild ref={ref}>
282
+ {children}
283
+ </_Tooltip.Trigger>
284
+ <_Tooltip.Content
285
+ className="tlui-tooltip"
286
+ side={sideToUse}
287
+ sideOffset={sideOffset}
288
+ avoidCollisions
289
+ collisionPadding={8}
290
+ dir="ltr"
291
+ >
292
+ {content}
293
+ <_Tooltip.Arrow className="tlui-tooltip__arrow" />
294
+ </_Tooltip.Content>
295
+ </_Tooltip.Root>
296
+ )
297
+ }
298
+
299
+ const child = React.Children.only(children)
300
+ assert(React.isValidElement(child), 'TldrawUiTooltip children must be a single element')
301
+
302
+ const handleMouseEnter = (event: React.MouseEvent<HTMLElement>) => {
303
+ child.props.onMouseEnter?.(event)
304
+ tooltipManager.showTooltip(
305
+ tooltipId.current,
306
+ content,
307
+ event.currentTarget as HTMLElement,
308
+ sideToUse,
309
+ sideOffset
310
+ )
311
+ }
312
+
313
+ const handleMouseLeave = (event: React.MouseEvent<HTMLElement>) => {
314
+ child.props.onMouseLeave?.(event)
315
+ tooltipManager.hideTooltip(tooltipId.current)
316
+ }
317
+
318
+ const handleFocus = (event: React.FocusEvent<HTMLElement>) => {
319
+ child.props.onFocus?.(event)
320
+ tooltipManager.showTooltip(
321
+ tooltipId.current,
322
+ content,
323
+ event.currentTarget as HTMLElement,
324
+ sideToUse,
325
+ sideOffset
326
+ )
327
+ }
328
+
329
+ const handleBlur = (event: React.FocusEvent<HTMLElement>) => {
330
+ child.props.onBlur?.(event)
331
+ tooltipManager.hideTooltip(tooltipId.current)
332
+ }
333
+
334
+ const childrenWithHandlers = React.cloneElement(children as React.ReactElement, {
335
+ onMouseEnter: handleMouseEnter,
336
+ onMouseLeave: handleMouseLeave,
337
+ onFocus: handleFocus,
338
+ onBlur: handleBlur,
339
+ })
340
+
341
+ return childrenWithHandlers
342
+ }
343
+ )
@@ -0,0 +1,107 @@
1
+ import classNames from 'classnames'
2
+ import { Slot } from 'radix-ui'
3
+ import { HTMLAttributes, ReactNode, createContext, forwardRef, useContext } from 'react'
4
+
5
+ /** @public */
6
+ export interface TldrawUiOrientationContext {
7
+ orientation: 'horizontal' | 'vertical'
8
+ tooltipSide: 'top' | 'right' | 'bottom' | 'left'
9
+ }
10
+
11
+ const TldrawUiOrientationContext = createContext<TldrawUiOrientationContext>({
12
+ orientation: 'horizontal',
13
+ tooltipSide: 'bottom',
14
+ })
15
+
16
+ /** @public */
17
+ export interface TldrawUiOrientationProviderProps {
18
+ children: ReactNode
19
+ orientation: 'horizontal' | 'vertical'
20
+ tooltipSide?: 'top' | 'right' | 'bottom' | 'left'
21
+ }
22
+ /** @public @react */
23
+ export function TldrawUiOrientationProvider({
24
+ children,
25
+ orientation,
26
+ tooltipSide,
27
+ }: TldrawUiOrientationProviderProps) {
28
+ const prevContext = useTldrawUiOrientation()
29
+ // generally, we want tooltip side to cascade down through the layout - apart from when the
30
+ // orientation changes. If the tooltip side is "bottom", and then I include some vertical layout
31
+ // elements, keeping the tooltip side as bottom will cause the tooltip to overlap elements
32
+ // stacked on top of each other. In the absence of a tooltip side, we pick a default side based
33
+ // on the orientation whenever the orientation changes.
34
+ const tooltipSideToUse =
35
+ tooltipSide ??
36
+ (orientation === prevContext.orientation
37
+ ? prevContext.tooltipSide
38
+ : orientation === 'horizontal'
39
+ ? 'bottom'
40
+ : 'right')
41
+
42
+ return (
43
+ <TldrawUiOrientationContext.Provider value={{ orientation, tooltipSide: tooltipSideToUse }}>
44
+ {children}
45
+ </TldrawUiOrientationContext.Provider>
46
+ )
47
+ }
48
+
49
+ /** @public */
50
+ export function useTldrawUiOrientation() {
51
+ return useContext(TldrawUiOrientationContext)
52
+ }
53
+
54
+ /** @public */
55
+ export interface TLUiLayoutProps extends HTMLAttributes<HTMLDivElement> {
56
+ children: ReactNode
57
+ tooltipSide?: 'top' | 'right' | 'bottom' | 'left'
58
+ asChild?: boolean
59
+ }
60
+
61
+ /**
62
+ * A row, usually of UI controls like buttons, select dropdown, checkboxes, etc.
63
+ *
64
+ * @public @react
65
+ */
66
+ export const TldrawUiRow = forwardRef<HTMLDivElement, TLUiLayoutProps>(
67
+ ({ asChild, className, tooltipSide, ...props }, ref) => {
68
+ const Component = asChild ? Slot.Root : 'div'
69
+ return (
70
+ <TldrawUiOrientationProvider orientation="horizontal" tooltipSide={tooltipSide}>
71
+ <Component ref={ref} className={classNames('tlui-row', className)} {...props} />
72
+ </TldrawUiOrientationProvider>
73
+ )
74
+ }
75
+ )
76
+
77
+ /**
78
+ * A column, usually of UI controls like buttons, select dropdown, checkboxes, etc.
79
+ *
80
+ * @public @react
81
+ */
82
+ export const TldrawUiColumn = forwardRef<HTMLDivElement, TLUiLayoutProps>(
83
+ ({ asChild, className, tooltipSide, ...props }, ref) => {
84
+ const Component = asChild ? Slot.Root : 'div'
85
+ return (
86
+ <TldrawUiOrientationProvider orientation="vertical" tooltipSide={tooltipSide}>
87
+ <Component ref={ref} className={classNames('tlui-column', className)} {...props} />
88
+ </TldrawUiOrientationProvider>
89
+ )
90
+ }
91
+ )
92
+
93
+ /**
94
+ * A tight grid 4 elements wide, usually of UI controls like buttons, select dropdown, checkboxes,
95
+ * etc.
96
+ *
97
+ * @public @react */
98
+ export const TldrawUiGrid = forwardRef<HTMLDivElement, TLUiLayoutProps>(
99
+ ({ asChild, className, tooltipSide, ...props }, ref) => {
100
+ const Component = asChild ? Slot.Root : 'div'
101
+ return (
102
+ <TldrawUiOrientationProvider orientation="horizontal" tooltipSide={tooltipSide}>
103
+ <Component ref={ref} className={classNames('tlui-grid', className)} {...props} />
104
+ </TldrawUiOrientationProvider>
105
+ )
106
+ }
107
+ )
@@ -3,6 +3,7 @@ import { ReactNode } from 'react'
3
3
  import { unwrapLabel } from '../../../context/actions'
4
4
  import { TLUiTranslationKey } from '../../../hooks/useTranslation/TLUiTranslationKey'
5
5
  import { useTranslation } from '../../../hooks/useTranslation/useTranslation'
6
+ import { TldrawUiColumn, TldrawUiGrid, TldrawUiRow, useTldrawUiOrientation } from '../layout'
6
7
  import { TldrawUiDropdownMenuGroup } from '../TldrawUiDropdownMenu'
7
8
  import { useTldrawUiMenuContext } from './TldrawUiMenuContext'
8
9
 
@@ -19,17 +20,18 @@ export interface TLUiMenuGroupProps<TranslationKey extends string = string> {
19
20
 
20
21
  /** @public @react */
21
22
  export function TldrawUiMenuGroup({ id, label, className, children }: TLUiMenuGroupProps) {
22
- const { type: menuType, sourceId } = useTldrawUiMenuContext()
23
+ const menu = useTldrawUiMenuContext()
24
+ const { orientation } = useTldrawUiOrientation()
23
25
  const msg = useTranslation()
24
- const labelToUse = unwrapLabel(label, menuType)
26
+ const labelToUse = unwrapLabel(label, menu.type)
25
27
  const labelStr = labelToUse ? msg(labelToUse as TLUiTranslationKey) : undefined
26
28
 
27
- switch (menuType) {
29
+ switch (menu.type) {
28
30
  case 'panel': {
29
31
  return (
30
32
  <div
31
33
  className={classNames('tlui-menu__group', className)}
32
- data-testid={`${sourceId}-group.${id}`}
34
+ data-testid={`${menu.sourceId}-group.${id}`}
33
35
  >
34
36
  {children}
35
37
  </div>
@@ -37,7 +39,10 @@ export function TldrawUiMenuGroup({ id, label, className, children }: TLUiMenuGr
37
39
  }
38
40
  case 'menu': {
39
41
  return (
40
- <TldrawUiDropdownMenuGroup className={className} data-testid={`${sourceId}-group.${id}`}>
42
+ <TldrawUiDropdownMenuGroup
43
+ className={className}
44
+ data-testid={`${menu.sourceId}-group.${id}`}
45
+ >
41
46
  {children}
42
47
  </TldrawUiDropdownMenuGroup>
43
48
  )
@@ -47,7 +52,7 @@ export function TldrawUiMenuGroup({ id, label, className, children }: TLUiMenuGr
47
52
  <div
48
53
  dir="ltr"
49
54
  className={classNames('tlui-menu__group', className)}
50
- data-testid={`${sourceId}-group.${id}`}
55
+ data-testid={`${menu.sourceId}-group.${id}`}
51
56
  >
52
57
  {children}
53
58
  </div>
@@ -56,12 +61,30 @@ export function TldrawUiMenuGroup({ id, label, className, children }: TLUiMenuGr
56
61
  case 'keyboard-shortcuts': {
57
62
  // todo: if groups need a label, let's give em a label
58
63
  return (
59
- <div className="tlui-shortcuts-dialog__group" data-testid={`${sourceId}-group.${id}`}>
64
+ <div className="tlui-shortcuts-dialog__group" data-testid={`${menu.sourceId}-group.${id}`}>
60
65
  <h2 className="tlui-shortcuts-dialog__group__title">{labelStr}</h2>
61
66
  <div className="tlui-shortcuts-dialog__group__content">{children}</div>
62
67
  </div>
63
68
  )
64
69
  }
70
+ case 'toolbar': {
71
+ const Layout = orientation === 'horizontal' ? TldrawUiRow : TldrawUiColumn
72
+ return (
73
+ <Layout className="tlui-main-toolbar__group" data-testid={`${menu.sourceId}-group.${id}`}>
74
+ {children}
75
+ </Layout>
76
+ )
77
+ }
78
+ case 'toolbar-overflow': {
79
+ return (
80
+ <TldrawUiGrid
81
+ className="tlui-main-toolbar__group"
82
+ data-testid={`${menu.sourceId}-group.${id}`}
83
+ >
84
+ {children}
85
+ </TldrawUiGrid>
86
+ )
87
+ }
65
88
  default: {
66
89
  return children
67
90
  }