diff --git a/ui/src/components/stickies/StickyNoteCard.tsx b/ui/src/components/stickies/StickyNoteCard.tsx index 7b9de0ed..12367cdf 100644 --- a/ui/src/components/stickies/StickyNoteCard.tsx +++ b/ui/src/components/stickies/StickyNoteCard.tsx @@ -5,6 +5,7 @@ import { TaskItem } from '@tiptap/extension-task-item'; import { TaskList } from '@tiptap/extension-task-list'; import StarterKit from '@tiptap/starter-kit'; import type { StickyApiResponse } from '../../api/types'; +import { Tooltip } from '../ui/Tooltip'; import { stickiesService } from '../../services/stickiesService'; import { STICKY_BACKGROUND_COLORS_DARK, @@ -314,6 +315,12 @@ export function StickyNoteCard({ const tb = 'rounded p-1 text-(--txt-icon-tertiary) hover:bg-(--bg-layer-transparent-hover) disabled:opacity-40'; + const isMac = + typeof navigator !== 'undefined' && /mac|iphone|ipad|ipod/i.test(navigator.platform); + const modKey = isMac ? 'Cmd' : 'Ctrl'; + const boldShortcut = `${modKey} + B`; + const italicShortcut = `${modKey} + I`; + const todoShortcut = `${modKey} + Shift + 9`; if (!editor) return null; if (!safeSlug) return null; @@ -326,7 +333,7 @@ export function StickyNoteCard({
{contentSaveError ? ( @@ -337,17 +344,19 @@ export function StickyNoteCard({
- + + + {colorOpen && (
)}
- -
+ } > - - - + + +

Italic

+

{italicShortcut}

+
+ } > - - - + + +

To-do list

+

{todoShortcut}

+ + } > - - + +
+
+ + + +
); diff --git a/ui/src/components/ui/Tooltip.tsx b/ui/src/components/ui/Tooltip.tsx index 4a99ff80..70bcdeb6 100644 --- a/ui/src/components/ui/Tooltip.tsx +++ b/ui/src/components/ui/Tooltip.tsx @@ -43,6 +43,23 @@ function computeStyle(el: HTMLElement, placement: TooltipPlacement): CSSProperti }; } +function mergeDescribedBy(existing: string | null, token: string): string { + const parts = (existing || '') + .split(/\s+/) + .map((s) => s.trim()) + .filter(Boolean); + if (!parts.includes(token)) parts.push(token); + return parts.join(' '); +} + +function removeDescribedBy(existing: string | null, token: string): string | null { + const next = (existing || '') + .split(/\s+/) + .map((s) => s.trim()) + .filter((s) => s && s !== token); + return next.length ? next.join(' ') : null; +} + export function Tooltip({ content, children, @@ -90,7 +107,7 @@ export function Tooltip({ }, [clearDelay]); const hideIfLeavingTrigger = useCallback( - (e: FocusEvent) => { + (e: FocusEvent) => { const next = e.relatedTarget as Node | null; if (next && e.currentTarget.contains(next)) return; hide(); @@ -112,6 +129,28 @@ export function Tooltip({ }; }, [open, updatePosition]); + useEffect(() => { + const root = triggerRef.current; + if (!root) return; + const focusable = root.querySelector( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])', + ); + if (!focusable) return; + + if (open) { + const next = mergeDescribedBy(focusable.getAttribute('aria-describedby'), id); + focusable.setAttribute('aria-describedby', next); + return; + } + + const next = removeDescribedBy(focusable.getAttribute('aria-describedby'), id); + if (next) { + focusable.setAttribute('aria-describedby', next); + } else { + focusable.removeAttribute('aria-describedby'); + } + }, [open, id]); + return ( <> div { + color: var(--txt-placeholder); +} + +.sticky-note-editor-content ul[data-type='taskList'] li > div, +.sticky-note-editor-content ul[data-type='taskList'] li > div > p { + transition: color 0.2s ease; +}