Compare commits

..

5 Commits

Author SHA1 Message Date
e1fbd6070a Implement Keyboard Shortcuts and Tooltip Enhancements
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Added a new KeyboardKey component for consistent keyboard key styling across the application.
- Introduced KeyboardShortcut component to wrap buttons with keyboard shortcut hints, improving user accessibility.
- Enhanced Tooltip and CursorTooltip components to support a hideBorder prop, allowing for customizable tooltip appearances.
- Updated CSS styles for keyboard keys in light and dark modes, ensuring visual consistency and improved user experience.
2026-08-21 19:34:56 +01:00
59bf2702bf Enhance CSS Transitions for Improved User Experience
- Added transition properties to the body for smoother background changes.
- Increased transition duration for the dashboard splitter's size adjustments, enhancing visual fluidity during resizing.
2026-08-21 19:18:54 +01:00
fedc8ba5fa Enhance DashboardSidebar Item Mapping Logic
- Updated the mapItemsRecursively function to accept an additional parameter for controlling tooltip visibility on collapsed items, improving user experience.
- Adjusted the recursive mapping of children items to ensure tooltips are displayed correctly based on the new parameter, enhancing clarity in the sidebar's item representation.
2026-08-21 19:09:40 +01:00
a67a86f185 Add G-code Language Support for CodeMirror
- Introduced G-code command descriptions and a lookup function for tooltips in the new commands.js file.
- Implemented G-code language support in CodeMirror with syntax highlighting and tooltip functionality in the new index.js file.
- Updated codeBlockEditorUtils.js to include G-code language support, allowing for enhanced editing capabilities for G-code files.
2026-08-21 19:05:21 +01:00
9e88db5abe Refactor NotificationContext to Optimize Notification Fetching
- Introduced useRef hooks to store references for fetchNotifications, authenticated, and connected states, improving performance and reducing unnecessary re-renders.
- Updated useEffect hooks to utilize the refs for fetching notifications, enhancing clarity and responsiveness to state changes while maintaining functionality.
2026-08-21 11:37:57 +01:00
11 changed files with 432 additions and 67 deletions

View File

