From 014da1702be17620718ddcbd57b6cef2bb19ed38 Mon Sep 17 00:00:00 2001 From: denis0001-dev Date: Thu, 2 Oct 2025 23:53:30 +0300 Subject: [PATCH] Fix category titles --- .../src/pages/app/resources/css/_chat.scss | 123 ++++++++++++---- .../ui/components/chat/ChatInputWrapper.tsx | 10 +- .../app/ui/components/chat/EmojiMenu.tsx | 139 +++++++++++++----- 3 files changed, 202 insertions(+), 70 deletions(-) diff --git a/frontend/src/pages/app/resources/css/_chat.scss b/frontend/src/pages/app/resources/css/_chat.scss index 7a03aa3..f07ec3a 100644 --- a/frontend/src/pages/app/resources/css/_chat.scss +++ b/frontend/src/pages/app/resources/css/_chat.scss @@ -77,6 +77,7 @@ display: flex; width: 100%; flex-direction: column; + overflow: hidden; .chat-main { flex-grow: 1; @@ -84,6 +85,7 @@ flex-direction: column; height: 100%; position: relative; + overflow: hidden; .chat-header { padding: 16px; @@ -537,22 +539,30 @@ display: flex; flex-direction: row; align-items: center; - - .emoji-btn { - margin-left: 10px; - color: $color-dark-on-surface-variant; - transition: color 0.2s ease; - flex-shrink: 0; - - &:hover { - color: $color-dark-primary; + + .buttons, .left-buttons { + display: flex; + flex-direction: row; + align-items: center; + } + + .left-buttons { + .emoji-btn { + margin: 10px; + color: $color-dark-on-surface-variant; + transition: color 0.2s ease; + flex-shrink: 0; + align-self: flex-end; + + &:hover { + color: $color-dark-primary; + } } } - + .message-input { flex: 1; - padding: 20px 20px; - padding-right: 0; + padding: 20px 0; border: none; border-radius: 25px; font-size: 1rem; @@ -573,11 +583,6 @@ } .buttons { - align-self: flex-end; - display: flex; - flex-direction: row; - align-items: center; - .send-btn { margin: 10px; width: 50px; @@ -784,7 +789,7 @@ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); backdrop-filter: blur(20px); width: 320px; - max-height: 400px; + height: 400px; overflow: hidden; display: flex; flex-direction: column; @@ -792,6 +797,7 @@ opacity: 0; transform: translateY(30px); transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); + user-select: none; &.open { opacity: 1; @@ -801,7 +807,6 @@ .emoji-menu-header { background: $color-dark-surface-container-high; border-bottom: 1px solid rgba($color-dark-outline-variant, 0.2); - padding: 8px; position: sticky; top: 0; z-index: 1; @@ -810,6 +815,9 @@ display: flex; gap: 4px; overflow-x: auto; + overflow-y: hidden; + scroll-behavior: smooth; + padding: 8px; &::-webkit-scrollbar { height: 4px; @@ -824,19 +832,25 @@ border-radius: 2px; } + &::-webkit-scrollbar-thumb:hover { + background-color: $color-dark-surface-container-high; + } + .emoji-category-tab { background: transparent; border: none; - border-radius: 8px; + border-radius: 10px; padding: 8px; cursor: pointer; - transition: all 0.2s ease; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); font-size: 1.2rem; min-width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; + position: relative; + overflow: hidden; &:hover { background: $color-dark-surface-container; @@ -845,18 +859,40 @@ &.active { background: $color-dark-primary-container; color: $color-dark-on-primary-container; + transform: scale(1.05); + } + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: $color-dark-primary-container; + opacity: 0; + transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1); + border-radius: 8px; + } + + &.active::before { + opacity: 1; + } + + span { + position: relative; + z-index: 1; } } } } .emoji-grid { - display: grid; - grid-template-columns: repeat(8, 1fr); - gap: 2px; - padding: 8px; - max-height: 300px; + display: flex; + flex-direction: column; + flex: 1; overflow-y: auto; + scroll-behavior: smooth; &::-webkit-scrollbar { width: 6px; @@ -871,18 +907,47 @@ border-radius: 3px; } + .emoji-category-section { + .emoji-category-title { + position: sticky; + top: 0; + padding-top: 5px; + padding-bottom: 5px; + padding-left: 12px; + padding-right: 12px; + font-size: 0.85rem; + font-weight: 600; + color: $color-dark-on-surface-variant; + z-index: 2; + margin: 0; + backdrop-filter: blur(10px); + } + + .emoji-category-grid { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 2px; + padding: 8px; + } + } + .emoji-item { + $size: 30px; + background: transparent; border: none; border-radius: 6px; - padding: 8px; + padding: 5px; cursor: pointer; transition: all 0.15s ease; - font-size: 1.4rem; + font-size: $size; + width: $size; + height: $size; + box-sizing: content-box; display: flex; align-items: center; justify-content: center; - min-height: 40px; &:hover { background: $color-dark-surface-container-high; diff --git a/frontend/src/pages/app/ui/components/chat/ChatInputWrapper.tsx b/frontend/src/pages/app/ui/components/chat/ChatInputWrapper.tsx index 0f416ac..502d932 100644 --- a/frontend/src/pages/app/ui/components/chat/ChatInputWrapper.tsx +++ b/frontend/src/pages/app/ui/components/chat/ChatInputWrapper.tsx @@ -44,7 +44,6 @@ export function ChatInputWrapper( const [errorOpen, setErrorOpen] = useState(false); const [emojiMenuOpen, setEmojiMenuOpen] = useState(false); const [emojiMenuPosition, setEmojiMenuPosition] = useState({ x: 0, y: 0 }); - const emojiButtonRef = useRef(null); const chatInputWrapperRef = useRef(null); // Expose a way for parent to programmatically add files @@ -176,12 +175,9 @@ export function ChatInputWrapper( )}
- +
+ +
([]); const menuRef = useRef(null); + const scrollRef = useRef(null); + const categoryRefs = useRef>(new Map()); + const tabsRef = useRef(null); + const tabRefs = useRef>(new Map()); useEffect(() => { if (isOpen) { @@ -20,6 +24,54 @@ export function EmojiMenu({ isOpen, onClose, onEmojiSelect, position }: EmojiMen } }, [isOpen]); + const handleScroll = useCallback(() => { + if (!scrollRef.current) return; + + // Find which category is currently visible + for (const [categoryName, element] of categoryRefs.current) { + if (element) { + const rect = element.getBoundingClientRect(); + const containerRect = scrollRef.current.getBoundingClientRect(); + + // Check if category header is in view + if (rect.top <= containerRect.top + 50 && rect.bottom > containerRect.top + 50) { + if (activeCategory !== categoryName) { + setActiveCategory(categoryName); + scrollTabIntoView(categoryName); + } + break; + } + } + } + }, [activeCategory]); + + function scrollToCategory(categoryName: string) { + const element = categoryRefs.current.get(categoryName); + if (element && scrollRef.current) { + element.scrollIntoView({ + behavior: 'smooth', + block: 'start' + }); + } + } + + function scrollTabIntoView(categoryName: string) { + const tabElement = tabRefs.current.get(categoryName); + if (tabElement && tabsRef.current) { + const tabsRect = tabsRef.current.getBoundingClientRect(); + const tabRect = tabElement.getBoundingClientRect(); + + // Check if tab is outside the visible area + if (tabRect.left < tabsRect.left || tabRect.right > tabsRect.right) { + tabElement.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + inline: 'center' + }); + } + } + } + useEffect(() => { function handleClickOutside(event: MouseEvent) { if (menuRef.current && !menuRef.current.contains(event.target as Node)) { @@ -50,13 +102,6 @@ export function EmojiMenu({ isOpen, onClose, onEmojiSelect, position }: EmojiMen onClose(); }; - function getCurrentEmojis(): string[] { - if (activeCategory === "recent") { - return recentEmojis; - } - const category = EMOJI_CATEGORIES.find(cat => cat.name === activeCategory); - return category?.emojis || []; - }; return (
-
- {EMOJI_CATEGORIES.map((category) => ( - - ))} -
-
- -
- {getCurrentEmojis().map((emoji, index) => ( +
+ {EMOJI_CATEGORIES.map((category) => ( ))}
+
- {getCurrentEmojis().length === 0 && activeCategory === "recent" && ( -
- No recent emojis -
- )} +
+ {EMOJI_CATEGORIES.map((category) => { + const emojis = category.name === "recent" ? recentEmojis : category.emojis; + + return ( +
{ + if (el) categoryRefs.current.set(category.name, el); + }} + className="emoji-category-section" + > +

+ {category.name.charAt(0).toUpperCase() + category.name.slice(1)} +

+ {emojis.length > 0 ? ( +
+ {emojis.map((emoji, index) => ( + + ))} +
+ ) : ( +
+ No {category.name} emojis +
+ )} +
+ ); + })} +
); }