atmosphere explorer
at main 96 lines 3.5 kB view raw
1import { createSignal, For } from "solid-js"; 2import { buildScopeString, GRANULAR_SCOPES, scopeIdsToString } from "./scope-utils"; 3 4interface ScopeSelectorProps { 5 onConfirm: (scopeString: string, scopeIds: string) => void; 6 onCancel: () => void; 7 initialScopes?: Set<string>; 8} 9 10export const ScopeSelector = (props: ScopeSelectorProps) => { 11 const [selectedScopes, setSelectedScopes] = createSignal<Set<string>>( 12 props.initialScopes || new Set(["create", "update", "delete", "blob"]), 13 ); 14 15 const isBlobDisabled = () => { 16 const scopes = selectedScopes(); 17 return !scopes.has("create") && !scopes.has("update"); 18 }; 19 20 const toggleScope = (scopeId: string) => { 21 setSelectedScopes((prev) => { 22 const newSet = new Set(prev); 23 if (newSet.has(scopeId)) { 24 newSet.delete(scopeId); 25 if ( 26 (scopeId === "create" || scopeId === "update") && 27 !newSet.has("create") && 28 !newSet.has("update") 29 ) { 30 newSet.delete("blob"); 31 } 32 } else { 33 newSet.add(scopeId); 34 } 35 return newSet; 36 }); 37 }; 38 39 const handleConfirm = () => { 40 const scopes = selectedScopes(); 41 const scopeString = buildScopeString(scopes); 42 const scopeIds = scopeIdsToString(scopes); 43 props.onConfirm(scopeString, scopeIds); 44 }; 45 46 return ( 47 <div class="flex flex-col gap-y-3"> 48 <div class="flex items-center gap-2"> 49 <button 50 onclick={props.onCancel} 51 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" 52 > 53 <span class="iconify lucide--arrow-left"></span> 54 </button> 55 <div class="font-semibold">Select permissions</div> 56 </div> 57 <div class="flex flex-col px-1"> 58 <For each={GRANULAR_SCOPES}> 59 {(scope) => { 60 const isSelected = () => selectedScopes().has(scope.id); 61 const isDisabled = () => scope.id === "blob" && isBlobDisabled(); 62 63 return ( 64 <button 65 onclick={() => !isDisabled() && toggleScope(scope.id)} 66 disabled={isDisabled()} 67 class="group flex items-center gap-3 py-1.5" 68 classList={{ "opacity-50": isDisabled() }} 69 > 70 <div 71 class="flex size-5 items-center justify-center rounded border-2" 72 classList={{ 73 "bg-blue-500 border-transparent group-hover:bg-blue-600 group-active:bg-blue-400": 74 isSelected() && !isDisabled(), 75 "border-neutral-400 dark:border-neutral-500 group-hover:border-neutral-500 dark:group-hover:border-neutral-400 group-hover:bg-neutral-100 dark:group-hover:bg-neutral-800": 76 !isSelected() && !isDisabled(), 77 "border-neutral-300 dark:border-neutral-600": isDisabled(), 78 }} 79 > 80 {isSelected() && <span class="iconify lucide--check text-sm text-white"></span>} 81 </div> 82 <span>{scope.label}</span> 83 </button> 84 ); 85 }} 86 </For> 87 </div> 88 <button 89 onclick={handleConfirm} 90 class="dark:hover:bg-dark-200 dark:active:bg-dark-100 flex w-full items-center justify-center gap-2 rounded-lg border border-neutral-200 px-3 py-2 hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700" 91 > 92 Continue 93 </button> 94 </div> 95 ); 96};