Skip to content

Commit 8fabf8f

Browse files
authored
feat: support callout pick icon (#24)
1 parent 7ff3344 commit 8fabf8f

File tree

7 files changed

+181
-32
lines changed

7 files changed

+181
-32
lines changed

src/application/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export interface CodeBlockData extends BlockData {
8585

8686
export interface CalloutBlockData extends BlockData {
8787
icon: string;
88+
icon_type?: 'emoji' | 'icon',
8889
}
8990

9091
export interface MathEquationBlockData extends BlockData {

src/components/_shared/emoji-picker/EmojiPickerCategories.tsx

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import AutoSizer from 'react-virtualized-auto-sizer';
88
import { FixedSizeList } from 'react-window';
99
import { EmojiCategory, getRowsWithCategories } from './EmojiPicker.hooks';
1010

11-
function EmojiPickerCategories({
11+
function EmojiPickerCategories ({
1212
emojiCategories,
1313
onEmojiSelect,
1414
onEscape,
@@ -30,7 +30,7 @@ function EmojiPickerCategories({
3030
}, [emojiCategories]);
3131
const mouseY = useRef<number | null>(null);
3232
const mouseX = useRef<number | null>(null);
33-
33+
3434
const ref = React.useRef<HTMLDivElement>(null);
3535

3636
const getCategoryName = useCallback(
@@ -49,7 +49,7 @@ function EmojiPickerCategories({
4949

5050
return i18nName[id];
5151
},
52-
[t]
52+
[t],
5353
);
5454

5555
useEffect(() => {
@@ -71,7 +71,10 @@ function EmojiPickerCategories({
7171
const isFlags = item.category === 'flags';
7272

7373
return (
74-
<div style={style} data-index={index}>
74+
<div
75+
style={style}
76+
data-index={index}
77+
>
7578
{item.type === 'category' ? (
7679
<div className={'pt-2 text-base font-medium text-text-caption'}>{tagName}</div>
7780
) : null}
@@ -86,8 +89,6 @@ function EmojiPickerCategories({
8689

8790
if (isSelected) {
8891
classList.push('bg-fill-list-hover');
89-
} else {
90-
classList.push('hover:bg-transparent');
9192
}
9293

9394
if (isDefaultEmoji) {
@@ -99,7 +100,13 @@ function EmojiPickerCategories({
99100
}
100101

101102
return (
102-
<Tooltip key={emoji.id} title={emoji.name} placement={'top'} enterDelay={500} disableInteractive={true}>
103+
<Tooltip
104+
key={emoji.id}
105+
title={emoji.name}
106+
placement={'top'}
107+
enterDelay={500}
108+
disableInteractive={true}
109+
>
103110
<div
104111
data-key={emoji.id}
105112
style={{
@@ -114,13 +121,6 @@ function EmojiPickerCategories({
114121
mouseX.current = e.clientX;
115122
}}
116123
onMouseEnter={(e) => {
117-
if (mouseY.current === null || mouseY.current !== e.clientY || mouseX.current !== e.clientX) {
118-
setSelectCell({
119-
row: index,
120-
column: columnIndex,
121-
});
122-
}
123-
124124
mouseX.current = e.clientX;
125125
mouseY.current = e.clientY;
126126
}}
@@ -135,7 +135,7 @@ function EmojiPickerCategories({
135135
</div>
136136
);
137137
},
138-
[defaultEmoji, getCategoryName, onEmojiSelect, rows, selectCell.column, selectCell.row]
138+
[defaultEmoji, getCategoryName, onEmojiSelect, rows, selectCell],
139139
);
140140

141141
const getNewColumnIndex = useCallback(
@@ -150,7 +150,7 @@ function EmojiPickerCategories({
150150

151151
return newColumnIndex;
152152
},
153-
[rows]
153+
[rows],
154154
);
155155

156156
const findNextRow = useCallback(
@@ -171,7 +171,7 @@ function EmojiPickerCategories({
171171
column: newColumnIndex,
172172
};
173173
},
174-
[getNewColumnIndex, rows]
174+
[getNewColumnIndex, rows],
175175
);
176176

177177
const findPrevRow = useCallback(
@@ -191,7 +191,7 @@ function EmojiPickerCategories({
191191
column: newColumnIndex,
192192
};
193193
},
194-
[getNewColumnIndex, rows]
194+
[getNewColumnIndex, rows],
195195
);
196196

197197
const findPrevCell = useCallback(
@@ -215,7 +215,7 @@ function EmojiPickerCategories({
215215
column: prevColumn,
216216
};
217217
},
218-
[findPrevRow, rows]
218+
[findPrevRow, rows],
219219
);
220220

221221
const findNextCell = useCallback(
@@ -239,7 +239,7 @@ function EmojiPickerCategories({
239239
column: nextColumn,
240240
};
241241
},
242-
[findNextRow, rows]
242+
[findNextRow, rows],
243243
);
244244

245245
useEffect(() => {
@@ -325,7 +325,7 @@ function EmojiPickerCategories({
325325
rows,
326326
selectCell.column,
327327
selectCell.row,
328-
]
328+
],
329329
);
330330

331331
useEffect(() => {

src/components/_shared/emoji-picker/EmojiPickerHeader.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,20 @@ function EmojiPickerHeader ({ hideRemove, onEmojiSelect, onSkinSelect, searchVal
5555
onClick,
5656
tooltip,
5757
children,
58+
testId,
5859
}: {
5960
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
6061
tooltip: string;
6162
children: React.ReactNode;
63+
testId?: string;
6264
}) => {
6365
return (
6466
<Tooltip title={tooltip}>
6567
<Button
6668
size={'small'}
6769
variant={'outlined'}
6870
color={'inherit'}
71+
data-testid={testId}
6972
className={'h-9 w-9 min-w-[36px] px-0 py-0'}
7073
onClick={onClick}
7174
>
@@ -105,6 +108,7 @@ function EmojiPickerHeader ({ hideRemove, onEmojiSelect, onSkinSelect, searchVal
105108

106109
onEmojiSelect(emoji);
107110
},
111+
testId: 'random-emoji',
108112
tooltip: t('emoji.random'),
109113
children: <ShuffleIcon className={'h-5 w-5'} />,
110114
})}

src/components/_shared/icon-picker/IconPicker.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,14 +134,14 @@ function IconPicker ({
134134
<Button
135135
size="small"
136136
color="inherit"
137-
className="h-8 w-8 min-w-[32px] items-center px-1 py-1"
137+
className="h-8 w-8 min-w-[32px] items-center p-[7px]"
138138
onClick={(e) => {
139139
setSelectIcon(icon.id);
140140
setAnchorEl(e.currentTarget);
141141
}}
142142
>
143143
<div
144-
className={'w-6 h-6 text-text-title'}
144+
className={'w-5 h-5 text-text-title'}
145145
dangerouslySetInnerHTML={{ __html: icon.cleanSvg }}
146146
/>
147147
</Button>

src/components/_shared/view-icon/ChangeIconPopover.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ function ChangeIconPopover ({
5050
anchorPosition={anchorPosition}
5151
anchorReference={anchorPosition ? 'anchorPosition' : 'anchorEl'}
5252
>
53-
<div className={'border-b w-[402px] border-line-divider px-4 pt-2 flex items-center justify-between'}>
53+
<div
54+
data-testid="change-icon-popover"
55+
className={'border-b w-[402px] border-line-divider px-4 pt-2 flex items-center justify-between'}
56+
>
5457
<ViewTabs
5558
onChange={(_e, newValue) => setValue(newValue)}
5659
value={value}
@@ -63,6 +66,7 @@ function ChangeIconPopover ({
6366
className={'flex items-center flex-row justify-center gap-1.5'}
6467
value={'emoji'}
6568
label={'Emojis'}
69+
data-testid="emoji-tab"
6670
/>
6771
)
6872
}
@@ -72,6 +76,7 @@ function ChangeIconPopover ({
7276
className={'flex items-center flex-row justify-center gap-1.5'}
7377
value={'icon'}
7478
label={'Icons'}
79+
data-testid="icon-tab"
7580
/>
7681
)
7782
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { BlockType } from '@/application/types';
2+
import { initialEditorTest } from '@/components/editor/__tests__/mount';
3+
import { FromBlockJSON } from 'cypress/support/document';
4+
5+
const initialData: FromBlockJSON[] = [{
6+
type: BlockType.CalloutBlock,
7+
data: {},
8+
text: [{ insert: 'Hello Callout' }],
9+
children: [],
10+
}];
11+
12+
const { assertJSON, initializeEditor } = initialEditorTest();
13+
14+
describe('CalloutBlock', () => {
15+
beforeEach(() => {
16+
cy.viewport(1280, 720);
17+
Object.defineProperty(window.navigator, 'language', { value: 'en-US' });
18+
initializeEditor(initialData);
19+
const selector = '[role="textbox"]';
20+
21+
cy.get(selector).as('editor');
22+
23+
cy.wait(1000);
24+
25+
cy.get(selector).focus();
26+
});
27+
28+
it('should show callout icon popover when clicking icon button', () => {
29+
cy.get('@editor').get('[data-block-type="callout"]').as('callout');
30+
cy.get('@callout').find('[data-testid="callout-icon-button"]').click();
31+
cy.get('[data-testid="change-icon-popover"]').should('exist');
32+
});
33+
34+
it('should display `📌` emoji as default icon', () => {
35+
cy.get('@editor').get('[data-block-type="callout"]').as('callout');
36+
cy.get('@callout').find('[data-testid="callout-icon-button"]').should('have.text', '📌');
37+
});
38+
39+
it('should add child block when pressing enter', () => {
40+
cy.selectMultipleText(['Hello Callout']);
41+
cy.wait(100);
42+
43+
cy.get('@editor').realPress('ArrowRight');
44+
cy.get('@editor').realPress('Enter');
45+
cy.get('@editor').type('Hello World');
46+
assertJSON([
47+
{
48+
type: BlockType.CalloutBlock,
49+
data: {},
50+
children: [{
51+
type: BlockType.Paragraph,
52+
data: {},
53+
children: [],
54+
text: [{ insert: 'Hello World' }],
55+
}],
56+
text: [{ insert: 'Hello Callout' }],
57+
},
58+
]);
59+
});
60+
61+
it('should turn to paragraph block when pressing backspace at the beginning of the block', () => {
62+
cy.selectMultipleText(['Hello Callout']);
63+
cy.wait(100);
64+
65+
cy.get('@editor').realPress('ArrowLeft');
66+
67+
cy.get('@editor').realPress('Backspace');
68+
assertJSON([
69+
{
70+
type: BlockType.Paragraph,
71+
data: {},
72+
children: [],
73+
text: [{ insert: 'Hello Callout' }],
74+
},
75+
]);
76+
});
77+
});

0 commit comments

Comments
 (0)