Add sliding and sliding animations on mobile.
This commit is contained in:
parent
00d22f7769
commit
aa42223fc9
4 changed files with 153 additions and 23 deletions
|
|
@ -21,7 +21,7 @@
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<div class="app-container">
|
<div class="app-container no-active-chat">
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside class="sidebar">
|
<aside class="sidebar">
|
||||||
<header class="sidebar-header">
|
<header class="sidebar-header">
|
||||||
|
|
@ -83,7 +83,7 @@
|
||||||
<div class="active-chat-container hidden" id="active-chat-container">
|
<div class="active-chat-container hidden" id="active-chat-container">
|
||||||
<header class="chat-header">
|
<header class="chat-header">
|
||||||
<div class="active-contact-info">
|
<div class="active-contact-info">
|
||||||
<!-- <button class="icon-btn back-btn" id="back-to-sidebar"><i data-lucide="arrow-left"></i></button> -->
|
<!-- <button class="icon-btn back-btn mif-arrow-left mif-2x" id="back-to-sidebar" title="Back to chats"></button> -->
|
||||||
<div class="avatar active-avatar" id="active-chat-avatar">C</div>
|
<div class="avatar active-avatar" id="active-chat-avatar">C</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 id="active-chat-name">Contact Name</h3>
|
<h3 id="active-chat-name">Contact Name</h3>
|
||||||
|
|
|
||||||
102
js/app.js
102
js/app.js
|
|
@ -27,6 +27,15 @@ function loadChats() {
|
||||||
elements.chatsLoader.classList.remove('hidden');
|
elements.chatsLoader.classList.remove('hidden');
|
||||||
fetchChats().then(() => {
|
fetchChats().then(() => {
|
||||||
ui.renderChatList(getChats(), activeChatState, selectChat);
|
ui.renderChatList(getChats(), activeChatState, selectChat);
|
||||||
|
|
||||||
|
const hash = window.location.hash;
|
||||||
|
if (hash && hash.startsWith('#chat-')) {
|
||||||
|
const chatId = hash.replace('#chat-', '');
|
||||||
|
const chat = getChats().find(c => c.id === chatId);
|
||||||
|
if (chat) {
|
||||||
|
selectChat(chat, true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load chats:', error);
|
console.error('Failed to load chats:', error);
|
||||||
|
|
@ -44,11 +53,67 @@ function loadChats() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let isScrollingProgrammatically = false;
|
||||||
|
let scrollTimeout = null;
|
||||||
|
|
||||||
|
function scrollToChat(smooth = true) {
|
||||||
|
isScrollingProgrammatically = true;
|
||||||
|
elements.appContainer.scrollTo({
|
||||||
|
left: elements.appContainer.clientWidth,
|
||||||
|
behavior: smooth ? 'smooth' : 'auto'
|
||||||
|
});
|
||||||
|
setTimeout(() => { isScrollingProgrammatically = false; }, smooth ? 400 : 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToList(smooth = true) {
|
||||||
|
isScrollingProgrammatically = true;
|
||||||
|
elements.appContainer.scrollTo({
|
||||||
|
left: 0,
|
||||||
|
behavior: smooth ? 'smooth' : 'auto'
|
||||||
|
});
|
||||||
|
setTimeout(() => { isScrollingProgrammatically = false; }, smooth ? 400 : 50);
|
||||||
|
}
|
||||||
|
|
||||||
function setupEventListeners() {
|
function setupEventListeners() {
|
||||||
|
if (!window.location.hash) {
|
||||||
|
window.location.hash = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('hashchange', () => {
|
||||||
|
const hash = window.location.hash;
|
||||||
|
if (hash && hash.startsWith('#chat-')) {
|
||||||
|
const chatId = hash.replace('#chat-', '');
|
||||||
|
const chat = getChats().find(c => c.id === chatId);
|
||||||
|
if (chat) {
|
||||||
|
selectChat(chat, true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
closeActiveChat(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
elements.appContainer.addEventListener('scroll', () => {
|
||||||
|
if (window.innerWidth > 768) return;
|
||||||
|
if (isScrollingProgrammatically) return;
|
||||||
|
|
||||||
|
clearTimeout(scrollTimeout);
|
||||||
|
scrollTimeout = setTimeout(() => {
|
||||||
|
const scrollLeft = elements.appContainer.scrollLeft;
|
||||||
|
const width = elements.appContainer.clientWidth;
|
||||||
|
|
||||||
|
if (scrollLeft < width * 0.2) {
|
||||||
|
// User swiped back to the list view
|
||||||
|
if (activeChatState) {
|
||||||
|
closeActiveChat(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (e.code == "Escape") {
|
if (e.code == "Escape") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
ui.toggleChatState();
|
closeActiveChat(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -94,8 +159,7 @@ function setupEventListeners() {
|
||||||
})
|
})
|
||||||
|
|
||||||
elements.backToSidebarBtn.addEventListener('click', () => {
|
elements.backToSidebarBtn.addEventListener('click', () => {
|
||||||
elements.sidebar.classList.remove('hidden');
|
closeActiveChat(false);
|
||||||
elements.activeChatContainer.classList.add('hidden');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
elements.settingsIconBtn.addEventListener('click', openSettings);
|
elements.settingsIconBtn.addEventListener('click', openSettings);
|
||||||
|
|
@ -155,7 +219,7 @@ async function handleIncomingMessage(msg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function selectChat(chat) {
|
async function selectChat(chat, isPopState = false, smoothScroll = true) {
|
||||||
activeChatState = chat;
|
activeChatState = chat;
|
||||||
|
|
||||||
chat.unreadCount = 0;
|
chat.unreadCount = 0;
|
||||||
|
|
@ -179,8 +243,14 @@ async function selectChat(chat) {
|
||||||
</span>
|
</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
|
elements.appContainer.classList.remove('no-active-chat');
|
||||||
|
|
||||||
if (window.innerWidth <= 768) {
|
if (window.innerWidth <= 768) {
|
||||||
elements.sidebar.classList.add('hidden');
|
scrollToChat(smoothScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPopState && window.location.hash !== `#chat-${chat.id}`) {
|
||||||
|
window.location.hash = `chat-${chat.id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -193,6 +263,28 @@ async function selectChat(chat) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function closeActiveChat(isPopState = false) {
|
||||||
|
activeChatState = null;
|
||||||
|
|
||||||
|
if (window.innerWidth <= 768) {
|
||||||
|
scrollToList();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!activeChatState) {
|
||||||
|
elements.appContainer.classList.add('no-active-chat');
|
||||||
|
ui.toggleChatState(false);
|
||||||
|
}
|
||||||
|
}, 350);
|
||||||
|
} else {
|
||||||
|
ui.toggleChatState(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPopState) {
|
||||||
|
if (window.location.hash.startsWith('#chat-')) {
|
||||||
|
history.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function sendMessage() {
|
async function sendMessage() {
|
||||||
const text = elements.messageInput.value.trim();
|
const text = elements.messageInput.value.trim();
|
||||||
if (!text || !activeChatState) return;
|
if (!text || !activeChatState) return;
|
||||||
|
|
|
||||||
1
js/ui.js
1
js/ui.js
|
|
@ -19,6 +19,7 @@ export const elements = {
|
||||||
messageInput: document.getElementById('message-input'),
|
messageInput: document.getElementById('message-input'),
|
||||||
backToSidebarBtn: document.querySelector('.chat-header'),
|
backToSidebarBtn: document.querySelector('.chat-header'),
|
||||||
sidebar: document.querySelector('.sidebar'),
|
sidebar: document.querySelector('.sidebar'),
|
||||||
|
appContainer: document.querySelector('.app-container'),
|
||||||
settingsModal: document.getElementById('settings-modal'),
|
settingsModal: document.getElementById('settings-modal'),
|
||||||
settingsIconBtn: document.querySelector('.header-actions button[title="Settings"]'),
|
settingsIconBtn: document.querySelector('.header-actions button[title="Settings"]'),
|
||||||
cancelSettingsBtn: document.getElementById('cancel-settings'),
|
cancelSettingsBtn: document.getElementById('cancel-settings'),
|
||||||
|
|
|
||||||
69
style.css
69
style.css
|
|
@ -29,11 +29,21 @@
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
height: 100dvh;
|
||||||
|
overflow: hidden;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: var(--bg-main);
|
background-color: var(--bg-main);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
background-image:
|
background-image:
|
||||||
radial-gradient(at 0% 0%, rgba(99, 102, 241, 0.12) 0px, transparent 50%),
|
radial-gradient(at 0% 0%, rgba(99, 102, 241, 0.12) 0px, transparent 50%),
|
||||||
radial-gradient(at 100% 100%, rgba(168, 85, 247, 0.12) 0px, transparent 50%);
|
radial-gradient(at 100% 100%, rgba(168, 85, 247, 0.12) 0px, transparent 50%);
|
||||||
|
|
@ -70,10 +80,11 @@ input:focus {
|
||||||
|
|
||||||
.app-container {
|
.app-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100vw;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
backdrop-filter: var(--glass-blur);
|
backdrop-filter: var(--glass-blur);
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-name {
|
.app-name {
|
||||||
|
|
@ -727,33 +738,59 @@ input:focus {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive design */
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.app-container {
|
.app-container {
|
||||||
position: relative;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
scroll-snap-type: x mandatory;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
|
||||||
|
/* Hide scrollbars */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-container::-webkit-scrollbar {
|
||||||
|
display: none; /* Chrome, Safari, Opera */
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: absolute;
|
flex: 0 0 100%;
|
||||||
left: 0;
|
position: relative;
|
||||||
top: 0;
|
left: auto;
|
||||||
|
top: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 10;
|
z-index: 1;
|
||||||
}
|
scroll-snap-align: start;
|
||||||
|
scroll-snap-stop: always;
|
||||||
.sidebar.hidden {
|
|
||||||
transform: translateX(-100%);
|
|
||||||
display: flex !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-area {
|
.chat-area {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
flex: 0 0 100%;
|
||||||
|
position: relative;
|
||||||
|
left: auto;
|
||||||
|
top: auto;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
scroll-snap-align: start;
|
||||||
|
scroll-snap-stop: always;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Disable scrolling to active chat if none is selected */
|
||||||
|
/* .app-container.no-active-chat .chat-area {
|
||||||
|
display: none !important;
|
||||||
|
} */
|
||||||
|
|
||||||
.back-btn {
|
.back-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-right: 8px;
|
margin-right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-group {
|
.message-group {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue