Skip to content

Commit 7ad5f09

Browse files
committed
Improve collapsible sidebar with animations and footer redesign
- Add Material Design easing (cubic-bezier) for smooth transitions - Redesign footer with animated "Open" text + square logo - Add hover overlay with backdrop blur on collapsed sidebar - Remove tooltips from menu items for cleaner UX - Center menu icons when collapsed, restore alignment on expand - Update breadcrumbs to always show project name - Sync settings layout structure with live layout - Fix menu item vertical positioning consistency
1 parent bf6dfff commit 7ad5f09

File tree

10 files changed

+421
-288
lines changed

10 files changed

+421
-288
lines changed

assets/css/app.css

Lines changed: 226 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -301,13 +301,9 @@
301301
}
302302
}
303303

304-
/* Collapsed sidebar styles */
305-
.menu-item-collapsed .menu-item-text {
306-
display: none;
307-
}
308-
309-
.menu-item-collapsed svg {
310-
margin-right: 0;
304+
/* Prevent icons from shrinking in flex containers */
305+
.menu-item svg {
306+
flex-shrink: 0;
311307
}
312308

313309
/* Hide scrollbar in sidebar to prevent flash during transition */
@@ -340,6 +336,229 @@
340336
}
341337
}
342338

339+
/* Material Design easing for sidebar transitions */
340+
#sidebar-panel,
341+
#sidebar-panel + div > div:first-child {
342+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
343+
}
344+
345+
/* Menu item text - base styles with transition */
346+
#sidebar-panel .menu-item-text {
347+
transition: max-width 200ms cubic-bezier(0.4, 0, 0.2, 1), opacity 200ms cubic-bezier(0.4, 0, 0.2, 1);
348+
overflow: hidden;
349+
white-space: nowrap;
350+
}
351+
352+
/* Logo text transition */
353+
#sidebar-panel .sidebar-logo-text {
354+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
355+
}
356+
357+
/* Sidebar footer - default state (expanded) */
358+
#sidebar-panel .sidebar-footer {
359+
.sidebar-logo-text {
360+
max-width: 50px;
361+
margin-right: 4px;
362+
opacity: 1;
363+
}
364+
365+
.sidebar-version-chip {
366+
opacity: 0.5;
367+
transition-delay: 200ms; /* Appear after menu animation */
368+
}
369+
}
370+
371+
/* Keep sidebar expanded when menu is open */
372+
#sidebar-panel.sidebar-collapsed.menu-open {
373+
width: 192px !important;
374+
box-shadow: 8px 0 24px rgba(0, 0, 0, 0.3), 4px 0 8px rgba(0, 0, 0, 0.2);
375+
376+
&::after {
377+
opacity: 1;
378+
visibility: visible;
379+
}
380+
381+
.menu-item-text {
382+
max-width: 150px !important;
383+
opacity: 1 !important;
384+
}
385+
386+
/* Reset menu item alignment when menu open */
387+
.menu-item {
388+
justify-content: flex-start !important;
389+
}
390+
391+
.menu-item svg {
392+
margin-right: 0.5rem !important;
393+
}
394+
395+
.project-picker-text,
396+
.project-picker-chevron,
397+
.user-menu-chevron,
398+
.sidebar-expanded-logo {
399+
display: block !important;
400+
}
401+
402+
.user-menu-text {
403+
display: flex !important;
404+
}
405+
406+
.sidebar-logo-text {
407+
max-width: 50px !important;
408+
margin-right: 4px !important;
409+
opacity: 1 !important;
410+
}
411+
412+
.sidebar-version-chip {
413+
opacity: 0.5 !important;
414+
transition-delay: 200ms !important;
415+
}
416+
417+
#project-picker-wrapper,
418+
#user-menu-wrapper {
419+
display: block !important;
420+
}
421+
422+
#project-picker-trigger,
423+
#user-menu-trigger {
424+
width: 100% !important;
425+
gap: 0.5rem !important;
426+
}
427+
}
428+
429+
/* Sidebar collapsed state - hide text */
430+
#sidebar-panel.sidebar-collapsed {
431+
transition: width 200ms cubic-bezier(0.4, 0, 0.2, 1), box-shadow 200ms cubic-bezier(0.4, 0, 0.2, 1);
432+
433+
/* Hide text elements when collapsed - use opacity/width for smooth animation */
434+
.menu-item-text {
435+
max-width: 0 !important;
436+
opacity: 0 !important;
437+
}
438+
439+
/* Center icons when collapsed */
440+
.menu-item {
441+
justify-content: center;
442+
}
443+
444+
.menu-item svg {
445+
margin-right: 0 !important;
446+
}
447+
448+
.project-picker-text,
449+
.project-picker-chevron,
450+
.user-menu-text,
451+
.user-menu-chevron {
452+
display: none !important;
453+
}
454+
455+
/* Animate logo text to collapse */
456+
.sidebar-logo-text {
457+
max-width: 0 !important;
458+
margin-right: 0 !important;
459+
opacity: 0 !important;
460+
}
461+
462+
/* Hide version chip when collapsed - keep space, just hide content */
463+
.sidebar-version-chip {
464+
opacity: 0 !important;
465+
transition-delay: 0ms !important;
466+
}
467+
468+
/* Center wrappers when collapsed */
469+
#project-picker-wrapper,
470+
#user-menu-wrapper {
471+
display: flex !important;
472+
justify-content: center !important;
473+
}
474+
475+
/* Shrink to fit when collapsed */
476+
#project-picker-trigger,
477+
#user-menu-trigger {
478+
width: auto !important;
479+
gap: 0 !important;
480+
}
481+
482+
/* Hover expansion - show everything, expand width */
483+
/* Overlay when collapsed - matches standardized overlay style */
484+
&::after {
485+
content: "";
486+
position: fixed;
487+
top: 0;
488+
left: 0;
489+
right: 0;
490+
bottom: 0;
491+
background: rgb(17 24 39 / 0.6); /* gray-900/60 */
492+
backdrop-filter: blur(4px); /* backdrop-blur-sm */
493+
pointer-events: none;
494+
opacity: 0;
495+
visibility: hidden;
496+
transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1), visibility 200ms cubic-bezier(0.4, 0, 0.2, 1);
497+
z-index: -1;
498+
}
499+
500+
&:hover {
501+
width: 192px !important;
502+
box-shadow: 8px 0 24px rgba(0, 0, 0, 0.3), 4px 0 8px rgba(0, 0, 0, 0.2);
503+
504+
&::after {
505+
opacity: 1;
506+
visibility: visible;
507+
}
508+
509+
/* Show all text elements on hover */
510+
.menu-item-text {
511+
max-width: 150px !important;
512+
opacity: 1 !important;
513+
}
514+
515+
/* Reset menu item alignment on hover */
516+
.menu-item {
517+
justify-content: flex-start !important;
518+
}
519+
520+
.menu-item svg {
521+
margin-right: 0.5rem !important;
522+
}
523+
524+
.project-picker-text,
525+
.project-picker-chevron,
526+
.user-menu-chevron {
527+
display: block !important;
528+
}
529+
530+
/* User menu button needs flex display */
531+
.user-menu-text {
532+
display: flex !important;
533+
}
534+
535+
/* Animate logo text on hover */
536+
.sidebar-logo-text {
537+
max-width: 50px !important;
538+
margin-right: 4px !important;
539+
opacity: 1 !important;
540+
}
541+
542+
/* Show version chip on hover - fades in after menu animation */
543+
.sidebar-version-chip {
544+
opacity: 0.5 !important;
545+
transition-delay: 200ms !important;
546+
}
547+
548+
/* Restore wrapper and button layout on hover */
549+
#project-picker-wrapper,
550+
#user-menu-wrapper {
551+
display: block !important;
552+
}
553+
554+
#project-picker-trigger,
555+
#user-menu-trigger {
556+
width: 100% !important;
557+
gap: 0.5rem !important;
558+
}
559+
}
560+
}
561+
343562
/* Alerts and form errors used by phx.new */
344563
.alert {
345564
padding: 15px;

assets/js/collaborative-editor/components/Breadcrumbs.tsx

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,34 @@ import { cn } from '../../utils/cn';
55

66
export function Breadcrumbs({ children }: { children: React.ReactNode[] }) {
77
// Split: Last item is always the title (always visible)
8-
// Of the remaining breadcrumbs, show only the last one, hide the rest
9-
const { hiddenItems, visibleBreadcrumb, title } = useMemo(() => {
8+
// Project name (2nd item) is always visible for context when sidebar is collapsed
9+
// Other breadcrumbs go into the dropdown
10+
const { hiddenItems, visibleBreadcrumbs, title } = useMemo(() => {
1011
if (children.length === 0) {
11-
return { hiddenItems: [], visibleBreadcrumb: null, title: null };
12+
return { hiddenItems: [], visibleBreadcrumbs: [], title: null };
1213
}
1314

14-
// Last child is the title
15+
// Last child is the title (workflow name)
1516
const titleItem = children[children.length - 1];
1617
const breadcrumbs = children.slice(0, -1);
1718

18-
if (breadcrumbs.length > 1) {
19-
// Hide all but the last breadcrumb
19+
if (breadcrumbs.length >= 2) {
20+
// Structure: [Projects, ProjectName, Workflows, ...]
21+
// Hide only "Projects" (index 0) in the dropdown
22+
// Show project name (index 1) and everything after as visible
23+
const hiddenItems = breadcrumbs.slice(0, 1); // Only "Projects" link in dropdown
24+
const visibleBreadcrumbs = breadcrumbs.slice(1); // ProjectName, Workflows, etc.
25+
2026
return {
21-
hiddenItems: breadcrumbs.slice(0, -1),
22-
visibleBreadcrumb: breadcrumbs[breadcrumbs.length - 1],
27+
hiddenItems,
28+
visibleBreadcrumbs,
2329
title: titleItem,
2430
};
2531
}
2632

2733
return {
2834
hiddenItems: [],
29-
visibleBreadcrumb: breadcrumbs[0] ?? null,
35+
visibleBreadcrumbs: breadcrumbs,
3036
title: titleItem,
3137
};
3238
}, [children]);
@@ -39,28 +45,28 @@ export function Breadcrumbs({ children }: { children: React.ReactNode[] }) {
3945
result.push(<BreadcrumbDropdown key="dropdown" items={hiddenItems} />);
4046
}
4147

42-
// Add visible breadcrumb (if exists)
43-
if (visibleBreadcrumb) {
44-
// Only show separator if there are hidden items (ellipsis dropdown before this)
45-
if (hiddenItems.length > 0) {
48+
// Add visible breadcrumbs
49+
visibleBreadcrumbs.forEach((breadcrumb, index) => {
50+
// Add separator if there's something before this breadcrumb
51+
if (hiddenItems.length > 0 || index > 0) {
4652
result.push(
4753
<span
48-
key="chevron-breadcrumb"
54+
key={`chevron-breadcrumb-${index}`}
4955
className="hero-chevron-right-mini w-5 h-5 text-secondary-500"
5056
/>
5157
);
5258
}
5359
result.push(
54-
<li key="visible-breadcrumb" className="flex items-center">
55-
{visibleBreadcrumb}
60+
<li key={`visible-breadcrumb-${index}`} className="flex items-center">
61+
{breadcrumb}
5662
</li>
5763
);
58-
}
64+
});
5965

6066
// Add title (with separator only if there's something before it)
6167
if (title) {
62-
// Show separator if there are hidden items OR a visible breadcrumb
63-
if (hiddenItems.length > 0 || visibleBreadcrumb !== null) {
68+
// Show separator if there are hidden items OR visible breadcrumbs
69+
if (hiddenItems.length > 0 || visibleBreadcrumbs.length > 0) {
6470
result.push(
6571
<span
6672
key="chevron-title"
@@ -76,7 +82,7 @@ export function Breadcrumbs({ children }: { children: React.ReactNode[] }) {
7682
}
7783

7884
return result;
79-
}, [hiddenItems, visibleBreadcrumb, title]);
85+
}, [hiddenItems, visibleBreadcrumbs, title]);
8086

8187
return (
8288
<nav className="flex" aria-label="Breadcrumb">

assets/js/collaborative-editor/components/Header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ export function Header({
257257
<EmailVerificationBanner />
258258

259259
<div className="flex-none bg-white shadow-xs border-b border-gray-200 relative z-50">
260-
<div className="mx-auto sm:px-6 lg:px-6 py-6 flex items-center h-20 text-sm">
260+
<div className="mx-auto sm:px-4 lg:px-4 py-6 flex items-center h-20 text-sm">
261261
<Breadcrumbs>{children}</Breadcrumbs>
262262
<ReadOnlyWarning className="ml-3" />
263263
{projectId && workflowId && (

assets/js/collaborative-editor/components/ide/FullScreenIDE.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ export function FullScreenIDE({
801801

802802
{/* IDE Heading Bar */}
803803
<div className="flex-none bg-white border-b border-gray-200">
804-
<div className="flex items-center justify-between px-6 py-2">
804+
<div className="flex items-center justify-between px-4 py-2">
805805
<div className="flex items-center gap-3 flex-1 min-w-0">
806806
{/* Job Selector */}
807807
<div className="shrink-0">

0 commit comments

Comments
 (0)