Skip to content

Commit 870541e

Browse files
committed
refactor: fix and redesign select heading box
- Removed the SelectBox component and integrated its functionality directly into SelectHeadingBox. - Enhanced the heading selection UI with dropdown details and improved accessibility. - Added handling for more heading options with a toggle feature. - Updated styles for better visual hierarchy and user interaction.
1 parent 44ce741 commit 870541e

2 files changed

Lines changed: 131 additions & 138 deletions

File tree

packages/webapp/src/components/TipTap/toolbar/SelectHeadingBox.tsx

Lines changed: 131 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,146 @@
1-
import SelectBox from '@components/ui/SelectBox'
2-
import React, { useCallback } from 'react'
3-
4-
const SelectHeadingBox = ({ editor }: any) => {
5-
const options = [
6-
{ value: 'p', label: 'Normal Text', className: 'text-[14px]' },
7-
{ value: 1, label: 'Heading 1', className: 'text-[20px]' },
8-
{ value: 2, label: 'Heading 2', className: 'text-[18px]' },
9-
{ value: 3, label: 'Heading 3', className: 'text-[17px]' },
10-
{ value: 4, label: 'Heading 4', className: 'text-[16px]' },
11-
{ value: 5, label: 'Heading 5', className: 'text-[15px]' }
12-
]
13-
14-
const restOptions = [
15-
{ value: 6, label: 'Heading 6', className: 'text-[14px]' },
16-
{ value: 7, label: 'Heading 7', className: 'text-[13px]' },
17-
{ value: 8, label: 'Heading 8', className: 'text-[13px]' },
18-
{ value: 9, label: 'Heading 9', className: 'text-[13px]' },
19-
{ value: 10, label: 'Heading 10', className: 'text-[13px]' }
20-
]
21-
22-
const subOptions = {
23-
summary: 'More',
24-
options: restOptions
25-
}
26-
27-
const onHeadingChange = useCallback(
28-
(value: string) => {
1+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
2+
import { IoMdArrowDropdown } from 'react-icons/io'
3+
import { MdCheck } from 'react-icons/md'
4+
5+
const MAIN_OPTIONS = [
6+
{ value: 'p', label: 'Normal Text' },
7+
{ value: 1, label: 'Heading 1' },
8+
{ value: 2, label: 'Heading 2' },
9+
{ value: 3, label: 'Heading 3' },
10+
{ value: 4, label: 'Heading 4' },
11+
{ value: 5, label: 'Heading 5' }
12+
]
13+
14+
const MORE_OPTIONS = [
15+
{ value: 6, label: 'Heading 6' },
16+
{ value: 7, label: 'Heading 7' },
17+
{ value: 8, label: 'Heading 8' },
18+
{ value: 9, label: 'Heading 9' },
19+
{ value: 10, label: 'Heading 10' }
20+
]
21+
22+
const ALL_OPTIONS = [...MAIN_OPTIONS, ...MORE_OPTIONS]
23+
24+
// Font size classes for visual hierarchy
25+
const FONT_STYLES: Record<string | number, string> = {
26+
p: 'text-sm',
27+
1: 'text-lg font-semibold',
28+
2: 'text-base font-semibold',
29+
3: 'text-base font-medium',
30+
4: 'text-sm font-medium',
31+
5: 'text-sm',
32+
6: 'text-xs',
33+
7: 'text-xs',
34+
8: 'text-xs',
35+
9: 'text-xs',
36+
10: 'text-xs'
37+
}
38+
39+
interface SelectHeadingBoxProps {
40+
editor: any
41+
}
42+
43+
const SelectHeadingBox = ({ editor }: SelectHeadingBoxProps) => {
44+
const detailsRef = useRef<HTMLDetailsElement>(null)
45+
const [showMore, setShowMore] = useState(false)
46+
47+
// Close dropdown when clicking outside
48+
useEffect(() => {
49+
const handleClickOutside = (e: MouseEvent) => {
50+
if (detailsRef.current && !detailsRef.current.contains(e.target as Node)) {
51+
detailsRef.current.removeAttribute('open')
52+
setShowMore(false)
53+
}
54+
}
55+
document.addEventListener('click', handleClickOutside)
56+
return () => document.removeEventListener('click', handleClickOutside)
57+
}, [])
58+
59+
const currentHeading = useMemo(() => {
60+
const active = ALL_OPTIONS.find((opt) =>
61+
opt.value === 'p'
62+
? !editor.isActive('contentHeading')
63+
: editor.isActive('contentHeading', { level: opt.value })
64+
)
65+
return active || MAIN_OPTIONS[0]
66+
// eslint-disable-next-line react-hooks/exhaustive-deps
67+
}, [editor.state.selection])
68+
69+
const handleSelect = useCallback(
70+
(value: string | number) => {
2971
if (value === 'p') {
3072
editor.chain().focus().normalText().run()
3173
} else {
3274
editor.chain().focus().wrapBlock({ level: +value }).run()
3375
}
76+
detailsRef.current?.removeAttribute('open')
77+
setShowMore(false)
3478
},
3579
[editor]
3680
)
3781

38-
function getCurrentHeading(editor: any): any {
39-
const newOptions = [...options, ...restOptions]
40-
const selectedHeadingOption = newOptions.find((option) => {
41-
return editor.isActive('contentHeading', { level: option.value })
42-
})
43-
44-
return selectedHeadingOption || newOptions.at(0)
45-
}
82+
const isActive = (value: string | number) => currentHeading.value === value
4683

4784
return (
4885
<div className="tooltip tooltip-bottom" data-tip="Styles (⌘+⌥+[0-9])">
49-
<SelectBox
50-
options={options}
51-
subOptions={subOptions}
52-
value={getCurrentHeading(editor)}
53-
onChange={onHeadingChange}
54-
/>
86+
<details ref={detailsRef} className="dropdown">
87+
<summary className="btn btn-ghost btn-sm h-8 min-h-0 gap-1 px-2 font-normal">
88+
<span className="min-w-20 text-left text-sm">{currentHeading.label}</span>
89+
<IoMdArrowDropdown className="opacity-60" />
90+
</summary>
91+
92+
<ul className="dropdown-content menu bg-base-100 border-base-300 z-50 mt-1 w-48 rounded-lg border p-1 shadow-xl">
93+
{MAIN_OPTIONS.map((opt) => (
94+
<li key={opt.value}>
95+
<button
96+
type="button"
97+
onClick={() => handleSelect(opt.value)}
98+
className={`flex items-center justify-between rounded-md px-3 py-2 ${FONT_STYLES[opt.value]} ${
99+
isActive(opt.value) ? 'bg-primary/10 text-primary' : 'hover:bg-base-200'
100+
}`}>
101+
<span>{opt.label}</span>
102+
{isActive(opt.value) && <MdCheck className="text-primary" size={16} />}
103+
</button>
104+
</li>
105+
))}
106+
107+
{/* Divider */}
108+
<li className="bg-base-200 my-1 h-px" role="separator" />
109+
110+
{/* More toggle */}
111+
<li>
112+
<button
113+
type="button"
114+
onClick={(e) => {
115+
e.stopPropagation()
116+
setShowMore(!showMore)
117+
}}
118+
className="text-base-content/60 hover:bg-base-200 flex items-center justify-between rounded-md px-3 py-2 text-xs">
119+
<span>More headings</span>
120+
<IoMdArrowDropdown
121+
className={`transition-transform ${showMore ? 'rotate-180' : ''}`}
122+
size={14}
123+
/>
124+
</button>
125+
</li>
126+
127+
{/* Hidden headings */}
128+
{showMore &&
129+
MORE_OPTIONS.map((opt) => (
130+
<li key={opt.value}>
131+
<button
132+
type="button"
133+
onClick={() => handleSelect(opt.value)}
134+
className={`flex items-center justify-between rounded-md px-3 py-2 pl-5 ${FONT_STYLES[opt.value]} ${
135+
isActive(opt.value) ? 'bg-primary/10 text-primary' : 'hover:bg-base-200'
136+
}`}>
137+
<span>{opt.label}</span>
138+
{isActive(opt.value) && <MdCheck className="text-primary" size={16} />}
139+
</button>
140+
</li>
141+
))}
142+
</ul>
143+
</details>
55144
</div>
56145
)
57146
}

packages/webapp/src/components/ui/SelectBox.tsx

Lines changed: 0 additions & 96 deletions
This file was deleted.

0 commit comments

Comments
 (0)