@ -357,8 +357,83 @@ code {
padding: 0 !important;
}
.ant-popover-inner:has(.keyboard-shortcut-tooltip) {
padding: 8px !important;
:root,
.light-mode,
html:has(.light-mode) {
--kbd-color: #363636;
--kbd-bg: #efefef;
--kbd-inset: #e8e8e8;
--kbd-edge: #c3c3c3;
--kbd-ridge: #c9c9c9;
--kbd-drop-shadow: #333333;
--kbd-text-shadow: #f6f6f6;
}
.dark-mode,
html:has(.dark-mode) {
--kbd-color: #ececec;
--kbd-bg: #3a3a3a;
--kbd-inset: #323232;
--kbd-edge: #1c1c1c;
--kbd-ridge: #141414;
--kbd-drop-shadow: #000000;
--kbd-text-shadow: #111111;
}
.keyboard-key {
display: inline-block;
color: var(--kbd-color);
text-decoration: none;
text-align: center;
background: var(--kbd-bg);
padding: 3px 7px;
margin: 5px 3px;
border-radius: 4px;
box-shadow:
inset 0 0 25px var(--kbd-inset),
0 1px 0 var(--kbd-edge),
0 2px 0 var(--kbd-ridge),
0 2px 3px var(--kbd-drop-shadow);
text-shadow: 0 1px 0 var(--kbd-text-shadow);
}
.keyboard-key-md {
padding: 4.5px 10.5px;
margin: 7.5px 3.5px;
font-size: 110%;
border-radius: 6px;
box-shadow:
inset 0 0 37.5px var(--kbd-inset),
0 1.5px 0 var(--kbd-edge),
0 3px 0 var(--kbd-ridge),
0 3px 4.5px var(--kbd-drop-shadow);
text-shadow: 0 1.5px 0 var(--kbd-text-shadow);
}
.keyboard-key-lg {
padding: 6px 14px;
margin: 10px 6px;
font-size: 200%;
border-radius: 8px;
box-shadow:
inset 0 0 50px var(--kbd-inset),
0 2px 0 var(--kbd-edge),
0 4px 0 var(--kbd-ridge),
0 4px 6px var(--kbd-drop-shadow);
text-shadow: 0 2px 0 var(--kbd-text-shadow);
}
.keyboard-key-xl {
padding: 9px 21px;
margin: 15px 9px;
font-size: 300%;
border-radius: 12px;
box-shadow:
inset 0 0 75px var(--kbd-inset),
0 3px 0 var(--kbd-edge),
0 6px 0 var(--kbd-ridge),
0 6px 9px var(--kbd-drop-shadow);
text-shadow: 0 3px 0 var(--kbd-text-shadow);
}
.cursor-tooltip {
@ -432,6 +507,9 @@ body {
min-width: 0 !important;
max-width: 100% !important;
flex: 0 0 100% !important;
transition:
all 0.3s,
background 0s;
}
.dashboard-sider.ant-layout-sider-collapsed {
@ -1022,11 +1100,11 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
.farmcontrol-splitter.ant-splitter-horizontal.is-size-transitioning
.ant-splitter-panel {
transition:
--dashboard-splitter-sidebar-size 0.15s ease-in-out,
flex-basis 0.15s ease-in-out,
width 0.15s ease-in-out,
min-width 0.15s ease-in-out,
max-width 0.15s ease-in-out;
--dashboard-splitter-sidebar-size 0.3s ease-in-out,
flex-basis 0.3s ease-in-out,
width 0.3s ease-in-out,
min-width 0.3s ease-in-out,
max-width 0.3s ease-in-out;
}
@property --dashboard-splitter-sidebar-size {

View File

@ -0,0 +1,102 @@
/**
* G-code command descriptions for hover tooltips.
* Adapted from gcode-lang-codemirror (MIT):
* https://github.com/oneislandearth/gcode-lang-codemirror
*/
export const GCodeCommands = {
G0: 'Rapid movement',
G1: 'Linear movement',
G2: 'Clockwise arc movement',
G3: 'Counter-clockwise arc movement',
G4: 'Dwell (wait P milliseconds or S seconds)',
G10: 'Retract (firmware retraction)',
G11: 'Recover (firmware retraction)',
G20: 'Set units to inches',
G21: 'Set units to millimeters',
G28: 'Home axes',
G29: 'Automatic bed leveling',
G80: 'Mesh bed leveling (Prusa) / cancel canned cycle',
G90: 'Use absolute positioning',
G91: 'Use relative positioning',
G92: 'Set current position',
M0: 'Stop after the buffer is empty',
M17: 'Enable stepper motors',
M18: 'Disable stepper motors',
M20: 'List SD card files',
M21: 'Initialize (mount) SD card',
M22: 'Release (unmount) SD card',
M23: 'Select SD file for printing',
M24: 'Start / resume SD print',
M25: 'Pause SD print',
M26: 'Set SD position in bytes',
M27: 'Report SD print status',
M28: 'Start writing to SD card',
M29: 'Stop writing to SD card',
M40: 'Eject part',
M42: 'Set pin state / stop if out of material',
M73: 'Set print progress',
M80: 'Turn ATX power on',
M81: 'Turn ATX power off',
M82: 'Extruder absolute mode',
M83: 'Extruder relative mode',
M84: 'Disable idle hold / steppers',
M92: 'Set steps per unit',
M104: 'Set extruder temperature',
M105: 'Get temperatures',
M106: 'Set fan speed',
M107: 'Turn fan off',
M109: 'Set extruder temperature and wait',
M110: 'Set current line number',
M112: 'Emergency stop',
M114: 'Get current position',
M115: 'Get firmware version and capabilities',
M117: 'Set LCD / status message',
M119: 'Get endstop status',
M140: 'Set bed temperature',
M141: 'Set chamber temperature',
M190: 'Set bed temperature and wait',
M191: 'Set chamber temperature and wait',
M220: 'Set feedrate percentage',
M221: 'Set flow percentage',
M300: 'Beep (S Hz for P ms)',
M600: 'Filament change',
M601: 'Pause print (Prusa)',
M862: 'G-code compatibility check (Prusa)',
M900: 'Linear / pressure advance (K)',
T0: 'Select tool / extruder 0',
T1: 'Select tool / extruder 1',
X: 'X axis',
Y: 'Y axis',
Z: 'Z axis',
E: 'Extruder axis',
A: 'A axis',
B: 'B axis',
C: 'C axis',
F: 'Feedrate',
S: 'Speed, temperature, or value',
P: 'Parameter / time',
I: 'Arc X offset / parameter',
J: 'Arc Y offset / parameter',
K: 'Arc Z offset / linear advance',
R: 'Arc radius / RPM'
}
export function lookupGCodeCommand(word) {
const upper = String(word || '').toUpperCase()
const commandMatch = upper.match(/^([GMT])0*(\d+)(\.\d+)?/)
if (commandMatch) {
const base = commandMatch[1] + String(parseInt(commandMatch[2], 10))
const canonical = base + (commandMatch[3] || '')
return (
GCodeCommands[canonical] ||
GCodeCommands[base] ||
GCodeCommands[upper] ||
null
)
}
const axis = upper.charAt(0)
if (axis && GCodeCommands[axis]) {
return GCodeCommands[axis]
}
return null
}

View File

@ -0,0 +1,109 @@
import {
StreamLanguage,
LanguageSupport
} from '@codemirror/language'
import { EditorView, hoverTooltip } from '@codemirror/view'
import { lookupGCodeCommand } from './commands.js'
/**
* G-code language support for CodeMirror 6.
* Highlighting and command tooltips adapted from gcode-lang-codemirror:
* https://github.com/oneislandearth/gcode-lang-codemirror
*
* Implemented as a StreamLanguage so typical 3D-printer G-code (no `%`
* wrappers, E axis, slicer `{placeholders}` / `[placeholders]`) highlights
* without the CodeMirror 0.19 dependencies of the original package.
*/
const wordChar = /[A-Za-z0-9.]/
function gcodeToken(stream) {
if (stream.eatSpace()) return null
if (stream.eat(';')) {
stream.skipToEnd()
return 'comment'
}
if (stream.eat('(')) {
if (!stream.skipTo(')')) stream.skipToEnd()
else stream.eat(')')
return 'comment'
}
if (stream.eat('{')) {
if (!stream.skipTo('}')) stream.skipToEnd()
else stream.eat('}')
return 'processingInstruction'
}
if (stream.eat('[')) {
if (!stream.skipTo(']')) stream.skipToEnd()
else stream.eat(']')
return 'processingInstruction'
}
if (stream.match(/^[GgMmTt]\d+(\.\d+)?/)) return 'keyword'
if (stream.match(/^[Nn]\d+/)) return 'meta'
if (stream.match(/^\*\d+/)) return 'meta'
if (stream.eat('%')) return 'meta'
if (stream.match(/^[A-Za-z]/)) return 'strong'
if (stream.match(/^[+-]?(\d+\.?\d*|\.\d+)/)) return 'number'
stream.next()
return null
}
export const gcodeLanguage = StreamLanguage.define({
name: 'gcode',
token: gcodeToken,
languageData: {
commentTokens: { line: ';', block: { open: '(', close: ')' } }
}
})
export const gcodeCommandTooltips = hoverTooltip((view, pos, side) => {
const line = view.state.doc.lineAt(pos)
let start = pos
let end = pos
const charAt = (offset) => line.text.charAt(offset - line.from)
while (start > line.from && wordChar.test(charAt(start - 1))) start--
while (end < line.to && wordChar.test(charAt(end))) end++
if ((start === pos && side < 0) || (end === pos && side > 0)) {
return null
}
const word = line.text.slice(start - line.from, end - line.from)
const description = lookupGCodeCommand(word)
if (!description) return null
return {
pos: start,
end,
above: true,
create() {
const dom = document.createElement('div')
dom.className = 'cm-tooltip-gcode'
dom.textContent = description
return { dom }
}
}
})
const gcodeTooltipTheme = EditorView.theme({
'.cm-tooltip-gcode': {
padding: '4px 8px',
fontSize: '12px'
}
})
export function gcodeLang() {
return new LanguageSupport(gcodeLanguage, [
gcodeCommandTooltips,
gcodeTooltipTheme
])
}
export { gcodeLang as GCodeLanguage }
export { GCodeCommands, lookupGCodeCommand } from './commands.js'

View File

@ -15,17 +15,19 @@ const getTooltipRoot = () => {
return root
}
const CursorTooltip = ({ content, visible, pointRef }) => {
const CursorTooltip = ({ content, visible, pointRef, hideBorder = false }) => {
const tooltipRef = useRef(null)
const sizeRef = useRef({ width: 0, height: 0 })
const frameRef = useRef(null)
const contentRef = useRef(content)
const lastContentRef = useRef(content)
const lastHideBorderRef = useRef(hideBorder)
const [root, setRoot] = useState(null)
contentRef.current = content
if (content != null && content !== false && content !== '') {
lastContentRef.current = content
lastHideBorderRef.current = hideBorder
}
const applyPosition = useCallback(() => {
@ -96,10 +98,28 @@ const CursorTooltip = ({ content, visible, pointRef }) => {
if (!root) return null
const tooltipContent = lastHideBorderRef.current ? (
lastContentRef.current
) : (
<Card
size='small'
styles={{ body: { padding: '4px 10px' } }}
style={{
maxWidth: 360,
borderRadius: 10,
backgroundColor: 'var(--layout-modal-bg)'
}}
>
{lastContentRef.current}
</Card>
)
return createPortal(
<div
ref={tooltipRef}
className={`cursor-tooltip${visible ? ' is-visible' : ''}`}
className={`cursor-tooltip${visible ? ' is-visible' : ''}${
lastHideBorderRef.current ? ' hide-border' : ''
}`}
style={{
position: 'fixed',
top: 0,
@ -109,17 +129,7 @@ const CursorTooltip = ({ content, visible, pointRef }) => {
willChange: 'transform, opacity'
}}
>
<Card
size='small'
styles={{ body: { padding: '4px 10px' } }}
style={{
maxWidth: 360,
borderRadius: 10,
backgroundColor: 'var(--layout-modal-bg)'
}}
>
{lastContentRef.current}
</Card>
{tooltipContent}
</div>,
root
)
@ -128,6 +138,7 @@ const CursorTooltip = ({ content, visible, pointRef }) => {
CursorTooltip.propTypes = {
content: PropTypes.node,
visible: PropTypes.bool,
hideBorder: PropTypes.bool,
pointRef: PropTypes.shape({
current: PropTypes.shape({
x: PropTypes.number,

View File

@ -19,6 +19,7 @@ import SimpleBar from 'simplebar-react'
import { useThemeContext } from '../context/ThemeContext'
import { filterSidebarItemsByListPermission } from '../../../database/Sidebars'
import Tooltip from './Tooltip'
import KeyboardShortcut from './KeyboardShortcut'
const { Sider } = Layout
const CollapsedItemIcon = ({ className, icon, title }) => {
@ -75,17 +76,19 @@ const DashboardSidebar = ({
}
// Recursive function to map items and their children
const mapItemsRecursively = (items) => {
const mapItemsRecursively = (items, showCollapsedTooltip = true) => {
return items.map((item) => {
if (item?.type === 'divider') {
return item
}
console.log(item)
const icon = item.icon || getSidebarIconNode(item.iconKey)
const mappedItem = {
key: item.key,
icon:
collapsed && !isMobile ? (
collapsed && !isMobile && showCollapsedTooltip && !item.children ? (
<CollapsedItemIcon title={item.label} icon={icon} />
) : (
icon
@ -100,7 +103,7 @@ const DashboardSidebar = ({
// Recursively map children if they exist
if (item?.children && Array.isArray(item.children)) {
mappedItem.children = mapItemsRecursively(item.children)
mappedItem.children = mapItemsRecursively(item.children, false)
}
return mappedItem
@ -156,13 +159,21 @@ const DashboardSidebar = ({
<Flex vertical style={{ width: '100%' }}>
<Divider style={{ margin: 0 }} />
<Flex style={{ padding: '4px', width: '100%' }}>
<Button
size={isElectron ? 'middle' : 'large'}
type='text'
icon={collapsed ? <ExpandSidebarIcon /> : <CollapseSidebarIcon />}
style={{ flexGrow: 1, width: '100%' }}
onClick={() => handleCollapse(!collapsed)}
/>
<KeyboardShortcut
shortcut='alt+shift+s'
hint='ALT ⇧ S'
onTrigger={() => handleCollapse(!collapsed)}
>
<Button
size={isElectron ? 'middle' : 'large'}
type='text'
icon={
collapsed ? <ExpandSidebarIcon /> : <CollapseSidebarIcon />
}
style={{ flexGrow: 1, width: '100%' }}
onClick={() => handleCollapse(!collapsed)}
/>
</KeyboardShortcut>
</Flex>
</Flex>
</Flex>

View File

@ -0,0 +1,29 @@
import PropTypes from 'prop-types'
const SIZE_CLASS = {
sm: '',
md: 'keyboard-key-md',
lg: 'keyboard-key-lg',
xl: 'keyboard-key-xl'
}
const KeyboardKey = ({ children, size = 'sm', className }) => {
const sizeClass = SIZE_CLASS[size] ?? ''
return (
<kbd
className={['keyboard-key', sizeClass, className]
.filter(Boolean)
.join(' ')}
>
{children}
</kbd>
)
}
KeyboardKey.propTypes = {
children: PropTypes.node.isRequired,
size: PropTypes.oneOf(['sm', 'md', 'lg', 'xl']),
className: PropTypes.string
}
export default KeyboardKey

View File

@ -1,8 +1,9 @@
import { useEffect, useRef, cloneElement, useMemo } from 'react'
import PropTypes from 'prop-types'
import { Popover, Typography } from 'antd'
import loglevel from 'loglevel'
import config from '../../../config'
import Tooltip from './Tooltip'
import KeyboardKey from './KeyboardKey'
const logger = loglevel.getLogger('ApiServerContext')
logger.setLevel(config.logLevel)
@ -42,8 +43,6 @@ function getPressedKey(event) {
return null
}
const { Text } = Typography
const KeyboardShortcut = ({
shortcut,
children,
@ -98,17 +97,15 @@ const KeyboardShortcut = ({
const element = cloneElement(children, { ref: childRef })
if (hint) {
var osSpecificHint = hint.replaceAll('ALT', '⌥')
return (
<Popover
content={
<Text keyboard className='keyboard-shortcut-tooltip'>
{hint}
</Text>
}
arrow={false}
<Tooltip
hideBorder
title={<KeyboardKey size='md'>{osSpecificHint}</KeyboardKey>}
>
{element}
</Popover>
</Tooltip>
)
}
return element

View File

@ -1,10 +1,4 @@
import {
cloneElement,
isValidElement,
useEffect,
useId,
useRef
} from 'react'
import { cloneElement, isValidElement, useEffect, useId, useRef } from 'react'
import PropTypes from 'prop-types'
import { useTooltipContext } from '../context/TooltipContext'
@ -13,7 +7,13 @@ const mergeHandler = (original, next) => (event) => {
original?.(event)
}
const Tooltip = ({ children, title, content, listenParents = 0 }) => {
const Tooltip = ({
children,
title,
content,
listenParents = 0,
hideBorder = false
}) => {
const { showTooltip, hideTooltip } = useTooltipContext()
const id = useId()
const tooltipContent = title ?? content
@ -21,7 +21,9 @@ const Tooltip = ({ children, title, content, listenParents = 0 }) => {
const hoveredTargetsRef = useRef(new Set())
const hoveringRef = useRef(false)
const tooltipContentRef = useRef(tooltipContent)
const hideBorderRef = useRef(hideBorder)
tooltipContentRef.current = tooltipContent
hideBorderRef.current = hideBorder
useEffect(() => {
return () => hideTooltip(id)
@ -29,14 +31,20 @@ const Tooltip = ({ children, title, content, listenParents = 0 }) => {
useEffect(() => {
if (hoveringRef.current) {
showTooltip(id, tooltipContent)
showTooltip(id, tooltipContent, undefined, undefined, hideBorder)
}
}, [id, showTooltip, tooltipContent])
}, [id, showTooltip, tooltipContent, hideBorder])
const onMouseEnter = (event) => {
hoveredTargetsRef.current.add(event.currentTarget)
hoveringRef.current = true
showTooltip(id, tooltipContentRef.current, event.clientX, event.clientY)
showTooltip(
id,
tooltipContentRef.current,
event.clientX,
event.clientY,
hideBorderRef.current
)
}
const onMouseLeave = (event) => {
@ -82,11 +90,7 @@ const Tooltip = ({ children, title, content, listenParents = 0 }) => {
}, [listenParents])
const wrapWithSpan = (node) => (
<span
ref={spanRef}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
<span ref={spanRef} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
{node}
</span>
)
@ -115,7 +119,8 @@ Tooltip.propTypes = {
children: PropTypes.node.isRequired,
title: PropTypes.node,
content: PropTypes.node,
listenParents: PropTypes.number
listenParents: PropTypes.number,
hideBorder: PropTypes.bool
}
export default Tooltip

View File

@ -13,6 +13,7 @@ import { php } from '@codemirror/lang-php'
import { yaml } from '@codemirror/lang-yaml'
import { xml } from '@codemirror/lang-xml'
import { fcTemplateLang } from '../../../codemirror/fcTemplateLang'
import { gcodeLang } from '../../../codemirror/gcodeLang'
export function toEditorCode(code, language = 'javascript') {
if (code == null) return ''
@ -67,6 +68,10 @@ export function getCodeLanguageExtension(
case 'yaml':
case 'yml':
return yaml()
case 'gcode':
case 'g-code':
case 'g':
return gcodeLang()
default:
return javascriptLang({ autoCompleteObject })
}

View File

@ -3,7 +3,8 @@ import {
useState,
useContext,
useCallback,
useEffect
useEffect,
useRef
} from 'react'
import { useLocation } from 'react-router-dom'
import { notification, Drawer } from 'antd'
@ -59,6 +60,13 @@ const NotificationProvider = ({ children }) => {
}
}, [authenticated, fetchNotificationsApi, showError])
const fetchNotificationsRef = useRef(fetchNotifications)
const authenticatedRef = useRef(authenticated)
const connectedRef = useRef(connected)
fetchNotificationsRef.current = fetchNotifications
authenticatedRef.current = authenticated
connectedRef.current = connected
const markNotificationAsRead = useCallback(
async (notificationId) => {
try {
@ -116,14 +124,15 @@ const NotificationProvider = ({ children }) => {
// Initial load / when we become authenticated and connected
useEffect(() => {
if (!authenticated || !connected) return
fetchNotifications()
}, [authenticated, connected, fetchNotifications])
fetchNotificationsRef.current()
}, [authenticated, connected])
// Refresh when the notification center opens
useEffect(() => {
if (!notificationCenterVisible || !authenticated || !connected) return
fetchNotifications()
}, [notificationCenterVisible, authenticated, connected, fetchNotifications])
if (!notificationCenterVisible) return
if (!authenticatedRef.current || !connectedRef.current) return
fetchNotificationsRef.current()
}, [notificationCenterVisible])
useEffect(() => {
setNotificationCenterVisible(false)

View File

@ -17,6 +17,7 @@ const FADE_OUT_MS = 250
export const TooltipProvider = ({ children }) => {
const [content, setContent] = useState(null)
const [visible, setVisible] = useState(false)
const [hideBorder, setHideBorder] = useState(false)
const stackRef = useRef([])
const hideTimerRef = useRef(null)
const fadeTimerRef = useRef(null)
@ -46,7 +47,7 @@ export const TooltipProvider = ({ children }) => {
}, [])
const showTooltip = useCallback(
(id, nextContent, x, y) => {
(id, nextContent, x, y, nextHideBorder = false) => {
if (nextContent == null || nextContent === false || nextContent === '') {
return
}
@ -57,11 +58,13 @@ export const TooltipProvider = ({ children }) => {
clearTimers()
const hideBorderValue = Boolean(nextHideBorder)
stackRef.current = [
...stackRef.current.filter((entry) => entry.id !== id),
{ id, content: nextContent }
{ id, content: nextContent, hideBorder: hideBorderValue }
]
setContent(nextContent)
setHideBorder(hideBorderValue)
if (!visibleRef.current) {
window.requestAnimationFrame(() => setVisible(true))
@ -81,6 +84,7 @@ export const TooltipProvider = ({ children }) => {
if (last) {
setContent(last.content)
setHideBorder(Boolean(last.hideBorder))
setVisible(true)
return
}
@ -100,7 +104,12 @@ export const TooltipProvider = ({ children }) => {
return (
<TooltipContext.Provider value={{ showTooltip, hideTooltip }}>
{children}
<CursorTooltip content={content} visible={visible} pointRef={pointRef} />
<CursorTooltip
content={content}
visible={visible}
pointRef={pointRef}
hideBorder={hideBorder}
/>
</TooltipContext.Provider>
)
}