mirror of
https://github.com/gustavosett/Windows-11-Clipboard-History-For-Linux
synced 2026-04-25 17:15:35 +02:00
fix: emojis disappearing after changing tabs
This commit is contained in:
10
src/App.tsx
10
src/App.tsx
@@ -115,7 +115,15 @@ function App() {
|
||||
<TabBar activeTab={activeTab} onTabChange={handleTabChange} />
|
||||
|
||||
{/* Scrollable content area */}
|
||||
<div className="flex-1 overflow-y-auto scrollbar-win11">{renderContent()}</div>
|
||||
<div
|
||||
className={clsx(
|
||||
'flex-1',
|
||||
// Only use scrollbar for non-emoji tabs, emoji has its own virtualized scrolling
|
||||
activeTab === 'emoji' ? 'overflow-hidden' : 'overflow-y-auto scrollbar-win11'
|
||||
)}
|
||||
>
|
||||
{renderContent()}
|
||||
</div>
|
||||
|
||||
{/* Footer hint */}
|
||||
<div className="px-4 py-2 text-center border-t dark:border-win11-border-subtle border-win11Light-border">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Emoji Picker Component
|
||||
* Windows 11 style emoji picker with virtualized grid for performance
|
||||
*/
|
||||
import { useState, useCallback, memo, useRef, useEffect } from 'react'
|
||||
import { useState, useCallback, memo, useRef, useLayoutEffect } from 'react'
|
||||
import { FixedSizeGrid as Grid } from 'react-window'
|
||||
import { clsx } from 'clsx'
|
||||
import { Search, Clock, X, ChevronLeft, ChevronRight } from 'lucide-react'
|
||||
@@ -10,20 +10,23 @@ import { useEmojiPicker } from '../hooks/useEmojiPicker'
|
||||
import type { Emoji } from '../services/emojiService'
|
||||
|
||||
/** Size of each emoji cell */
|
||||
const CELL_SIZE = 36
|
||||
/** Default height fallback */
|
||||
const DEFAULT_HEIGHT = 280
|
||||
const CELL_SIZE = 40
|
||||
/** Padding inside the grid container */
|
||||
const GRID_PADDING = 12
|
||||
|
||||
interface EmojiCellProps {
|
||||
emoji: Emoji
|
||||
onSelect: (emoji: Emoji) => void
|
||||
onHover?: (emoji: Emoji | null) => void
|
||||
}
|
||||
|
||||
/** Individual emoji cell - memoized for performance */
|
||||
const EmojiCell = memo(function EmojiCell({ emoji, onSelect }: EmojiCellProps) {
|
||||
const EmojiCell = memo(function EmojiCell({ emoji, onSelect, onHover }: EmojiCellProps) {
|
||||
return (
|
||||
<button
|
||||
onClick={() => onSelect(emoji)}
|
||||
onMouseEnter={() => onHover?.(emoji)}
|
||||
onMouseLeave={() => onHover?.(null)}
|
||||
className={clsx(
|
||||
'flex items-center justify-center',
|
||||
'w-full h-full text-2xl',
|
||||
@@ -91,22 +94,39 @@ export function EmojiPicker() {
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null)
|
||||
const [dimensions, setDimensions] = useState({ width: 0, height: 0 })
|
||||
|
||||
// Measure container size
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return
|
||||
|
||||
// Measure container size - use useLayoutEffect for synchronous measurement
|
||||
useLayoutEffect(() => {
|
||||
const updateSize = () => {
|
||||
if (containerRef.current) {
|
||||
const { width, height } = containerRef.current.getBoundingClientRect()
|
||||
setDimensions({ width, height })
|
||||
if (width > 0 && height > 0) {
|
||||
setDimensions((prev) => {
|
||||
// Only update if changed to avoid unnecessary re-renders
|
||||
if (prev.width !== width || prev.height !== height) {
|
||||
return { width, height }
|
||||
}
|
||||
return prev
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initial measurement
|
||||
updateSize()
|
||||
|
||||
// Fallback: if ref exists but dimensions weren't captured, retry after paint
|
||||
const rafId = requestAnimationFrame(updateSize)
|
||||
|
||||
// Observe for size changes
|
||||
const observer = new ResizeObserver(updateSize)
|
||||
observer.observe(containerRef.current)
|
||||
return () => observer.disconnect()
|
||||
if (containerRef.current) {
|
||||
observer.observe(containerRef.current)
|
||||
}
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(rafId)
|
||||
observer.disconnect()
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Scroll categories
|
||||
@@ -128,16 +148,17 @@ export function EmojiPicker() {
|
||||
[pasteEmoji]
|
||||
)
|
||||
|
||||
// Calculate grid dimensions
|
||||
const gridWidth = dimensions.width > 0 ? dimensions.width : 320
|
||||
const gridHeight = dimensions.height > 0 ? dimensions.height : DEFAULT_HEIGHT
|
||||
|
||||
// Dynamic column calculation for better fit
|
||||
const availableWidth = Math.max(0, gridWidth * 0.93)
|
||||
const columnCount = Math.max(1, Math.floor(availableWidth / CELL_SIZE))
|
||||
const columnWidth = availableWidth / columnCount
|
||||
// Calculate grid dimensions based on container
|
||||
// Width: use full container width minus padding
|
||||
const innerWidth = Math.max(0, dimensions.width - GRID_PADDING * 2)
|
||||
const columnCount = Math.max(1, Math.floor(innerWidth / CELL_SIZE))
|
||||
const columnWidth = columnCount > 0 ? innerWidth / columnCount : CELL_SIZE
|
||||
const rowCount = Math.ceil(filteredEmojis.length / columnCount)
|
||||
|
||||
// Height: use container height, the grid will handle scrolling internally
|
||||
const gridHeight = dimensions.height > 0 ? dimensions.height : 200
|
||||
const gridWidth = dimensions.width > 0 ? dimensions.width : 320
|
||||
|
||||
// Cell renderer for virtualized grid
|
||||
const Cell = useCallback(
|
||||
(props: { columnIndex: number; rowIndex: number; style: React.CSSProperties }) => {
|
||||
@@ -152,15 +173,17 @@ export function EmojiPicker() {
|
||||
<div
|
||||
style={{
|
||||
...style,
|
||||
left: Number(style.left) + 12, // Add left padding offset
|
||||
left: Number(style.left) + GRID_PADDING,
|
||||
width: columnWidth,
|
||||
height: CELL_SIZE,
|
||||
padding: 2, // Inner padding for spacing
|
||||
padding: 4,
|
||||
}}
|
||||
onMouseEnter={() => setHoveredEmoji(emoji)}
|
||||
onMouseLeave={() => setHoveredEmoji(null)}
|
||||
>
|
||||
<EmojiCell emoji={emoji} onSelect={handleSelect} />
|
||||
<EmojiCell
|
||||
emoji={emoji}
|
||||
onSelect={handleSelect}
|
||||
onHover={setHoveredEmoji}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
@@ -281,14 +304,15 @@ export function EmojiPicker() {
|
||||
)}
|
||||
|
||||
{/* Emoji grid */}
|
||||
<div className="flex-1 min-h-0" ref={containerRef}>
|
||||
{filteredEmojis.length === 0 ? (
|
||||
<div className="flex-1 min-h-0 overflow-hidden" ref={containerRef}>
|
||||
{filteredEmojis.length === 0 && (
|
||||
<div className="flex flex-col items-center justify-center h-full py-8">
|
||||
<p className="text-sm dark:text-win11-text-secondary text-win11Light-text-secondary">
|
||||
No emojis found
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
)}
|
||||
{filteredEmojis.length > 0 && dimensions.width > 0 && (
|
||||
<Grid
|
||||
columnCount={columnCount}
|
||||
columnWidth={columnWidth}
|
||||
@@ -297,6 +321,7 @@ export function EmojiPicker() {
|
||||
rowHeight={CELL_SIZE}
|
||||
width={gridWidth}
|
||||
className="scrollbar-win11"
|
||||
style={{ overflowX: 'hidden' }}
|
||||
>
|
||||
{Cell}
|
||||
</Grid>
|
||||
|
||||
Reference in New Issue
Block a user