Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions frontend/src/main/resources/styling/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -925,3 +925,125 @@ body {
.bell-button.bell-button-active .bell-icon {
animation: bell-ring 1s ease-in-out;
}

/* ========================================
Progressive Disclosure Menu Styles
======================================== */

/* Menu toggle button (collapsed state) */
.menu-collapsed-row {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
opacity: 1;
transform: scale(1);
transition:
opacity 300ms ease,
transform 300ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.menu-collapsed-row.menu-collapsed-hidden {
opacity: 0;
transform: scale(0.8);
pointer-events: none;
position: absolute;
}

.menu-toggle-button {
min-width: 48px;
width: 48px;
height: 40px;
padding: 8px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}

.menu-dots {
font-size: 1.4rem;
font-weight: bold;
letter-spacing: 2px;
line-height: 1;
}

/* Menu expanded row (Share, New, Load) */
.menu-expanded-row {
opacity: 1;
transform: scale(1);
transition:
opacity 300ms ease,
transform 400ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.menu-expanded-row.menu-row-hidden {
opacity: 0;
transform: scale(0.9);
pointer-events: none;
position: absolute;
}

/* Share expanded row (Back, Text, Link) */
.share-expanded-row {
opacity: 1;
transform: translateX(0);
transition:
opacity 300ms ease,
transform 400ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.share-expanded-row.share-row-hidden {
opacity: 0;
transform: translateX(30px);
pointer-events: none;
position: absolute;
}

/* Action button animation class */
.action-button-animate {
transition:
transform 300ms cubic-bezier(0.34, 1.56, 0.64, 1),
opacity 250ms ease,
background-color 200ms ease,
box-shadow 200ms ease;
}

.action-button-animate:active {
transform: scale(0.95);
}

/* Icon-only button (for back arrow) */
.button-icon-only {
min-width: 40px;
width: 40px;
height: 40px;
padding: 8px;
border-radius: 8px;
font-size: 1.2rem;
}

/* Staggered animation for menu items appearing */
.menu-expanded-row .button:nth-child(1) {
transition-delay: 0ms;
}

.menu-expanded-row .button:nth-child(2) {
transition-delay: 50ms;
}

.menu-expanded-row .button:nth-child(3) {
transition-delay: 100ms;
}

.share-expanded-row .button:nth-child(1) {
transition-delay: 0ms;
}

.share-expanded-row .button:nth-child(2) {
transition-delay: 50ms;
}

.share-expanded-row .button:nth-child(3) {
transition-delay: 100ms;
}
110 changes: 51 additions & 59 deletions frontend/src/main/scala/crestedbutte/laminar/Components.scala
Original file line number Diff line number Diff line change
Expand Up @@ -635,11 +635,6 @@ object Components {
)
},
cls := "centered",
// Notification bell button - in its own row above trip buttons
div(
cls := "notification-bell-row",
NotificationBellButton($plan),
),
div(
cls := "trip-button-container",

Expand Down Expand Up @@ -1091,6 +1086,8 @@ object Components {
isLocked.update(!_)
},
),
// Bell notification button - moved to top row
NotificationBellButton($plan),
)

def copyButtons(
Expand All @@ -1103,6 +1100,8 @@ object Components {
focusPlanNameInput: Var[Boolean],
hasSavedPlans: Var[Boolean],
) = {
// Progressive disclosure state: menu collapsed -> menu expanded -> share expanded
val menuExpanded: Var[Boolean] = Var(false)
val shareExpanded: Var[Boolean] = Var(false)
val saveExpanded: Var[Boolean] = Var(false)
val tripName: Var[String] = Var("")
Expand All @@ -1117,6 +1116,7 @@ object Components {
if (
actionContainer != null && !actionContainer.contains(target)
) {
menuExpanded.set(false)
shareExpanded.set(false)
saveExpanded.set(false)
saveConfirmation.set(None)
Expand Down Expand Up @@ -1145,42 +1145,41 @@ object Components {
div(
cls := "action-buttons-container share-save-buttons-container",

// Collapsed state: Share, New, and Load buttons
// Level 0: Collapsed state - just the menu button (⋯)
div(
cls := "expanded-buttons-row collapsed-buttons-row",
cls <-- shareExpanded.signal
.combineWith(saveExpanded.signal)
.map { case (share, save) =>
if (share || save) "delayed-appear" else ""
},
styleProp("opacity") <-- shareExpanded.signal
.combineWith(saveExpanded.signal)
.map { case (share, save) =>
if (share || save) "0" else "1"
},
styleProp(
"pointer-events",
) <-- shareExpanded.signal
.combineWith(saveExpanded.signal)
.map { case (share, save) =>
if (share || save) "none" else "auto"
cls := "menu-collapsed-row",
cls <-- menuExpanded.signal.map(expanded =>
if (expanded) "menu-collapsed-hidden" else "",
),
button(
cls := "button menu-toggle-button",
title := "More actions",
span(cls := "menu-dots", "⋯"),
onClick --> Observer { _ =>
menuExpanded.set(true)
},
styleProp("position") <-- shareExpanded.signal
),
),

// Level 1: Menu expanded - Share, New, Load buttons
div(
cls := "expanded-buttons-row menu-expanded-row",
cls <-- menuExpanded.signal
.combineWith(shareExpanded.signal)
.combineWith(saveExpanded.signal)
.map { case (share, save) =>
if (share || save) "absolute" else "relative"
.map { case (menuOpen, shareOpen, saveOpen) =>
if (!menuOpen || shareOpen || saveOpen) "menu-row-hidden" else ""
},
button(
cls := "button button-fixed-width",
cls := "button button-fixed-width action-button-animate",
SvgIcon.share("share-icon"),
onClick --> Observer { _ =>
shareExpanded.set(true)
saveExpanded.set(false)
},
),
// New button - creates a fresh empty trip
button(
cls := "button button-fixed-width",
cls := "button button-fixed-width action-button-animate",
"New",
onClick --> Observer { _ =>
// Create a fresh empty plan
Expand All @@ -1195,17 +1194,19 @@ object Components {
isLocked.set(false)
// Start in adding new route mode
addingNewRoute.set(true)
menuExpanded.set(false)
},
),
// Load button - dynamically show/hide based on saved plans
child <-- hasSavedPlans.signal.map { hasPlans =>
if (hasPlans) {
button(
cls := "button button-fixed-width",
cls := "button button-fixed-width action-button-animate",
"Load",
onClick --> Observer { _ =>
loadTripsMode.set(true)
addingNewRoute.set(true)
menuExpanded.set(false)
},
)
}
Expand All @@ -1215,28 +1216,24 @@ object Components {
},
),

// Share expanded: Text and Link buttons
// Level 2: Share expanded - Text and Link buttons
div(
cls := "expanded-buttons-row",
styleProp(
"pointer-events",
) <-- shareExpanded.signal
.map(expanded => if (expanded) "auto" else "none",
),
styleProp("position") <-- shareExpanded.signal
.map(expanded =>
if (expanded) "relative" else "absolute",
),
// Text button - emerges from Share position (left side, no translation needed)
cls := "expanded-buttons-row share-expanded-row",
cls <-- shareExpanded.signal.map(expanded =>
if (expanded) "" else "share-row-hidden",
),
// Back button to return to menu level
button(
cls := "button button-fixed-width expand-from-left",
styleProp("transform") <-- shareExpanded.signal
.map(expanded =>
if (expanded) "translateX(0) scale(1)"
else "translateX(60px) scale(0)",
),
styleProp("opacity") <-- shareExpanded.signal
.map(expanded => if (expanded) "1" else "0"),
cls := "button button-icon-only action-button-animate",
title := "Back",
span("←"),
onClick --> Observer { _ =>
shareExpanded.set(false)
},
),
// Text button
button(
cls := "button button-fixed-width action-button-animate",
"Text",
onClick --> Observer { _ =>
val text = plan.plainTextRepresentation
Expand All @@ -1260,18 +1257,12 @@ object Components {
.writeText(text)
}
shareExpanded.set(false)
menuExpanded.set(false)
},
),
// Link button - emerges from Share position (needs to slide right to its final position)
// Link button
button(
cls := "button button-fixed-width expand-from-right",
styleProp("transform") <-- shareExpanded.signal
.map(expanded =>
if (expanded) "translateX(0) scale(1)"
else "translateX(-120px) scale(0)",
),
styleProp("opacity") <-- shareExpanded.signal
.map(expanded => if (expanded) "1" else "0"),
cls := "button button-fixed-width action-button-animate",
"Link",
onClick --> Observer { _ =>
val url =
Expand Down Expand Up @@ -1300,6 +1291,7 @@ object Components {
.writeText(url)
}
shareExpanded.set(false)
menuExpanded.set(false)
},
),
),
Expand Down