feat(ui): add new ui
This commit is contained in:
@ -3,66 +3,174 @@ import { Home, BookOpen, Award, User, Video } from "lucide-react";
|
||||
import { SidebarProvider, SidebarTrigger } from "../../components/ui/sidebar";
|
||||
import { AppSidebar } from "../../components/AppSidebar";
|
||||
|
||||
export function StudentLayout() {
|
||||
const navItems = [
|
||||
{ to: "/student/home", icon: Home, label: "Home" },
|
||||
{ to: "/student/practice", icon: BookOpen, label: "Practice" },
|
||||
{ to: "/student/lessons", icon: Video, label: "Lessons" },
|
||||
{ to: "/student/rewards", icon: Award, label: "Rewards" },
|
||||
{ to: "/student/profile", icon: User, label: "Profile" },
|
||||
];
|
||||
const NAV_ITEMS = [
|
||||
{
|
||||
to: "/student/home",
|
||||
icon: Home,
|
||||
label: "Home",
|
||||
color: "#f97316",
|
||||
bg: "rgba(249,115,22,0.12)",
|
||||
},
|
||||
{
|
||||
to: "/student/practice",
|
||||
icon: BookOpen,
|
||||
label: "Practice",
|
||||
color: "#a855f7",
|
||||
bg: "rgba(168,85,247,0.12)",
|
||||
},
|
||||
{
|
||||
to: "/student/lessons",
|
||||
icon: Video,
|
||||
label: "Lessons",
|
||||
color: "#0891b2",
|
||||
bg: "rgba(8,145,178,0.12)",
|
||||
},
|
||||
{
|
||||
to: "/student/rewards",
|
||||
icon: Award,
|
||||
label: "Rewards",
|
||||
color: "#16a34a",
|
||||
bg: "rgba(22,163,74,0.12)",
|
||||
},
|
||||
{
|
||||
to: "/student/profile",
|
||||
icon: User,
|
||||
label: "Profile",
|
||||
color: "#e11d48",
|
||||
bg: "rgba(225,29,72,0.12)",
|
||||
},
|
||||
];
|
||||
|
||||
const STYLES = `
|
||||
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@700;800;900&display=swap');
|
||||
|
||||
/* ── The floating island dock ── */
|
||||
.sl-dock-wrap {
|
||||
position: fixed;
|
||||
bottom: calc(1.25rem + env(safe-area-inset-bottom));
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 20;
|
||||
|
||||
/* Frosted pill */
|
||||
background: rgba(255, 251, 244, 0.72);
|
||||
backdrop-filter: blur(24px) saturate(180%);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(180%);
|
||||
border: 1.5px solid rgba(255,255,255,0.7);
|
||||
border-radius: 100px;
|
||||
box-shadow:
|
||||
0 8px 32px rgba(0,0,0,0.12),
|
||||
0 2px 8px rgba(0,0,0,0.06),
|
||||
inset 0 1px 0 rgba(255,255,255,0.8);
|
||||
|
||||
padding: 0.45rem 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.15rem;
|
||||
}
|
||||
|
||||
/* ── Each nav item ── */
|
||||
.sl-dock-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
border-radius: 100px;
|
||||
padding: 0.5rem 0.6rem;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
transition:
|
||||
padding 0.35s cubic-bezier(0.34,1.56,0.64,1),
|
||||
gap 0.35s cubic-bezier(0.34,1.56,0.64,1),
|
||||
background 0.25s ease;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
.sl-dock-item:active { transform: scale(0.91); }
|
||||
.sl-dock-item.active {
|
||||
padding: 0.5rem 1rem 0.5rem 0.75rem;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
|
||||
/* ── Icon circle ── */
|
||||
.sl-dock-icon {
|
||||
width: 32px; height: 32px; flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: transparent;
|
||||
transition: background 0.25s ease, transform 0.35s cubic-bezier(0.34,1.56,0.64,1);
|
||||
}
|
||||
.sl-dock-item.active .sl-dock-icon {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* ── Label (only visible when active) ── */
|
||||
.sl-dock-label {
|
||||
font-family: 'Nunito', sans-serif;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.01em;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
transition:
|
||||
max-width 0.35s cubic-bezier(0.34,1.56,0.64,1),
|
||||
opacity 0.25s ease 0.05s;
|
||||
pointer-events: none;
|
||||
}
|
||||
.sl-dock-item.active .sl-dock-label {
|
||||
max-width: 80px;
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
export function StudentLayout() {
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<style>{STYLES}</style>
|
||||
<div className="flex min-h-screen w-full overflow-x-hidden">
|
||||
{/* Desktop Sidebar */}
|
||||
<AppSidebar />
|
||||
|
||||
<div className="flex flex-col flex-1 min-w-0">
|
||||
<SidebarTrigger className="hidden md:block" />
|
||||
<main className="flex-1 pb-24 md:pb-0">
|
||||
{/* Extra bottom padding so content clears the floating dock */}
|
||||
<main className="flex-1 pb-32 md:pb-0">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{/* Mobile bottom nav */}
|
||||
<nav className="fixed bottom-0 left-0 right-0 rounded-t-4xl pt-2 bg-white border-t border-gray-200 shadow-4xl z-20 md:hidden">
|
||||
<div className="max-w-7xl mx-auto px-2">
|
||||
<div className="flex justify-around items-center">
|
||||
{navItems.map((item) => (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
className={({ isActive }) =>
|
||||
`flex flex-col items-center justify-center py-3 px-4 flex-1 transition-all duration-200 font-satoshi tracking-wide ${
|
||||
isActive
|
||||
? "text-indigo-600"
|
||||
: "text-gray-500 hover:text-gray-700"
|
||||
}`
|
||||
}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<>
|
||||
<item.icon
|
||||
size={24}
|
||||
className={`mb-1 transition-transform ${
|
||||
isActive ? "scale-110" : ""
|
||||
}`}
|
||||
strokeWidth={isActive ? 2.5 : 2}
|
||||
/>
|
||||
<span
|
||||
className={`text-xs font-medium ${
|
||||
isActive ? "font-semibold" : ""
|
||||
}`}
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{/* ── Floating island dock (mobile only) ── */}
|
||||
<nav className="sl-dock-wrap md:hidden">
|
||||
{NAV_ITEMS.map((item) => (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
className={({ isActive }) =>
|
||||
`sl-dock-item${isActive ? " active" : ""}`
|
||||
}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<>
|
||||
<div
|
||||
className="sl-dock-icon"
|
||||
style={{ background: isActive ? item.bg : "transparent" }}
|
||||
>
|
||||
<item.icon
|
||||
size={18}
|
||||
strokeWidth={isActive ? 2.5 : 1.75}
|
||||
color={isActive ? item.color : "#94a3b8"}
|
||||
/>
|
||||
</div>
|
||||
<span className="sl-dock-label" style={{ color: item.color }}>
|
||||
{item.label}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</NavLink>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
|
||||
Reference in New Issue
Block a user