diff --git a/gui/assets/main.scss b/gui/assets/main.scss index f93216b..9fca136 100644 --- a/gui/assets/main.scss +++ b/gui/assets/main.scss @@ -499,7 +499,7 @@ a:visited { } -.server-card__connect { +.server-card__action { flex-shrink: 0; display: flex; align-items: center; @@ -515,7 +515,7 @@ a:visited { transition: background 0.15s, border-color 0.15s, transform 0.1s; } -.server-card__connect img { +.server-card__action img { width: 20px; height: 20px; filter: brightness(0) invert(0.8); /* light gray */ @@ -523,17 +523,17 @@ a:visited { transition: opacity 0.15s; } -.server-card__connect:hover { +.server-card__action:hover { background: rgba(255, 255, 255, 0.15); border-color: rgba(255, 255, 255, 0.35); transform: scale(1.08); } -.server-card__connect:hover img { +.server-card__action:hover img { opacity: 1.0; } -.server-card__connect:active { +.server-card__action:active { transform: scale(0.95); } @@ -556,3 +556,152 @@ a:visited { border-color: rgba(255, 255, 255, 0.4); color: rgba(255, 255, 255, 0.7); } + + +.modal-backdrop { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.0); + z-index: 999; + animation: backdrop-fade-in 150ms ease-out forwards; +} + +.modal-container { + position: fixed; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + pointer-events: none; +} + + +.modal { + pointer-events: auto; + + /* Make this solid or nearly solid instead of see-through */ + /* Old: background: rgba(255, 255, 255, 0.05); */ + background: #141414; /* or #151822, or rgb(15, 15, 20) */ + + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.45); + + padding: 1.25rem 1.5rem 1.4rem; + width: 500px; + max-width: 90vw; + + color: #fff; + display: flex; + flex-direction: column; + gap: 0.9rem; + + opacity: 0; + transform: scale(0.9); + animation: modal-pop-in 160ms ease-out forwards; +} + +.modal h2 { + font-size: 1.05rem; + font-weight: 600; + text-align: left; + margin: 0; +} + +/* Form layout */ + +.modal-field { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.modal-field label { + font-size: 0.8rem; + opacity: 0.7; +} + +.modal-field input { + padding: 0.55rem 0.6rem; + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.18); + background: rgba(0, 0, 0, 0.35); + color: #fff; + font-size: 0.85rem; + outline: none; + transition: border-color 0.15s, background 0.15s, box-shadow 0.15s; +} + +.modal-field input::placeholder { + color: rgba(255, 255, 255, 0.45); +} + +.modal-field input:focus { + border-color: rgba(255, 255, 255, 0.55); + background: rgba(0, 0, 0, 0.55); + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.25); +} + +/* Actions row */ + +.modal-actions { + display: flex; + justify-content: flex-end; + gap: 0.5rem; + margin-top: 0.5rem; +} + +/* Secondary button (Cancel) */ + +.modal-btn { + padding: 0.5rem 0.9rem; + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.18); + background: rgba(255, 255, 255, 0.07); + color: rgba(255, 255, 255, 0.85); + font-size: 0.85rem; + cursor: pointer; + transition: background 0.15s, border-color 0.15s, transform 0.1s; +} + +.modal-btn:hover { + background: rgba(255, 255, 255, 0.15); + border-color: rgba(255, 255, 255, 0.35); + transform: translateY(-1px); +} + +.modal-btn:active { + transform: translateY(0) scale(0.97); +} + +/* Primary button (Save) */ + +.modal-btn--primary { + background: rgba(67, 156, 255, 0.85); + border-color: rgba(67, 156, 255, 1); + color: #ffffff; +} + +.modal-btn--primary:hover { + background: rgba(92, 174, 255, 0.95); + border-color: rgba(135, 196, 255, 1); +} + +/* Keyframes */ + +@keyframes backdrop-fade-in { + from { background: rgba(0, 0, 0, 0.0); } + to { background: rgba(0, 0, 0, 0.4); } +} + +@keyframes modal-pop-in { + from { + opacity: 0; + transform: scale(0.9); + } + to { + opacity: 1; + transform: scale(1.0); + } +} diff --git a/gui/src/app.rs b/gui/src/app.rs index 47afa65..de63e4c 100644 --- a/gui/src/app.rs +++ b/gui/src/app.rs @@ -777,6 +777,7 @@ pub fn LoginView(config: Resource) -> Element { }, ]; + let mut show_add_modal = use_signal(|| false); let version = option_env!("MUMBLE_WEB2_VERSION"); rsx!( div { @@ -805,25 +806,113 @@ pub fn LoginView(config: Resource) -> Element { span { class: "server-card__address", "{server.address}" } } button { - class: "server-card__connect", + class: "server-card__action", + onclick: move |_| { /* TODO: initiate connection */ }, + img { + src: asset!("assets/edit-3-svgrepo-com.svg"), + alt: "Connect", + } + } + button { + class: "server-card__action", onclick: move |_| { /* TODO: initiate connection */ }, img { src: asset!("assets/arrow-right-svgrepo-com.svg"), alt: "Connect", } } + } } } button { class: "add-server-btn", - onclick: move |_| {}, + onclick: move |_| show_add_modal.set(true), "+ Add Server" } + + // Conditionally render the modal + if *show_add_modal.read() { + AddServerModal { show: show_add_modal } + } } ) } +#[component] +fn AddServerModal(show: Signal) -> Element { + rsx! { + // Full-screen overlay + div { + class: "modal-backdrop", + onclick: move |_| show.set(false), + } + // Centering container + div { + class: "modal-container", + onclick: move |evt| evt.stop_propagation(), + div { + class: "modal", + h2 { "Add Server" } + + div { + class: "modal-field", + label { "Name" } + input { + r#type: "text", + placeholder: "My Mumble Server", + } + } + div { + class: "modal-field", + label { "Address" } + input { + r#type: "text", + placeholder: "mumble.example.com", + } + } + div { + class: "modal-field", + label { "Port" } + input { + r#type: "number", + placeholder: "64738", + } + } + div { + class: "modal-field", + label { "Username" } + input { + r#type: "text", + placeholder: "Nickname", + } + } + div { + class: "modal-field", + label { "Password (optional)" } + input { + r#type: "password", + placeholder: "Password", + } + } + div { + class: "modal-actions", + button { + class: "modal-btn", + onclick: move |_| show.set(false), + "Cancel" + } + button { + class: "modal-btn modal-btn--primary", + onclick: move |_| { /* TODO: save server */ }, + "Save" + } + } + } + } + } +} + pub fn app() -> Element { static STYLE: Asset = asset!("/assets/main.scss");