Skip to content

Commit dca95b2

Browse files
fix: align editor UI with design spec (#64) (#65)
- Callout emoji: text-base → text-lg (text-base not in typography scale) - Drag feedback: add scale(1.02) and shadow-lg per drag-and-drop spec - Drag handle: remove transition-opacity for instant hover per spec - Add regression tests for design spec compliance Co-authored-by: Ona <no-reply@ona.com>
1 parent 488db98 commit dca95b2

3 files changed

Lines changed: 74 additions & 2 deletions

File tree

src/components/editor/callout-plugin.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export function CalloutPlugin(): null {
7676

7777
if (!emojiSpan) {
7878
emojiSpan = document.createElement("span");
79-
emojiSpan.className = "callout-emoji select-none text-base shrink-0";
79+
emojiSpan.className = "callout-emoji select-none text-lg shrink-0";
8080
emojiSpan.contentEditable = "false";
8181
dom.insertBefore(emojiSpan, dom.firstChild);
8282
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { describe, it, expect } from "vitest";
2+
import { readFileSync } from "fs";
3+
import { resolve } from "path";
4+
5+
/**
6+
* Regression tests for issue #64: UI does not match design spec after PR #59.
7+
*
8+
* These tests verify that editor plugin source files comply with the design
9+
* spec constraints. They catch violations at the source level since the
10+
* plugins manipulate DOM directly and aren't easily unit-testable.
11+
*/
12+
13+
function readSource(relativePath: string): string {
14+
return readFileSync(resolve(__dirname, relativePath), "utf-8");
15+
}
16+
17+
describe("callout-plugin design spec compliance", () => {
18+
const source = readSource("./callout-plugin.tsx");
19+
20+
it("callout emoji does not use text-base (not in typography scale)", () => {
21+
// The typography scale does not include text-base. Emoji should use
22+
// text-lg or text-sm per the design spec.
23+
const emojiClassMatch = source.match(
24+
/callout-emoji[^"]*text-base[^"]*/
25+
);
26+
expect(emojiClassMatch).toBeNull();
27+
});
28+
29+
it("callout emoji uses an allowed typography size", () => {
30+
const emojiClassLine = source.match(
31+
/callout-emoji[^"]*?(text-(?:sm|lg|xl|2xl|3xl))/
32+
);
33+
expect(emojiClassLine).not.toBeNull();
34+
});
35+
});
36+
37+
describe("draggable-block-plugin design spec compliance", () => {
38+
const source = readSource("./draggable-block-plugin.tsx");
39+
40+
it("drag handle does not use transition classes (hover states must be instant)", () => {
41+
// Design spec: "Hover states: no transition (instant)."
42+
// The drag handle className must not include transition-opacity or duration-*.
43+
const menuClassMatch = source.match(
44+
/DRAGGABLE_BLOCK_MENU_CLASSNAME[^`]*transition-opacity/
45+
);
46+
expect(menuClassMatch).toBeNull();
47+
});
48+
49+
it("dragging element applies scale(1.02) per drag-and-drop spec", () => {
50+
// Design spec: "Dragging: element at 50% opacity, slight scale (scale-[1.02]), shadow-lg."
51+
expect(source).toContain('style.transform = "scale(1.02)"');
52+
});
53+
54+
it("dragging element applies shadow-lg equivalent per drag-and-drop spec", () => {
55+
// The shadow-lg value from Tailwind's theme
56+
expect(source).toContain("style.boxShadow");
57+
expect(source).toContain("0 10px 15px -3px");
58+
});
59+
60+
it("drag end resets transform and boxShadow", () => {
61+
expect(source).toContain('style.transform = ""');
62+
expect(source).toContain('style.boxShadow = ""');
63+
});
64+
});

src/components/editor/draggable-block-plugin.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,9 @@ export function DraggableBlockPlugin({
183183
// Set drag image
184184
const dragImage = target.cloneNode(true) as HTMLElement;
185185
dragImage.style.opacity = "0.5";
186+
dragImage.style.transform = "scale(1.02)";
187+
dragImage.style.boxShadow =
188+
"0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)";
186189
dragImage.style.position = "absolute";
187190
dragImage.style.top = "-1000px";
188191
document.body.appendChild(dragImage);
@@ -191,6 +194,9 @@ export function DraggableBlockPlugin({
191194

192195
draggingBlockElemRef.current = target;
193196
target.style.opacity = "0.5";
197+
target.style.transform = "scale(1.02)";
198+
target.style.boxShadow =
199+
"0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)";
194200
},
195201
[editor]
196202
);
@@ -199,6 +205,8 @@ export function DraggableBlockPlugin({
199205
isDraggingRef.current = false;
200206
if (draggingBlockElemRef.current) {
201207
draggingBlockElemRef.current.style.opacity = "1";
208+
draggingBlockElemRef.current.style.transform = "";
209+
draggingBlockElemRef.current.style.boxShadow = "";
202210
draggingBlockElemRef.current = null;
203211
}
204212
if (dropIndicatorRef.current) {
@@ -292,7 +300,7 @@ export function DraggableBlockPlugin({
292300
{/* Drag handle */}
293301
<div
294302
ref={menuRef}
295-
className={`${DRAGGABLE_BLOCK_MENU_CLASSNAME} absolute z-10 cursor-grab opacity-0 transition-opacity duration-100 active:cursor-grabbing`}
303+
className={`${DRAGGABLE_BLOCK_MENU_CLASSNAME} absolute z-10 cursor-grab opacity-0 active:cursor-grabbing`}
296304
draggable
297305
onDragStart={handleDragStart}
298306
onDragEnd={handleDragEnd}

0 commit comments

Comments
 (0)