a reactive (signals based) hypermedia web framework (wip) stormlightlabs.github.io/volt/
hypermedia frontend signals
at main 6.8 kB view raw
1/* ========================================================================== */ 2/* Components */ 3/* ========================================================================== */ 4 5/** 6 * Dialog (Modal) 7 * 8 * Native <dialog> element provides semantic modal functionality. 9 * Structure: <dialog><article><header>...</header>content<footer>...</footer></article></dialog> 10 * Close buttons should use aria-label="Close" for targeting. 11 */ 12dialog { 13 border: none; 14 padding: 0; 15 margin: auto; 16 max-width: min(90vw, 600px); 17 max-height: 90vh; 18 background-color: var(--color-bg); 19 border-radius: var(--radius-lg); 20 box-shadow: var(--shadow-lg); 21 overflow: hidden; 22} 23 24dialog::backdrop { 25 background-color: rgba(0, 0, 0, 0.5); 26 backdrop-filter: blur(4px); 27} 28 29@media (prefers-color-scheme: dark) { 30 dialog::backdrop { 31 background-color: rgba(0, 0, 0, 0.7); 32 } 33} 34 35dialog article { 36 margin: 0; 37 padding: 0; 38 display: flex; 39 flex-direction: column; 40 max-height: 90vh; 41} 42 43dialog header { 44 position: relative; 45 border-bottom: 1px solid var(--color-border); 46 padding: var(--space-lg); 47 margin: 0; 48} 49 50dialog header h1, 51dialog header h2, 52dialog header h3, 53dialog header h4, 54dialog header h5, 55dialog header h6 { 56 margin-top: 0; 57 margin-bottom: 0; 58 padding-right: var(--space-3xl); 59} 60 61dialog header button[aria-label="Close"], 62dialog header button[aria-label="close"] { 63 position: absolute; 64 top: var(--space-lg); 65 right: var(--space-lg); 66 background: none; 67 border: none; 68 font-size: 1.75rem; 69 line-height: 1; 70 padding: var(--space-xs); 71 margin: 0; 72 width: auto; 73 color: var(--color-text-muted); 74 cursor: pointer; 75 transition: color var(--transition-fast), transform var(--transition-fast); 76} 77 78dialog header button[aria-label="Close"]:hover, 79dialog header button[aria-label="close"]:hover { 80 color: var(--color-text); 81 background: none; 82 transform: scale(1.1); 83} 84 85dialog article > :not(header):not(footer) { 86 padding: var(--space-lg); 87 overflow-y: auto; 88 flex: 1; 89} 90 91dialog form { 92 margin: 0; 93 max-width: none; 94} 95 96dialog footer { 97 border-top: 1px solid var(--color-border); 98 padding: var(--space-lg); 99 margin: 0; 100 display: flex; 101 gap: var(--space-md); 102 justify-content: flex-end; 103 flex-wrap: wrap; 104} 105 106dialog footer button { 107 margin: 0; 108} 109 110@media (max-width: 768px) { 111 dialog { 112 max-width: 95vw; 113 max-height: 95vh; 114 } 115 116 dialog header, 117 dialog article > :not(header):not(footer), 118 dialog footer { 119 padding: var(--space-md); 120 } 121 122 123 dialog header button[aria-label="Close"], 124 dialog header button[aria-label="close"] { 125 top: var(--space-md); 126 right: var(--space-md); 127 } 128} 129 130/** 131 * Accordian 132 * 133 * implemented as details and summary 134 */ 135details { 136 margin: var(--space-lg) 0; 137 padding: var(--space-md); 138 border: 1px solid var(--color-border); 139 border-radius: var(--radius-md); 140 max-width: var(--content-width); 141} 142 143summary { 144 font-weight: 700; 145 cursor: pointer; 146 user-select: none; 147 padding: var(--space-sm); 148 margin: calc(-1 * var(--space-sm)); 149 transition: background-color var(--transition-fast); 150} 151 152summary:hover { 153 background-color: var(--color-bg-alt); 154} 155 156details[open] summary { 157 margin-bottom: var(--space-md); 158 border-bottom: 1px solid var(--color-border); 159} 160 161/** 162 * Tooltips 163 * 164 * Declarative tooltips using data-vx-tooltip attribute. 165 * Placements: top (default), right, bottom, left 166 * Structure: <element data-vx-tooltip="Tooltip text" data-placement="top"> 167 */ 168[data-vx-tooltip] { 169 position: relative; 170 cursor: help; 171} 172 173[data-vx-tooltip]::before, 174[data-vx-tooltip]::after { 175 position: absolute; 176 opacity: 0; 177 pointer-events: none; 178 transition: opacity var(--transition-fast), transform var(--transition-fast); 179 z-index: 1000; 180} 181 182[data-vx-tooltip]::before { 183 content: attr(data-vx-tooltip); 184 background: var(--color-text); 185 color: var(--color-bg); 186 padding: var(--space-sm) var(--space-md); 187 border-radius: var(--radius-sm); 188 font-size: 0.875rem; 189 white-space: normal; 190 min-width: 200px; 191 max-width: 300px; 192 width: max-content; 193 text-align: center; 194 display: -webkit-box; 195 -webkit-box-orient: vertical; 196 -webkit-line-clamp: 4; 197 line-clamp: 4; 198 overflow: hidden; 199 text-overflow: ellipsis; 200 line-height: 1.4; 201} 202 203[data-vx-tooltip]::after { 204 content: ''; 205 border: 6px solid transparent; 206} 207 208[data-vx-tooltip]:hover::before, 209[data-vx-tooltip]:hover::after, 210[data-vx-tooltip]:focus::before, 211[data-vx-tooltip]:focus::after, 212[data-vx-tooltip]:focus-visible::before, 213[data-vx-tooltip]:focus-visible::after { 214 opacity: 1; 215} 216 217/* Placement: Top (default) */ 218[data-vx-tooltip]:not([data-placement])::before, 219[data-vx-tooltip][data-placement="top"]::before { 220 bottom: calc(100% + 12px); 221 left: 50%; 222 transform: translateX(-50%) translateY(4px); 223} 224 225[data-vx-tooltip]:not([data-placement])::after, 226[data-vx-tooltip][data-placement="top"]::after { 227 bottom: calc(100% + 6px); 228 left: 50%; 229 transform: translateX(-50%); 230 border-top-color: var(--color-text); 231} 232 233[data-vx-tooltip]:not([data-placement]):hover::before, 234[data-vx-tooltip][data-placement="top"]:hover::before, 235[data-vx-tooltip]:not([data-placement]):focus::before, 236[data-vx-tooltip][data-placement="top"]:focus::before { 237 transform: translateX(-50%) translateY(0); 238} 239 240/* Placement: Bottom */ 241[data-vx-tooltip][data-placement="bottom"]::before { 242 top: calc(100% + 12px); 243 left: 50%; 244 transform: translateX(-50%) translateY(-4px); 245} 246 247[data-vx-tooltip][data-placement="bottom"]::after { 248 top: calc(100% + 6px); 249 left: 50%; 250 transform: translateX(-50%); 251 border-bottom-color: var(--color-text); 252} 253 254[data-vx-tooltip][data-placement="bottom"]:hover::before, 255[data-vx-tooltip][data-placement="bottom"]:focus::before { 256 transform: translateX(-50%) translateY(0); 257} 258 259[data-vx-tooltip][data-placement="right"]::before { 260 left: calc(100% + 12px); 261 top: 50%; 262 transform: translateY(-50%) translateX(-4px); 263} 264 265[data-vx-tooltip][data-placement="right"]::after { 266 left: calc(100% + 6px); 267 top: 50%; 268 transform: translateY(-50%); 269 border-right-color: var(--color-text); 270} 271 272[data-vx-tooltip][data-placement="right"]:hover::before, 273[data-vx-tooltip][data-placement="right"]:focus::before { 274 transform: translateY(-50%) translateX(0); 275} 276 277[data-vx-tooltip][data-placement="left"]::before { 278 right: calc(100% + 12px); 279 top: 50%; 280 transform: translateY(-50%) translateX(4px); 281} 282 283[data-vx-tooltip][data-placement="left"]::after { 284 right: calc(100% + 6px); 285 top: 50%; 286 transform: translateY(-50%); 287 border-left-color: var(--color-text); 288} 289 290[data-vx-tooltip][data-placement="left"]:hover::before, 291[data-vx-tooltip][data-placement="left"]:focus::before { 292 transform: translateY(-50%) translateX(0); 293} 294 295@media (max-width: 768px) { 296 [data-vx-tooltip]::before, 297 [data-vx-tooltip]::after { 298 display: none; 299 } 300}