atmosphere explorer
0
fork

Configure Feed

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

rework modal animation

Juliet 3ee6b8b0 2643f090

+358 -358
+63 -67
src/auth/account.tsx
··· 126 126 scopeFlow.cancel(); 127 127 }} 128 128 alignTop 129 + contentClass="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-88 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md dark:border-neutral-700" 129 130 > 130 - <div class="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-88 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0"> 131 - <Show when={!scopeFlow.showScopeSelector() && !showingAddAccount()}> 132 - <div class="mb-2 px-1 font-semibold"> 133 - <span>Manage accounts</span> 134 - </div> 135 - <div class="mb-3 max-h-80 overflow-y-auto md:max-h-100"> 136 - <For each={Object.keys(sessions)}> 137 - {(did) => ( 138 - <div class="flex w-full items-center justify-between"> 139 - <A 140 - href={`/at://${did}`} 141 - onClick={() => setOpenManager(false)} 142 - class="flex items-center rounded-md p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 131 + <Show when={!scopeFlow.showScopeSelector() && !showingAddAccount()}> 132 + <div class="mb-2 px-1 font-semibold"> 133 + <span>Manage accounts</span> 134 + </div> 135 + <div class="mb-3 max-h-80 overflow-y-auto md:max-h-100"> 136 + <For each={Object.keys(sessions)}> 137 + {(did) => ( 138 + <div class="flex w-full items-center justify-between"> 139 + <A 140 + href={`/at://${did}`} 141 + onClick={() => setOpenManager(false)} 142 + class="flex items-center rounded-md p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 143 + > 144 + <Show 145 + when={avatars[did as Did]} 146 + fallback={<span class="iconify lucide--user-round m-0.5 size-5"></span>} 143 147 > 144 - <Show 145 - when={avatars[did as Did]} 146 - fallback={<span class="iconify lucide--user-round m-0.5 size-5"></span>} 147 - > 148 - <img 149 - src={getThumbnailUrl(avatars[did as Did])} 150 - class="size-6 rounded-full" 151 - /> 152 - </Show> 153 - </A> 154 - <button 155 - class="flex grow items-center justify-between gap-1 truncate rounded-md p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 156 - onclick={() => handleAccountClick(did as Did)} 157 - > 158 - <span class="truncate">{sessions[did]?.handle || did}</span> 159 - <Show when={did === agent()?.sub && sessions[did].signedIn}> 160 - <span class="iconify lucide--circle-check shrink-0 text-blue-500 dark:text-blue-400"></span> 161 - </Show> 162 - <Show when={!sessions[did].signedIn}> 163 - <span class="iconify lucide--circle-alert shrink-0 text-red-500 dark:text-red-400"></span> 164 - </Show> 165 - </button> 166 - <AccountDropdown 167 - did={did as Did} 168 - onEditPermissions={(accountDid) => scopeFlow.initiateWithRedirect(accountDid)} 169 - /> 170 - </div> 171 - )} 172 - </For> 173 - </div> 174 - <button 175 - onclick={() => setShowingAddAccount(true)} 176 - class="flex w-full items-center justify-center gap-2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-100 px-3 py-2 hover:bg-neutral-200 active:bg-neutral-300 dark:border-neutral-600 dark:bg-neutral-800 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 177 - > 178 - <span class="iconify lucide--plus"></span> 179 - <span>Add account</span> 180 - </button> 181 - </Show> 148 + <img src={getThumbnailUrl(avatars[did as Did])} class="size-6 rounded-full" /> 149 + </Show> 150 + </A> 151 + <button 152 + class="flex grow items-center justify-between gap-1 truncate rounded-md p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 153 + onclick={() => handleAccountClick(did as Did)} 154 + > 155 + <span class="truncate">{sessions[did]?.handle || did}</span> 156 + <Show when={did === agent()?.sub && sessions[did].signedIn}> 157 + <span class="iconify lucide--circle-check shrink-0 text-blue-500 dark:text-blue-400"></span> 158 + </Show> 159 + <Show when={!sessions[did].signedIn}> 160 + <span class="iconify lucide--circle-alert shrink-0 text-red-500 dark:text-red-400"></span> 161 + </Show> 162 + </button> 163 + <AccountDropdown 164 + did={did as Did} 165 + onEditPermissions={(accountDid) => scopeFlow.initiateWithRedirect(accountDid)} 166 + /> 167 + </div> 168 + )} 169 + </For> 170 + </div> 171 + <button 172 + onclick={() => setShowingAddAccount(true)} 173 + class="flex w-full items-center justify-center gap-2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-100 px-3 py-2 hover:bg-neutral-200 active:bg-neutral-300 dark:border-neutral-600 dark:bg-neutral-800 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 174 + > 175 + <span class="iconify lucide--plus"></span> 176 + <span>Add account</span> 177 + </button> 178 + </Show> 182 179 183 - <Show when={showingAddAccount() && !scopeFlow.showScopeSelector()}> 184 - <Login onCancel={() => setShowingAddAccount(false)} /> 185 - </Show> 180 + <Show when={showingAddAccount() && !scopeFlow.showScopeSelector()}> 181 + <Login onCancel={() => setShowingAddAccount(false)} /> 182 + </Show> 186 183 187 - <Show when={scopeFlow.showScopeSelector()}> 188 - <ScopeSelector 189 - initialScopes={parseScopeString( 190 - sessions[scopeFlow.pendingAccount()]?.grantedScopes || "", 191 - )} 192 - onConfirm={scopeFlow.complete} 193 - onCancel={() => { 194 - scopeFlow.cancel(); 195 - setShowingAddAccount(false); 196 - }} 197 - /> 198 - </Show> 199 - </div> 184 + <Show when={scopeFlow.showScopeSelector()}> 185 + <ScopeSelector 186 + initialScopes={parseScopeString( 187 + sessions[scopeFlow.pendingAccount()]?.grantedScopes || "", 188 + )} 189 + onConfirm={scopeFlow.complete} 190 + onCancel={() => { 191 + scopeFlow.cancel(); 192 + setShowingAddAccount(false); 193 + }} 194 + /> 195 + </Show> 200 196 </Modal> 201 197 <button 202 198 onclick={() => setOpenManager(true)}
+2 -2
src/components/create/confirm-submit.tsx
··· 27 27 }; 28 28 29 29 return ( 30 - <div class="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-[24rem] rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0"> 30 + <> 31 31 <div class="flex flex-col gap-3 text-sm"> 32 32 <h2 class="font-semibold">{props.isCreate ? "Create" : "Edit"} record</h2> 33 33 <div class="flex flex-col gap-1.5"> ··· 90 90 </Button> 91 91 </div> 92 92 </div> 93 - </div> 93 + </> 94 94 ); 95 95 };
+2 -2
src/components/create/file-upload.tsx
··· 50 50 }; 51 51 52 52 return ( 53 - <div class="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-[20rem] rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0"> 53 + <> 54 54 <h2 class="mb-2 font-semibold">Upload blob</h2> 55 55 <div class="flex flex-col gap-2 text-sm"> 56 56 <div class="flex flex-col gap-1"> ··· 97 97 </Show> 98 98 </div> 99 99 </div> 100 - </div> 100 + </> 101 101 ); 102 102 };
+2 -2
src/components/create/handle-input.tsx
··· 40 40 }; 41 41 42 42 return ( 43 - <div class="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-[20rem] rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0"> 43 + <> 44 44 <h2 class="mb-2 font-semibold">Insert DID from handle</h2> 45 45 <form ref={handleFormRef} onSubmit={resolveDid} class="flex flex-col gap-2 text-sm"> 46 46 <div class="flex flex-col gap-1"> ··· 82 82 </Show> 83 83 </div> 84 84 </form> 85 - </div> 85 + </> 86 86 ); 87 87 };
+192 -197
src/components/create/index.tsx
··· 236 236 closeOnClick={false} 237 237 nonBlocking={isMinimized()} 238 238 alignTop 239 + contentClass={`dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto flex flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md dark:border-neutral-700 ${isMaximized() ? "w-[calc(100%-1rem)] max-w-7xl h-[85vh]" : "w-[calc(100%-1rem)] max-w-3xl h-[65vh]"} ${isMinimized() ? "hidden" : ""}`} 239 240 > 240 - <div 241 - classList={{ 242 - "dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto flex flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-all duration-200 dark:border-neutral-700 starting:opacity-0": true, 243 - "w-[calc(100%-1rem)] max-w-3xl h-[65vh]": !isMaximized(), 244 - "w-[calc(100%-1rem)] max-w-7xl h-[85vh]": isMaximized(), 245 - hidden: isMinimized(), 246 - }} 247 - > 248 - <div class="mb-2 flex w-full justify-between text-base"> 249 - <div class="flex items-center gap-2"> 250 - <span class="font-semibold select-none"> 251 - {props.create ? "Creating" : "Editing"} record 252 - </span> 253 - </div> 254 - <div class="flex items-center gap-1"> 255 - <button 256 - type="button" 257 - onclick={() => setIsMinimized(true)} 258 - class="flex items-center rounded-lg p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 241 + <div class="mb-2 flex w-full justify-between text-base"> 242 + <div class="flex items-center gap-2"> 243 + <span class="font-semibold select-none"> 244 + {props.create ? "Creating" : "Editing"} record 245 + </span> 246 + </div> 247 + <div class="flex items-center gap-1"> 248 + <button 249 + type="button" 250 + onclick={() => setIsMinimized(true)} 251 + class="flex items-center rounded-lg p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 252 + > 253 + <span class="iconify lucide--minus"></span> 254 + </button> 255 + <button 256 + type="button" 257 + onclick={() => setIsMaximized(!isMaximized())} 258 + class="flex items-center rounded-lg p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 259 + > 260 + <span 261 + class={`iconify ${isMaximized() ? "lucide--minimize-2" : "lucide--maximize-2"}`} 262 + ></span> 263 + </button> 264 + <button 265 + id="close" 266 + onclick={() => setOpenDialog(false)} 267 + class="flex items-center rounded-lg p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 268 + > 269 + <span class="iconify lucide--x"></span> 270 + </button> 271 + </div> 272 + </div> 273 + <form ref={formRef} class="flex min-h-0 flex-1 flex-col gap-y-2"> 274 + <Show when={props.create}> 275 + <div class="flex flex-wrap items-center gap-1 text-sm"> 276 + <span>at://</span> 277 + <select 278 + class="dark:bg-dark-100 max-w-40 truncate rounded-lg border-[0.5px] border-neutral-300 bg-white px-1 py-1 select-none focus:outline-[1px] focus:outline-neutral-600 dark:border-neutral-600 dark:focus:outline-neutral-400" 279 + name="repo" 280 + id="repo" 259 281 > 260 - <span class="iconify lucide--minus"></span> 261 - </button> 262 - <button 263 - type="button" 264 - onclick={() => setIsMaximized(!isMaximized())} 265 - class="flex items-center rounded-lg p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 266 - > 267 - <span 268 - class={`iconify ${isMaximized() ? "lucide--minimize-2" : "lucide--maximize-2"}`} 269 - ></span> 270 - </button> 271 - <button 272 - id="close" 273 - onclick={() => setOpenDialog(false)} 274 - class="flex items-center rounded-lg p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 275 - > 276 - <span class="iconify lucide--x"></span> 277 - </button> 282 + <For each={Object.keys(sessions)}> 283 + {(session) => ( 284 + <option value={session} selected={session === agent()?.sub}> 285 + {sessions[session].handle ?? session} 286 + </option> 287 + )} 288 + </For> 289 + </select> 290 + <span>/</span> 291 + <TextInput 292 + id="collection" 293 + name="collection" 294 + placeholder="Collection (default: $type)" 295 + class={`w-40 placeholder:text-xs lg:w-52 ${collectionError() ? "border-red-500 focus:outline-red-500 dark:border-red-400 dark:focus:outline-red-400" : ""}`} 296 + onInput={(e) => { 297 + const value = e.currentTarget.value; 298 + if (!value || isNsid(value)) setCollectionError(""); 299 + else 300 + setCollectionError( 301 + "Invalid collection: use reverse domain format (e.g. app.bsky.feed.post)", 302 + ); 303 + }} 304 + /> 305 + <span>/</span> 306 + <TextInput 307 + id="rkey" 308 + name="rkey" 309 + placeholder="Record key (default: TID)" 310 + class={`w-40 placeholder:text-xs lg:w-52 ${rkeyError() ? "border-red-500 focus:outline-red-500 dark:border-red-400 dark:focus:outline-red-400" : ""}`} 311 + onInput={(e) => { 312 + const value = e.currentTarget.value; 313 + if (!value || isRecordKey(value)) setRkeyError(""); 314 + else setRkeyError("Invalid record key: 1-512 chars, use a-z A-Z 0-9 . _ ~ : -"); 315 + }} 316 + /> 278 317 </div> 318 + <Show when={collectionError() || rkeyError()}> 319 + <div class="text-xs text-red-500 dark:text-red-400"> 320 + <div>{collectionError()}</div> 321 + <div>{rkeyError()}</div> 322 + </div> 323 + </Show> 324 + </Show> 325 + <div class="min-h-0 flex-1"> 326 + <Suspense 327 + fallback={ 328 + <div class="flex h-full items-center justify-center"> 329 + <span class="iconify lucide--loader-circle animate-spin text-xl"></span> 330 + </div> 331 + } 332 + > 333 + <Editor 334 + content={JSON.stringify( 335 + !props.create ? props.record 336 + : params.rkey ? placeholder() 337 + : defaultPlaceholder(), 338 + null, 339 + 2, 340 + )} 341 + /> 342 + </Suspense> 279 343 </div> 280 - <form ref={formRef} class="flex min-h-0 flex-1 flex-col gap-y-2"> 281 - <Show when={props.create}> 282 - <div class="flex flex-wrap items-center gap-1 text-sm"> 283 - <span>at://</span> 284 - <select 285 - class="dark:bg-dark-100 max-w-40 truncate rounded-lg border-[0.5px] border-neutral-300 bg-white px-1 py-1 select-none focus:outline-[1px] focus:outline-neutral-600 dark:border-neutral-600 dark:focus:outline-neutral-400" 286 - name="repo" 287 - id="repo" 344 + <div class="flex flex-col gap-2"> 345 + <Show when={notice()}> 346 + <div class="text-sm text-red-500 dark:text-red-400">{notice()}</div> 347 + </Show> 348 + <div class="flex justify-between gap-2"> 349 + <div class="relative" ref={insertMenuRef}> 350 + <button 351 + type="button" 352 + class="dark:hover:bg-dark-200 dark:shadow-dark-700 dark:active:bg-dark-100 flex w-fit rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-1.5 text-base shadow-xs hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800" 353 + onClick={() => setOpenInsertMenu(!openInsertMenu())} 288 354 > 289 - <For each={Object.keys(sessions)}> 290 - {(session) => ( 291 - <option value={session} selected={session === agent()?.sub}> 292 - {sessions[session].handle ?? session} 293 - </option> 294 - )} 295 - </For> 296 - </select> 297 - <span>/</span> 298 - <TextInput 299 - id="collection" 300 - name="collection" 301 - placeholder="Collection (default: $type)" 302 - class={`w-40 placeholder:text-xs lg:w-52 ${collectionError() ? "border-red-500 focus:outline-red-500 dark:border-red-400 dark:focus:outline-red-400" : ""}`} 303 - onInput={(e) => { 304 - const value = e.currentTarget.value; 305 - if (!value || isNsid(value)) setCollectionError(""); 306 - else 307 - setCollectionError( 308 - "Invalid collection: use reverse domain format (e.g. app.bsky.feed.post)", 309 - ); 310 - }} 311 - /> 312 - <span>/</span> 313 - <TextInput 314 - id="rkey" 315 - name="rkey" 316 - placeholder="Record key (default: TID)" 317 - class={`w-40 placeholder:text-xs lg:w-52 ${rkeyError() ? "border-red-500 focus:outline-red-500 dark:border-red-400 dark:focus:outline-red-400" : ""}`} 318 - onInput={(e) => { 319 - const value = e.currentTarget.value; 320 - if (!value || isRecordKey(value)) setRkeyError(""); 321 - else setRkeyError("Invalid record key: 1-512 chars, use a-z A-Z 0-9 . _ ~ : -"); 355 + <span class="iconify lucide--plus select-none"></span> 356 + </button> 357 + <Show when={openInsertMenu()}> 358 + <div class="dark:bg-dark-300 dark:shadow-dark-700 absolute bottom-full left-0 z-10 mb-1 flex w-40 flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-1.5 shadow-md dark:border-neutral-700"> 359 + <MenuItem 360 + icon="lucide--id-card" 361 + label="Insert DID" 362 + onClick={insertDidFromHandle} 363 + /> 364 + <MenuItem 365 + icon="lucide--clock" 366 + label="Insert timestamp" 367 + onClick={insertTimestamp} 368 + /> 369 + <button 370 + type="button" 371 + class={ 372 + hasUserScope("blob") ? 373 + "flex items-center gap-2 rounded-md p-2 text-left text-xs hover:bg-neutral-100 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 374 + : "flex items-center gap-2 rounded-md p-2 text-left text-xs opacity-40" 375 + } 376 + onClick={() => { 377 + if (hasUserScope("blob")) { 378 + setOpenInsertMenu(false); 379 + blobInput.click(); 380 + } 381 + }} 382 + > 383 + <span class="iconify lucide--upload shrink-0"></span> 384 + <span>Upload blob{hasUserScope("blob") ? "" : " (permission needed)"}</span> 385 + </button> 386 + </div> 387 + </Show> 388 + <input 389 + type="file" 390 + id="blob" 391 + class="sr-only" 392 + ref={blobInput} 393 + onChange={(e) => { 394 + if (e.target.files !== null) setOpenUpload(true); 322 395 }} 323 396 /> 324 397 </div> 325 - <Show when={collectionError() || rkeyError()}> 326 - <div class="text-xs text-red-500 dark:text-red-400"> 327 - <div>{collectionError()}</div> 328 - <div>{rkeyError()}</div> 329 - </div> 330 - </Show> 331 - </Show> 332 - <div class="min-h-0 flex-1"> 333 - <Suspense 334 - fallback={ 335 - <div class="flex h-full items-center justify-center"> 336 - <span class="iconify lucide--loader-circle animate-spin text-xl"></span> 337 - </div> 338 - } 398 + <Modal 399 + open={openUpload()} 400 + onClose={() => setOpenUpload(false)} 401 + closeOnClick={false} 402 + contentClass="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-[20rem] rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md dark:border-neutral-700" 339 403 > 340 - <Editor 341 - content={JSON.stringify( 342 - !props.create ? props.record 343 - : params.rkey ? placeholder() 344 - : defaultPlaceholder(), 345 - null, 346 - 2, 347 - )} 348 - /> 349 - </Suspense> 350 - </div> 351 - <div class="flex flex-col gap-2"> 352 - <Show when={notice()}> 353 - <div class="text-sm text-red-500 dark:text-red-400">{notice()}</div> 354 - </Show> 355 - <div class="flex justify-between gap-2"> 356 - <div class="relative" ref={insertMenuRef}> 357 - <button 358 - type="button" 359 - class="dark:hover:bg-dark-200 dark:shadow-dark-700 dark:active:bg-dark-100 flex w-fit rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-1.5 text-base shadow-xs hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800" 360 - onClick={() => setOpenInsertMenu(!openInsertMenu())} 361 - > 362 - <span class="iconify lucide--plus select-none"></span> 363 - </button> 364 - <Show when={openInsertMenu()}> 365 - <div class="dark:bg-dark-300 dark:shadow-dark-700 absolute bottom-full left-0 z-10 mb-1 flex w-40 flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-1.5 shadow-md dark:border-neutral-700"> 366 - <MenuItem 367 - icon="lucide--id-card" 368 - label="Insert DID" 369 - onClick={insertDidFromHandle} 370 - /> 371 - <MenuItem 372 - icon="lucide--clock" 373 - label="Insert timestamp" 374 - onClick={insertTimestamp} 375 - /> 376 - <button 377 - type="button" 378 - class={ 379 - hasUserScope("blob") ? 380 - "flex items-center gap-2 rounded-md p-2 text-left text-xs hover:bg-neutral-100 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 381 - : "flex items-center gap-2 rounded-md p-2 text-left text-xs opacity-40" 382 - } 383 - onClick={() => { 384 - if (hasUserScope("blob")) { 385 - setOpenInsertMenu(false); 386 - blobInput.click(); 387 - } 388 - }} 389 - > 390 - <span class="iconify lucide--upload shrink-0"></span> 391 - <span>Upload blob{hasUserScope("blob") ? "" : " (permission needed)"}</span> 392 - </button> 393 - </div> 394 - </Show> 395 - <input 396 - type="file" 397 - id="blob" 398 - class="sr-only" 399 - ref={blobInput} 400 - onChange={(e) => { 401 - if (e.target.files !== null) setOpenUpload(true); 402 - }} 403 - /> 404 - </div> 405 - <Modal 406 - open={openUpload()} 404 + <FileUpload 405 + file={blobInput.files![0]} 406 + blobInput={blobInput} 407 407 onClose={() => setOpenUpload(false)} 408 - closeOnClick={false} 409 - > 410 - <FileUpload 411 - file={blobInput.files![0]} 412 - blobInput={blobInput} 413 - onClose={() => setOpenUpload(false)} 414 - /> 415 - </Modal> 416 - <Modal 417 - open={openHandleDialog()} 418 - onClose={() => setOpenHandleDialog(false)} 419 - closeOnClick={false} 420 - > 421 - <HandleInput onClose={() => setOpenHandleDialog(false)} /> 422 - </Modal> 423 - <Modal 424 - open={openConfirmDialog()} 408 + /> 409 + </Modal> 410 + <Modal 411 + open={openHandleDialog()} 412 + onClose={() => setOpenHandleDialog(false)} 413 + closeOnClick={false} 414 + contentClass="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-[20rem] rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md dark:border-neutral-700" 415 + > 416 + <HandleInput onClose={() => setOpenHandleDialog(false)} /> 417 + </Modal> 418 + <Modal 419 + open={openConfirmDialog()} 420 + onClose={() => setOpenConfirmDialog(false)} 421 + closeOnClick={false} 422 + contentClass="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-[24rem] rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md dark:border-neutral-700" 423 + > 424 + <ConfirmSubmit 425 + isCreate={props.create} 426 + onConfirm={(validate, recreate) => { 427 + if (props.create) { 428 + createRecord(validate); 429 + } else { 430 + editRecord(validate, recreate); 431 + } 432 + }} 425 433 onClose={() => setOpenConfirmDialog(false)} 426 - closeOnClick={false} 427 - > 428 - <ConfirmSubmit 429 - isCreate={props.create} 430 - onConfirm={(validate, recreate) => { 431 - if (props.create) { 432 - createRecord(validate); 433 - } else { 434 - editRecord(validate, recreate); 435 - } 436 - }} 437 - onClose={() => setOpenConfirmDialog(false)} 438 - /> 439 - </Modal> 440 - <div class="flex items-center justify-end gap-2"> 441 - <Button onClick={() => setOpenConfirmDialog(true)}> 442 - {props.create ? "Create..." : "Edit..."} 443 - </Button> 444 - </div> 434 + /> 435 + </Modal> 436 + <div class="flex items-center justify-end gap-2"> 437 + <Button onClick={() => setOpenConfirmDialog(true)}> 438 + {props.create ? "Create..." : "Edit..."} 439 + </Button> 445 440 </div> 446 441 </div> 447 - </form> 448 - </div> 442 + </div> 443 + </form> 449 444 </Modal> 450 445 <Show when={isMinimized() && openDialog()}> 451 446 <button
+6 -1
src/components/modal.tsx
··· 6 6 closeOnClick?: boolean; 7 7 nonBlocking?: boolean; 8 8 alignTop?: boolean; 9 + contentClass?: string; 9 10 } 10 11 11 12 export const Modal = (props: ModalProps) => { ··· 54 55 } 55 56 }} 56 57 > 57 - {props.children} 58 + <div 59 + class={`transition-all ease-in starting:scale-95 starting:opacity-0 ${props.contentClass ?? ""}`} 60 + > 61 + {props.children} 62 + </div> 58 63 </div> 59 64 </Show> 60 65 );
+17 -15
src/components/permission-prompt.tsx
··· 27 27 }; 28 28 29 29 return ( 30 - <Modal open={requestedScope() !== null} onClose={() => setRequestedScope(null)}> 31 - <div class="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-[calc(100%-2rem)] max-w-md rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0"> 32 - <h2 class="mb-2 font-semibold">Permission required</h2> 33 - <p class="mb-4 text-sm text-neutral-600 dark:text-neutral-400"> 34 - You need the "{scopeLabel()}" permission to perform this action. 35 - </p> 36 - <div class="flex justify-end gap-2"> 37 - <Button onClick={() => setRequestedScope(null)}>Cancel</Button> 38 - <Button 39 - onClick={handleEditPermissions} 40 - class="dark:shadow-dark-700 rounded-lg bg-blue-500 px-2 py-1.5 text-xs text-white shadow-xs select-none hover:bg-blue-600 active:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-500 dark:active:bg-blue-400" 41 - > 42 - Edit permissions 43 - </Button> 44 - </div> 30 + <Modal 31 + open={requestedScope() !== null} 32 + onClose={() => setRequestedScope(null)} 33 + contentClass="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-[calc(100%-2rem)] max-w-md rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md dark:border-neutral-700" 34 + > 35 + <h2 class="mb-2 font-semibold">Permission required</h2> 36 + <p class="mb-4 text-sm text-neutral-600 dark:text-neutral-400"> 37 + You need the "{scopeLabel()}" permission to perform this action. 38 + </p> 39 + <div class="flex justify-end gap-2"> 40 + <Button onClick={() => setRequestedScope(null)}>Cancel</Button> 41 + <Button 42 + onClick={handleEditPermissions} 43 + class="dark:shadow-dark-700 rounded-lg bg-blue-500 px-2 py-1.5 text-xs text-white shadow-xs select-none hover:bg-blue-600 active:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-500 dark:active:bg-blue-400" 44 + > 45 + Edit permissions 46 + </Button> 45 47 </div> 46 48 </Modal> 47 49 );
+2 -6
src/components/search.tsx
··· 196 196 }; 197 197 198 198 return ( 199 - <Modal open={showSearch()} onClose={() => setShowSearch(false)} alignTop> 200 - <div class="dark:bg-dark-200 dark:shadow-dark-700 pointer-events-auto mx-3 w-full max-w-lg rounded-lg border-[0.5px] border-neutral-300 bg-white shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0"> 199 + <Modal open={showSearch()} onClose={() => setShowSearch(false)} alignTop contentClass="dark:bg-dark-200 dark:shadow-dark-700 pointer-events-auto mx-3 w-full max-w-lg rounded-lg border-[0.5px] border-neutral-300 bg-white shadow-md dark:border-neutral-700"> 201 200 <form 202 201 class="w-full" 203 202 onsubmit={(e) => { ··· 345 344 </div> 346 345 </Show> 347 346 </form> 348 - </div> 349 347 </Modal> 350 348 ); 351 349 }; ··· 361 359 362 360 return ( 363 361 <> 364 - <Modal open={openList()} onClose={() => setOpenList(false)} alignTop> 365 - <div class="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-88 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 sm:w-104 dark:border-neutral-700 starting:opacity-0"> 362 + <Modal open={openList()} onClose={() => setOpenList(false)} alignTop contentClass="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-88 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md sm:w-104 dark:border-neutral-700"> 366 363 <div class="mb-2 flex items-center gap-1 font-semibold"> 367 364 <span class="iconify lucide--link"></span> 368 365 <span>Supported URLs</span> ··· 395 392 }} 396 393 </For> 397 394 </div> 398 - </div> 399 395 </Modal> 400 396 <button 401 397 type="button"
+17 -15
src/views/collection.tsx
··· 300 300 /> 301 301 </Show> 302 302 </div> 303 - <Modal open={openDelete()} onClose={() => setOpenDelete(false)}> 304 - <div class="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0"> 305 - <h2 class="mb-2 font-semibold"> 306 - {recreate() ? "Recreate" : "Delete"}{" "} 307 - {records.filter((r) => r.toDelete).length} records? 308 - </h2> 309 - <div class="flex justify-end gap-2"> 310 - <Button onClick={() => setOpenDelete(false)}>Cancel</Button> 311 - <Button 312 - onClick={deleteRecords} 313 - class={`dark:shadow-dark-700 rounded-lg px-2 py-1.5 text-xs text-white shadow-xs select-none ${recreate() ? "bg-green-500 hover:bg-green-600 active:bg-green-700 dark:bg-green-600 dark:hover:bg-green-700 dark:active:bg-green-800" : "bg-red-500 hover:bg-red-600 active:bg-red-700"}`} 314 - > 315 - {recreate() ? "Recreate" : "Delete"} 316 - </Button> 317 - </div> 303 + <Modal 304 + open={openDelete()} 305 + onClose={() => setOpenDelete(false)} 306 + contentClass="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md dark:border-neutral-700" 307 + > 308 + <h2 class="mb-2 font-semibold"> 309 + {recreate() ? "Recreate" : "Delete"}{" "} 310 + {records.filter((r) => r.toDelete).length} records? 311 + </h2> 312 + <div class="flex justify-end gap-2"> 313 + <Button onClick={() => setOpenDelete(false)}>Cancel</Button> 314 + <Button 315 + onClick={deleteRecords} 316 + class={`dark:shadow-dark-700 rounded-lg px-2 py-1.5 text-xs text-white shadow-xs select-none ${recreate() ? "bg-green-500 hover:bg-green-600 active:bg-green-700 dark:bg-green-600 dark:hover:bg-green-700 dark:active:bg-green-800" : "bg-red-500 hover:bg-red-600 active:bg-red-700"}`} 317 + > 318 + {recreate() ? "Recreate" : "Delete"} 319 + </Button> 318 320 </div> 319 321 </Modal> 320 322 </Show>
+41 -39
src/views/pds.tsx
··· 81 81 > 82 82 <span class="iconify lucide--info text-neutral-600 dark:text-neutral-400"></span> 83 83 </button> 84 - <Modal open={openInfo()} onClose={() => setOpenInfo(false)}> 85 - <div class="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-max max-w-[90vw] rounded-lg border-[0.5px] border-neutral-300 bg-white p-3 shadow-md transition-opacity duration-200 sm:max-w-xl dark:border-neutral-700 starting:opacity-0"> 86 - <div class="mb-2 flex items-center justify-between gap-4"> 87 - <p class="truncate font-semibold">{repo.did}</p> 88 - <button 89 - onclick={() => setOpenInfo(false)} 90 - class="flex shrink-0 items-center rounded-md p-1.5 text-neutral-500 hover:bg-neutral-100 hover:text-neutral-700 active:bg-neutral-200 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-200 dark:active:bg-neutral-600" 91 - > 92 - <span class="iconify lucide--x"></span> 93 - </button> 94 - </div> 95 - <div class="grid grid-cols-[auto_1fr] items-baseline gap-x-1 gap-y-0.5 text-sm"> 96 - <span class="font-medium">Head:</span> 97 - <span class="wrap-anywhere text-neutral-700 dark:text-neutral-300">{repo.head}</span> 84 + <Modal 85 + open={openInfo()} 86 + onClose={() => setOpenInfo(false)} 87 + contentClass="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-max max-w-[90vw] rounded-lg border-[0.5px] border-neutral-300 bg-white p-3 shadow-md sm:max-w-xl dark:border-neutral-700" 88 + > 89 + <div class="mb-2 flex items-center justify-between gap-4"> 90 + <p class="truncate font-semibold">{repo.did}</p> 91 + <button 92 + onclick={() => setOpenInfo(false)} 93 + class="flex shrink-0 items-center rounded-md p-1.5 text-neutral-500 hover:bg-neutral-100 hover:text-neutral-700 active:bg-neutral-200 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-200 dark:active:bg-neutral-600" 94 + > 95 + <span class="iconify lucide--x"></span> 96 + </button> 97 + </div> 98 + <div class="grid grid-cols-[auto_1fr] items-baseline gap-x-1 gap-y-0.5 text-sm"> 99 + <span class="font-medium">Head:</span> 100 + <span class="wrap-anywhere text-neutral-700 dark:text-neutral-300">{repo.head}</span> 98 101 99 - <Show when={TID.validate(repo.rev)}> 100 - <span class="font-medium">Rev:</span> 101 - <div class="flex gap-1"> 102 - <span class="text-neutral-700 dark:text-neutral-300">{repo.rev}</span> 103 - <span class="text-neutral-600 dark:text-neutral-400">·</span> 104 - <span class="text-neutral-600 dark:text-neutral-400"> 105 - {localDateFromTimestamp(TID.parse(repo.rev).timestamp / 1000)} 106 - </span> 107 - </div> 108 - </Show> 102 + <Show when={TID.validate(repo.rev)}> 103 + <span class="font-medium">Rev:</span> 104 + <div class="flex gap-1"> 105 + <span class="text-neutral-700 dark:text-neutral-300">{repo.rev}</span> 106 + <span class="text-neutral-600 dark:text-neutral-400">·</span> 107 + <span class="text-neutral-600 dark:text-neutral-400"> 108 + {localDateFromTimestamp(TID.parse(repo.rev).timestamp / 1000)} 109 + </span> 110 + </div> 111 + </Show> 109 112 110 - <Show when={repo.active !== undefined}> 111 - <span class="font-medium">Active:</span> 112 - <span 113 - class={`iconify self-center ${ 114 - repo.active ? 115 - "lucide--check text-green-500 dark:text-green-400" 116 - : "lucide--x text-red-500 dark:text-red-400" 117 - }`} 118 - ></span> 119 - </Show> 113 + <Show when={repo.active !== undefined}> 114 + <span class="font-medium">Active:</span> 115 + <span 116 + class={`iconify self-center ${ 117 + repo.active ? 118 + "lucide--check text-green-500 dark:text-green-400" 119 + : "lucide--x text-red-500 dark:text-red-400" 120 + }`} 121 + ></span> 122 + </Show> 120 123 121 - <Show when={repo.status}> 122 - <span class="font-medium">Status:</span> 123 - <span class="text-neutral-700 dark:text-neutral-300">{repo.status}</span> 124 - </Show> 125 - </div> 124 + <Show when={repo.status}> 125 + <span class="font-medium">Status:</span> 126 + <span class="text-neutral-700 dark:text-neutral-300">{repo.status}</span> 127 + </Show> 126 128 </div> 127 129 </Modal> 128 130 </div>
+14 -12
src/views/record.tsx
··· 418 418 > 419 419 <span class="iconify lucide--trash-2"></span> 420 420 </PermissionButton> 421 - <Modal open={openDelete()} onClose={() => setOpenDelete(false)}> 422 - <div class="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0"> 423 - <h2 class="mb-2 font-semibold">Delete this record?</h2> 424 - <div class="flex justify-end gap-2"> 425 - <Button onClick={() => setOpenDelete(false)}>Cancel</Button> 426 - <Button 427 - onClick={deleteRecord} 428 - class="dark:shadow-dark-700 rounded-lg bg-red-500 px-2 py-1.5 text-xs text-white shadow-xs select-none hover:bg-red-400 active:bg-red-400" 429 - > 430 - Delete 431 - </Button> 432 - </div> 421 + <Modal 422 + open={openDelete()} 423 + onClose={() => setOpenDelete(false)} 424 + contentClass="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md dark:border-neutral-700" 425 + > 426 + <h2 class="mb-2 font-semibold">Delete this record?</h2> 427 + <div class="flex justify-end gap-2"> 428 + <Button onClick={() => setOpenDelete(false)}>Cancel</Button> 429 + <Button 430 + onClick={deleteRecord} 431 + class="dark:shadow-dark-700 rounded-lg bg-red-500 px-2 py-1.5 text-xs text-white shadow-xs select-none hover:bg-red-400 active:bg-red-400" 432 + > 433 + Delete 434 + </Button> 433 435 </div> 434 436 </Modal> 435 437 </Show>