A focused Docker Compose management web application.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: mobile navbar

Brooke 2b9e1763 71c5883c

+150 -40
+2
packages/panel/src/lib/index.ts
··· 2 2 import { faCircleXmark } from "@fortawesome/free-regular-svg-icons"; 3 3 import { addToast } from "../routes/Toaster.svelte"; 4 4 5 + export { openDialog, closeDialog } from "../routes/Dialog.svelte"; 6 + 5 7 export * as api from "./api"; 6 8 7 9 export function trim(str: string, maxLength: number) {
+4
packages/panel/src/routes/(authenticated)/+layout.svelte
··· 12 12 <style lang="scss"> 13 13 .container { 14 14 display: flex; 15 + 16 + @media (max-width: 425px) { 17 + flex-direction: column; 18 + } 15 19 } 16 20 </style>
+138 -39
packages/panel/src/routes/(authenticated)/Navbar.svelte
··· 1 + <!-- 2 + @component 3 + 4 + A Svelte component containing both the desktop and mobile Navbar. 5 + 6 + --> 7 + 1 8 <script lang="ts"> 2 9 import { page } from "$app/state"; 3 10 import Fa from "svelte-fa"; ··· 8 15 faGear, 9 16 faLayerGroup, 10 17 faMagnifyingGlass, 18 + faXmark, 11 19 } from "@fortawesome/free-solid-svg-icons"; 20 + import { slide } from "svelte/transition"; 21 + import { onNavigate } from "$app/navigation"; 12 22 13 23 const EXPANDED_KEY = "luminary-navbar-expanded"; 14 24 ··· 19 29 ] satisfies { icon: any; label: string; href: string }[]; 20 30 21 31 let expanded = $state(localStorage.getItem(EXPANDED_KEY) === "true"); 32 + let open = $state(false); 22 33 23 - let clientWidth = $state(0); 34 + let navbarWidth = $state(0); 35 + let windowWidth = $state(0); 36 + 37 + let mobile = $derived(windowWidth <= 425); 24 38 25 39 function toggleExpanded() { 26 40 expanded = !expanded; 27 41 localStorage.setItem(EXPANDED_KEY, expanded.toString()); 28 42 } 43 + 44 + function toggleOpen() { 45 + open = !open; 46 + } 47 + 48 + onNavigate(() => { 49 + open = false; 50 + }); 29 51 </script> 30 52 31 - <div style:width="{clientWidth}px"></div> 53 + <svelte:window bind:innerWidth={windowWidth} /> 32 54 33 - <nav class:expanded bind:clientWidth> 55 + {#snippet links()} 34 56 {#each PAGES as { icon, label, href }} 35 - <a {href} class:current={page.url.pathname.startsWith(href)}> 57 + <a class="entry" {href} class:current={page.url.pathname.startsWith(href)}> 36 58 <div class="icon"> 37 59 <Fa {icon} /> 38 60 </div> 39 61 <div class="label">{label}</div> 40 62 </a> 41 63 {/each} 64 + {/snippet} 42 65 43 - <button class="a" style="margin-top: auto"> 44 - <div class="icon"> 45 - <Fa icon={faMagnifyingGlass} /> 66 + {#if mobile} 67 + <div style:height="48px"></div> 68 + 69 + <nav class:open> 70 + <div class="titlebar"> 71 + <button class="a" onclick={toggleOpen}> 72 + <Fa icon={open ? faXmark : faBars} /> 73 + </button> 74 + <a href="./"> 75 + <Fa icon={faChevronLeft} /> 76 + </a> 46 77 </div> 47 - <div class="label">Search</div> 48 - </button> 78 + {#if open} 79 + <div class="list" transition:slide> 80 + <div class="flexc expanded">{@render links()}</div> 81 + </div> 82 + {/if} 83 + </nav> 84 + {:else} 85 + <div style:width="{navbarWidth}px"></div> 49 86 50 - <button class="a" onclick={toggleExpanded} aria-label="{expanded ? 'collapse' : 'expand'} sidebar"> 51 - <div class="icon"> 52 - <Fa icon={expanded ? faChevronLeft : faBars} /> 53 - </div> 54 - <div class="label">Collapse</div> 55 - </button> 56 - </nav> 87 + <nav class:expanded bind:clientWidth={navbarWidth}> 88 + {@render links()} 89 + 90 + <button class="a entry" style="margin-top: auto"> 91 + <div class="icon"> 92 + <Fa icon={faMagnifyingGlass} /> 93 + </div> 94 + <div class="label">Search</div> 95 + </button> 96 + 97 + <button class="a entry" onclick={toggleExpanded} aria-label="{expanded ? 'collapse' : 'expand'} sidebar"> 98 + <div class="icon"> 99 + <Fa icon={expanded ? faChevronLeft : faBars} /> 100 + </div> 101 + <div class="label">Collapse</div> 102 + </button> 103 + </nav> 104 + {/if} 57 105 58 106 <style lang="scss"> 59 107 $navbar-width: 125px; 60 108 61 109 nav { 62 110 background-color: var(--crust); 111 + 112 + position: fixed; 113 + left: 0; 114 + top: 0; 63 115 64 116 display: flex; 65 117 flex-direction: column; 66 - gap: 10px; 118 + 119 + @media (max-width: 425px) { 120 + background-color: var(--crust); 121 + 122 + transition: height 250ms ease; 123 + height: 48px; 124 + width: 100%; 67 125 68 - transition: width 250ms ease; 69 - height: 100dvh; 70 - width: 48px; 126 + &.open { 127 + height: 100dvh; 128 + width: 100%; 129 + } 130 + } 71 131 72 - position: fixed; 73 - left: 0; 74 - top: 0; 132 + @media (min-width: 426px) { 133 + transition: width 250ms ease; 134 + height: 100dvh; 135 + width: 48px; 75 136 76 - & > * { 77 - justify-content: left; 78 - align-items: center; 79 - display: flex; 137 + &.expanded { 138 + width: #{$navbar-width}; 139 + } 140 + } 141 + } 80 142 81 - padding: 0; 143 + .titlebar { 144 + justify-content: space-between; 145 + flex-direction: row-reverse; 146 + align-items: center; 147 + display: flex; 82 148 83 - height: 48px; 84 - border-radius: 0; 149 + flex-basis: 48px; 150 + height: 50px; 151 + width: 100%; 152 + 153 + & > * { 154 + width: 48px; 155 + height: 100%; 85 156 157 + font-size: 20px; 86 158 color: var(--subtext0); 87 159 88 - &:hover, 89 - &.current { 90 - color: var(--mauve); 91 - } 160 + display: flex; 161 + justify-content: center; 162 + align-items: center; 92 163 } 164 + } 93 165 94 - &.expanded { 95 - width: #{$navbar-width}; 166 + .list { 167 + transition: flex-basis 250ms ease; 168 + flex-basis: 0px; 169 + overflow: hidden; 170 + } 96 171 97 - & > * > .label { 98 - flex-basis: #{$navbar-width - 48px}; 99 - } 172 + .open .list { 173 + flex-basis: calc(100% - 48px); 174 + } 175 + 176 + .entry { 177 + justify-content: left; 178 + align-items: center; 179 + display: flex; 180 + 181 + padding: 0; 182 + 183 + height: 48px; 184 + border-radius: 0; 185 + 186 + color: var(--subtext0); 187 + 188 + &:hover, 189 + &.current { 190 + color: var(--mauve); 100 191 } 192 + 193 + &.current { 194 + pointer-events: none; 195 + } 196 + } 197 + 198 + .expanded .label { 199 + flex-basis: #{$navbar-width - 48px}; 101 200 } 102 201 103 202 .icon {
+1
packages/panel/src/routes/(authenticated)/settings/+page.svelte
··· 1 + <h1>Hello World</h1>
+5 -1
packages/panel/src/routes/Dialog.svelte
··· 98 98 min-width: 75vw; 99 99 min-height: 25vh; 100 100 101 + padding: 10px; 102 + 101 103 transition: ease 300ms; 102 104 scale: 0.95; 103 105 ··· 146 148 display: flex; 147 149 gap: 20px; 148 150 151 + margin-bottom: 10px; 152 + 149 153 h1 { 150 154 font-size: 25px; 151 155 152 156 @media (max-width: 425px) { 153 - font-size: inherit; 157 + font-size: 20px; 154 158 } 155 159 } 156 160