Sidebar improvement
It now works on mobile
This commit is contained in:
+39
-1
@@ -90,9 +90,46 @@ pub struct SidebarShellProps {
|
|||||||
/// ```
|
/// ```
|
||||||
#[component(SidebarShell)]
|
#[component(SidebarShell)]
|
||||||
fn sidebar_shell(props: &SidebarShellProps) -> Html {
|
fn sidebar_shell(props: &SidebarShellProps) -> Html {
|
||||||
|
let route = use_route::<Route>();
|
||||||
|
let mobile_sidebar_open = use_state(|| false);
|
||||||
|
|
||||||
|
// Close mobile sidebar automatically on any route transition
|
||||||
|
{
|
||||||
|
let mobile_sidebar_open = mobile_sidebar_open.clone();
|
||||||
|
use_effect_with(route, move |_| {
|
||||||
|
mobile_sidebar_open.set(false);
|
||||||
|
|| ()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let on_open = {
|
||||||
|
let mobile_sidebar_open = mobile_sidebar_open.clone();
|
||||||
|
Callback::from(move |_: MouseEvent| mobile_sidebar_open.set(true))
|
||||||
|
};
|
||||||
|
|
||||||
|
let on_close = {
|
||||||
|
let mobile_sidebar_open = mobile_sidebar_open.clone();
|
||||||
|
Callback::from(move |_: ()| mobile_sidebar_open.set(false))
|
||||||
|
};
|
||||||
|
|
||||||
|
let on_close_click = {
|
||||||
|
let on_close = on_close.clone();
|
||||||
|
Callback::from(move |_: MouseEvent| on_close.emit(()))
|
||||||
|
};
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div class="layout">
|
<div class="layout">
|
||||||
<sidebar::Sidebar/>
|
<button class="mobile-menu-toggle" onclick={on_open}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<line x1="4" x2="20" y1="12" y2="12"/>
|
||||||
|
<line x1="4" x2="20" y1="6" y2="6"/>
|
||||||
|
<line x1="4" x2="20" y1="18" y2="18"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class={if *mobile_sidebar_open { "sidebar-overlay open" } else { "sidebar-overlay" }} onclick={on_close_click}></div>
|
||||||
|
|
||||||
|
<sidebar::Sidebar is_open={*mobile_sidebar_open} on_close={on_close} />
|
||||||
<main class="content">
|
<main class="content">
|
||||||
{ for props.children.iter() }
|
{ for props.children.iter() }
|
||||||
</main>
|
</main>
|
||||||
@@ -100,6 +137,7 @@ fn sidebar_shell(props: &SidebarShellProps) -> Html {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Props for the AdminCheckWrapper component.
|
/// Props for the AdminCheckWrapper component.
|
||||||
#[derive(Properties, PartialEq)]
|
#[derive(Properties, PartialEq)]
|
||||||
pub struct AdminCheckWrapperProps {
|
pub struct AdminCheckWrapperProps {
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ pub fn sidebar_state_provider(props: &SidebarProps) -> Html {
|
|||||||
Callback::from(move |v: bool| {
|
Callback::from(move |v: bool| {
|
||||||
state.set(SidebarExpandState {
|
state.set(SidebarExpandState {
|
||||||
ticket_open: v,
|
ticket_open: v,
|
||||||
users_open: (*state).users_open,
|
users_open: state.users_open,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@@ -141,10 +141,10 @@ pub fn sidebar_state_provider(props: &SidebarProps) -> Html {
|
|||||||
let toggle_tickets = {
|
let toggle_tickets = {
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
Callback::from(move |_| {
|
Callback::from(move |_| {
|
||||||
let current = (*state).ticket_open;
|
let current = state.ticket_open;
|
||||||
state.set(SidebarExpandState {
|
state.set(SidebarExpandState {
|
||||||
ticket_open: !current,
|
ticket_open: !current,
|
||||||
users_open: (*state).users_open,
|
users_open: state.users_open,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@@ -153,7 +153,7 @@ pub fn sidebar_state_provider(props: &SidebarProps) -> Html {
|
|||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
Callback::from(move |v: bool| {
|
Callback::from(move |v: bool| {
|
||||||
state.set(SidebarExpandState {
|
state.set(SidebarExpandState {
|
||||||
ticket_open: (*state).ticket_open,
|
ticket_open: state.ticket_open,
|
||||||
users_open: v,
|
users_open: v,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -162,9 +162,9 @@ pub fn sidebar_state_provider(props: &SidebarProps) -> Html {
|
|||||||
let toggle_users = {
|
let toggle_users = {
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
Callback::from(move |_| {
|
Callback::from(move |_| {
|
||||||
let current = (*state).users_open;
|
let current = state.users_open;
|
||||||
state.set(SidebarExpandState {
|
state.set(SidebarExpandState {
|
||||||
ticket_open: (*state).ticket_open,
|
ticket_open: state.ticket_open,
|
||||||
users_open: !current,
|
users_open: !current,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -319,6 +319,11 @@ pub fn users_menu() -> Html {
|
|||||||
/// and administrative status. It fetches the current user's details via `/api/users/current`
|
/// and administrative status. It fetches the current user's details via `/api/users/current`
|
||||||
/// to determine what menu items to display.
|
/// to determine what menu items to display.
|
||||||
///
|
///
|
||||||
|
/// # Mobile Support
|
||||||
|
/// On small screens:
|
||||||
|
/// - Slides into view from the left when `props.is_open` is `true`.
|
||||||
|
/// - Renders a close button (`✕`) in the header that emits `props.on_close`.
|
||||||
|
///
|
||||||
/// # Structure
|
/// # Structure
|
||||||
/// - Wraps its content in a [`SidebarStateProvider`] to allow nested menus to manage their state.
|
/// - Wraps its content in a [`SidebarStateProvider`] to allow nested menus to manage their state.
|
||||||
/// - Contains a navigation (`<nav>`) element with an unordered list (`<ul>`) of menu items.
|
/// - Contains a navigation (`<nav>`) element with an unordered list (`<ul>`) of menu items.
|
||||||
@@ -337,8 +342,22 @@ pub fn users_menu() -> Html {
|
|||||||
/// # Logout Functionality
|
/// # Logout Functionality
|
||||||
/// The "Logout" button sends a GET request to `/api/logout`, clears the user's session,
|
/// The "Logout" button sends a GET request to `/api/logout`, clears the user's session,
|
||||||
/// and then redirects the user to the login page (`crate::Route::Login`).
|
/// and then redirects the user to the login page (`crate::Route::Login`).
|
||||||
|
/// Properties for the [`Sidebar`] component.
|
||||||
|
///
|
||||||
|
/// These properties enable managing and controlling the responsive mobile sidebar
|
||||||
|
/// layout and its visibility settings.
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct SidebarComponentProps {
|
||||||
|
/// A boolean flag indicating whether the mobile sidebar is currently slid open (`true`) or hidden (`false`).
|
||||||
|
#[prop_or_default]
|
||||||
|
pub is_open: bool,
|
||||||
|
/// A callback emitted when the user requests to close/hide the mobile sidebar.
|
||||||
|
#[prop_or_default]
|
||||||
|
pub on_close: Callback<()>,
|
||||||
|
}
|
||||||
|
|
||||||
#[component(Sidebar)]
|
#[component(Sidebar)]
|
||||||
pub fn sidebar() -> Html {
|
pub fn sidebar(props: &SidebarComponentProps) -> Html {
|
||||||
let is_admin = use_state(|| None::<bool>);
|
let is_admin = use_state(|| None::<bool>);
|
||||||
let navigator = use_navigator().expect("Sidebar must be used within a Router");
|
let navigator = use_navigator().expect("Sidebar must be used within a Router");
|
||||||
|
|
||||||
@@ -379,44 +398,65 @@ pub fn sidebar() -> Html {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match *is_admin {
|
match *is_admin {
|
||||||
None => html! { <div class="sidebar-loading">{ "Loading..." }</div> },
|
None => html! { <div class="sidebar-loading">{ "Lade..." }</div> },
|
||||||
|
|
||||||
// Non-admin: render a condensed user sidebar (no diagnostics, limited links)
|
// Non-admin: render a condensed user sidebar (no diagnostics, limited links)
|
||||||
Some(false) => html! {
|
Some(false) => {
|
||||||
|
let on_close = props.on_close.clone();
|
||||||
|
html! {
|
||||||
<SidebarStateProvider>
|
<SidebarStateProvider>
|
||||||
<nav class="sidebar user">
|
<nav class={if props.is_open { "sidebar user open" } else { "sidebar user" }}>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li class="sidebar-header">
|
||||||
<Link<crate::Route> to={crate::Route::Home} classes="home">
|
<Link<crate::Route> to={crate::Route::Home} classes="home">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="home-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" class="home-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
||||||
<polyline points="9 22 9 12 15 12 15 22"/>
|
<polyline points="9 22 9 12 15 12 15 22"/>
|
||||||
</svg>
|
</svg>
|
||||||
</Link<crate::Route>>
|
</Link<crate::Route>>
|
||||||
|
<button class="sidebar-close" onclick={move |_| on_close.emit(())}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<line x1="18" y1="6" x2="6" y2="18"/>
|
||||||
|
<line x1="6" y1="6" x2="18" y2="18"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
<TicketMenu/>
|
<TicketMenu/>
|
||||||
<li class="logout-item">
|
<li class="logout-item">
|
||||||
<button
|
<button
|
||||||
class="logout-button"
|
class="logout-button"
|
||||||
onclick={on_logout.clone()}
|
onclick={on_logout.clone()}
|
||||||
>
|
>
|
||||||
{ "Logout" }
|
{ "Abmelden" }
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</SidebarStateProvider>
|
</SidebarStateProvider>
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Admin: full sidebar wrapped in provider so submenu state persists
|
// Admin: full sidebar wrapped in provider so submenu state persists
|
||||||
Some(true) => html! {
|
Some(true) => {
|
||||||
|
let on_close = props.on_close.clone();
|
||||||
|
html! {
|
||||||
<SidebarStateProvider>
|
<SidebarStateProvider>
|
||||||
<nav class="sidebar admin">
|
<nav class={if props.is_open { "sidebar admin open" } else { "sidebar admin" }}>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li class="sidebar-header">
|
||||||
<Link<crate::Route> to={crate::Route::Home} classes="home">
|
<Link<crate::Route> to={crate::Route::Home} classes="home">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="home-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" class="home-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
||||||
<polyline points="9 22 9 12 15 12 15 22"/>
|
<polyline points="9 22 9 12 15 12 15 22"/>
|
||||||
</svg>
|
</svg>
|
||||||
</Link<crate::Route>>
|
</Link<crate::Route>>
|
||||||
|
<button class="sidebar-close" onclick={move |_| on_close.emit(())}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<line x1="18" y1="6" x2="6" y2="18"/>
|
||||||
|
<line x1="6" y1="6" x2="18" y2="18"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
<TicketMenu/>
|
<TicketMenu/>
|
||||||
<UsersMenu/>
|
<UsersMenu/>
|
||||||
<Link<crate::Route> to={crate::Route::Diagnostics}>{ "Statistiken" }</Link<crate::Route>>
|
<Link<crate::Route> to={crate::Route::Diagnostics}>{ "Statistiken" }</Link<crate::Route>>
|
||||||
@@ -426,12 +466,13 @@ pub fn sidebar() -> Html {
|
|||||||
class="logout-button"
|
class="logout-button"
|
||||||
onclick={on_logout.clone()}
|
onclick={on_logout.clone()}
|
||||||
>
|
>
|
||||||
{ "Logout" }
|
{ "Abmelden" }
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</SidebarStateProvider>
|
</SidebarStateProvider>
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,31 +4,30 @@
|
|||||||
.sidebar {
|
.sidebar {
|
||||||
width: 260px;
|
width: 260px;
|
||||||
background: $color-sidebar;
|
background: $color-sidebar;
|
||||||
color: #fffefe;
|
color: #fff;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
padding: $spacing-md;
|
padding: $spacing-md;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: $spacing-sm; flex: 1; }
|
ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: $spacing-sm; flex: 1; }
|
||||||
|
|
||||||
a, .menu-toggle {
|
a, .menu-toggle {
|
||||||
color: #ffffff;
|
color: #fff;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
display: block;
|
display: block;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
border-radius: 4px;
|
border-radius: $border-radius;
|
||||||
&:hover { background: rgba(158, 45, 1, 0.842); }
|
transition: background 0.2s ease-in-out;
|
||||||
|
&:hover { background: rgba(255,255,255,0.1); }
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-toggle { background: #ff5a316c; border: none; text-align: left; width: 100%; cursor: pointer; }
|
.menu-toggle { background: transparent; border: none; text-align: left; width: 100%; cursor: pointer; }
|
||||||
|
|
||||||
.submenu {
|
.submenu {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
background: rgba(255,255,255,0.02);
|
background: rgba(255,255,255,0.05);
|
||||||
border-radius: 4px;
|
border-radius: $border-radius;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
li { padding: 4px 0; }
|
li { padding: 4px 0; }
|
||||||
}
|
}
|
||||||
@@ -40,29 +39,135 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.logout-button {
|
.logout-button {
|
||||||
background: rgba(255, 93, 43, 0.527);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
color: #ffffff;
|
color: #fff;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.801);
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
&:hover { background: rgba(255,255,255,0.15); }
|
|
||||||
&:active { background: rgba(255,255,255,0.2); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.user { width: 220px; background: darken($color-sidebar, 6%); }
|
.sidebar-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: $spacing-md;
|
||||||
|
|
||||||
|
.home {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
padding-left: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-close {
|
||||||
|
display: none;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background 0.2s ease-in-out;
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
stroke: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.home-svg {
|
.home-svg {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin: 0 auto;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.home {
|
&.user { width: 220px; background: darken($color-sidebar, 6%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Floating mobile menu toggle button
|
||||||
|
.mobile-menu-toggle {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
position: fixed;
|
||||||
|
top: 1rem;
|
||||||
|
left: 1rem;
|
||||||
|
z-index: 998;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid rgba(128, 128, 128, 0.2);
|
||||||
|
background-color: var(--color-container);
|
||||||
|
color: var(--color-text);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
cursor: pointer;
|
||||||
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
stroke: var(--color-text);
|
||||||
|
transition: stroke 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overlay backdrop that dims the screen and allows dismissals on mobile
|
||||||
|
.sidebar-overlay {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
z-index: 999;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.3s ease-in-out;
|
||||||
|
|
||||||
|
&.open {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,20 @@
|
|||||||
@use "components/home";
|
@use "components/home";
|
||||||
@use "components/icon";
|
@use "components/icon";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--color-bg: #ffffff;
|
||||||
|
--color-container: #ffffff;
|
||||||
|
--color-text: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color-bg: #121212;
|
||||||
|
--color-container: #333333;
|
||||||
|
--color-text: #e2e2e2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: variables.$color-bg;
|
background: variables.$color-bg;
|
||||||
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
|
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
|
||||||
@@ -27,7 +41,10 @@ body {
|
|||||||
.sidebar {
|
.sidebar {
|
||||||
width: 260px;
|
width: 260px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
height: 100%;
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
@@ -35,6 +52,7 @@ body {
|
|||||||
.content {
|
.content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
margin-left: 260px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: variables.$spacing-md;
|
padding: variables.$spacing-md;
|
||||||
@@ -46,8 +64,8 @@ body {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: -100%;
|
left: -260px;
|
||||||
transition: left .2s;
|
transition: left .3s ease-in-out;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|||||||
Reference in New Issue
Block a user