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 && (
)}
-
-
+ }
>
-
-
-
+ }
>
-
-
- onDelete(sticky.id)}
- className={`${tb} ml-auto hover:text-(--txt-danger-primary)`}
- aria-label="Delete"
+ editor.chain().focus().toggleItalic().run()}
+ >
+
+
+
+
+ To-do list
+ {todoShortcut}
+
+ }
>
-
-
+ editor.chain().focus().toggleTaskList().run()}
+ >
+
+
+
+
+
+ onDelete(sticky.id)}
+ className={`${tb} hover:text-(--txt-danger-primary)`}
+ aria-label="Delete"
+ >
+
+
+
+
);
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;
+}