Skip to content

Commit

Permalink
💄 Improve mobile mode animations
Browse files Browse the repository at this point in the history
  • Loading branch information
JulyWitch committed Jan 17, 2025
1 parent 2025b25 commit b465015
Showing 1 changed file with 147 additions and 56 deletions.
203 changes: 147 additions & 56 deletions src/components/IconDetails/IconDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import { Icon } from "../../types/icons";
import { IconCredit } from "./IconCredit";
import { Tabs } from "./Tabs";
Expand All @@ -14,6 +14,18 @@ interface IconDetailsProps {

export function IconDetails({ icon, onClose }: IconDetailsProps) {
const [activeTab, setActiveTab] = useState("Preview");
const [startY, setStartY] = useState(0);
const [currentY, setCurrentY] = useState(0);

// Lock/unlock body scroll when bottom sheet is shown/hidden on mobile
useEffect(() => {
if (icon && window.innerWidth < 768) {
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = "auto";
};
}
}, [icon]);

const handleDownload = async () => {
try {
Expand All @@ -33,64 +45,143 @@ export function IconDetails({ icon, onClose }: IconDetailsProps) {
}
};

const handleTouchStart = (e: React.TouchEvent) => {
const touch = e.touches[0];
const target = e.target as HTMLElement;

// Allow scrolling of content inside the bottom sheet
const content = target.closest(".content-scroll");
if (content) {
const isScrollable =
content.scrollHeight > content.clientHeight;
if (isScrollable && content.scrollTop > 0) {
return;
}
}

setStartY(touch.clientY);
};

const handleTouchMove = (e: React.TouchEvent) => {
const touch = e.touches[0];
const target = e.target as HTMLElement;

// Check if we're scrolling inside content
const content = target.closest(".content-scroll");
if (content) {
const isScrollable =
content.scrollHeight > content.clientHeight;
if (isScrollable && content.scrollTop > 0) {
return;
}
}

const deltaY = touch.clientY - startY;
if (deltaY > 0) {
e.preventDefault();
setCurrentY(deltaY);
}
};

const handleTouchEnd = () => {
if (currentY > 150) {
onClose();
}
setCurrentY(0);
};

return (
<div
className={`
md:sticky md:right-0 md:top-0 md:h-screen md:min-w-96 md:max-w-96 md:shadow-lg
fixed inset-x-0 bottom-0 w-full h-screen bg-white shadow-lg
transform transition-all duration-300 ease-in-out
${icon ? "translate-y-0" : "translate-y-full md:translate-y-0"}
${!icon && "md:block md:visible invisible"}
`}
>
<div className="p-6">
<div className="flex justify-between items-center mb-6">
<h2 className="text-xl font-semibold">
Icon Details
</h2>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 rounded-full transition-colors md:hidden"
aria-label="Close details"
>
<X className="w-6 h-6" />
</button>
</div>
<>
{icon && (
<div
className="fixed inset-0 bg-black/50 md:hidden"
onClick={onClose}
/>
)}

{icon == null ? (
<p>Please select an icon</p>
) : (
<>
<IconCredit
iconPath={icon.path}
/>
<Tabs
activeTab={activeTab}
onTabChange={
setActiveTab
}
/>
{activeTab === "Preview" && (
<PreviewTab
icon={icon}
onDownload={
handleDownload
}
/>
)}
{activeTab === "Code" && (
<CodeTab
iconPath={
icon.path
}
/>
)}
{activeTab === "React" && (
<ReactTab />
<div
className={`
md:sticky md:right-0 md:top-0 md:h-screen md:min-w-96 md:max-w-96 md:shadow-lg
fixed inset-x-0 bottom-0 w-full h-[85vh] bg-white shadow-lg rounded-t-xl
transform transition-transform duration-200 ease-out md:rounded-none
${!icon ? "translate-y-full md:translate-y-0" : "translate-y-0"}
${!icon && "md:block hidden"}
z-50
`}
style={{
transform: currentY
? `translateY(${currentY}px)`
: undefined,
}}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
>
<div className="h-1.5 w-12 bg-gray-300 rounded-full mx-auto mt-4 mb-2 md:hidden" />

<div className="p-6 h-full flex flex-col">
<div className="flex justify-between items-center mb-6 flex-shrink-0">
<h2 className="text-xl font-semibold">
Icon Details
</h2>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 rounded-full transition-colors md:hidden"
aria-label="Close details"
>
<X className="w-6 h-6" />
</button>
</div>

<div className="content-scroll flex-1 overflow-y-auto">
{icon == null ? (
<p>
Please select an
icon
</p>
) : (
<>
<IconCredit
iconPath={
icon.path
}
/>
<Tabs
activeTab={
activeTab
}
onTabChange={
setActiveTab
}
/>
{activeTab ===
"Preview" && (
<PreviewTab
icon={
icon
}
onDownload={
handleDownload
}
/>
)}
{activeTab ===
"Code" && (
<CodeTab
iconPath={
icon.path
}
/>
)}
{activeTab ===
"React" && (
<ReactTab />
)}
</>
)}
</>
)}
</div>
</div>
</div>
</div>
</>
);
}

0 comments on commit b465015

Please sign in to comment.