Yōten: A social tracker for your language learning journey built on the atproto.

feat: redesign study session component

brookjeynes.dev c2713e96 f4dc1962

verified
+107 -109
+3
input.css
··· 55 55 } 56 56 57 57 @utility pill { 58 + display: flex; 59 + justify-content: center; 60 + align-items: center; 58 61 padding: theme(spacing.1) theme(spacing.4); 59 62 border-radius: theme(borderRadius.full); 60 63 font-size: theme(text.sm);
+2 -7
internal/server/views/partials/reactions.templ
··· 42 42 } 43 43 <div class="inline-block text-left w-fit"> 44 44 <button @click="open = !open" id="reaction-button" type="button" class="btn rounded-full hover:bg-bg py-1 px-2"> 45 - if !params.HasReactions { 46 - <i class="w-4 h-4" data-lucide="plus"></i> 47 - } else { 48 - <i class="w-4 h-4" data-lucide="heart"></i> 49 - <span class="font-normal">Be the first to react</span> 50 - } 45 + <i class="w-5 h-5" data-lucide="smile-plus"></i> 51 46 </button> 52 47 <div 53 48 x-show="open" ··· 66 61 class="btn flex-col gap-1 hover:bg-primary-surface rounded-md" 67 62 hx-vals={ templ.JSONString(ReactionHxVals{ReactionID: reaction.ID}) } 68 63 hx-disabled-elt="#reaction-button" 64 + title={ reaction.Label } 69 65 > 70 66 <span>{ reaction.Emoji }</span> 71 - <span class="text-xs text-text-muted">{ reaction.Label }</span> 72 67 </button> 73 68 } 74 69 </div>
+102 -102
internal/server/views/partials/study-session.templ
··· 31 31 } 32 32 } 33 33 34 + templ studySessionAction(params StudySessionProps) { 35 + <div class="flex justify-between sm:justify-normal gap-2 w-auto"> 36 + <details class="relative inline-block text-left"> 37 + <summary class="cursor-pointer list-none"> 38 + <div class="btn btn-muted p-2"> 39 + <i class="w-4 h-4 flex-shrink-0" data-lucide="ellipsis"></i> 40 + </div> 41 + </summary> 42 + <div 43 + class="absolute flex flex-col right-0 mt-2 p-1 gap-1 rounded w-32 bg-bg-light border border-bg-dark" 44 + > 45 + <button id="edit-button" type="button" class="w-full"> 46 + <a 47 + href={ templ.URL(fmt.Sprintf("/session/edit/%s", params.StudySession.Rkey)) } 48 + class="text-base text-text flex items-center px-4 py-2 text-sm hover:bg-bg gap-2" 49 + > 50 + <i class="w-4 h-4" data-lucide="square-pen"></i> 51 + Edit 52 + </a> 53 + </button> 54 + <button 55 + class="text-base text-red-600 flex items-center px-4 py-2 text-sm hover:bg-bg gap-2 group" 56 + type="button" 57 + id="delete-button" 58 + hx-disabled-elt="delete-button,#edit-button" 59 + hx-delete={ templ.URL(fmt.Sprintf("/session/%s", params.StudySession.Rkey)) } 60 + > 61 + <i class="w-4 h-4" data-lucide="trash-2"></i> 62 + Delete 63 + <i class="w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" data-lucide="loader-circle"></i> 64 + </button> 65 + </div> 66 + </details> 67 + </div> 68 + } 69 + 34 70 templ StudySession(params StudySessionProps) { 35 71 {{ elementId := SanitiseHtmlId(fmt.Sprintf("study-session-%s-%s", params.StudySession.Did, params.StudySession.Rkey)) }} 36 72 <div id={ elementId } class="card relative" x-data="{ open: false }" :class="{ 'z-20': open }"> 37 73 <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3"> 38 - <div class="flex items-center gap-3"> 39 - if params.StudySession.BskyProfile.Avatar == "" { 40 - <div class="flex items-center justify-center w-10 h-10 rounded-full bg-primary"> 41 - <i class="w-7 h-7" data-lucide="user"></i> 42 - </div> 43 - } else { 44 - <img src={ params.StudySession.BskyProfile.Avatar } class="w-10 h-10 rounded-full"/> 45 - } 46 - <div> 47 - <div class="flex items-center gap-2"> 48 - <a href={ templ.URL(fmt.Sprintf("/@%s", params.StudySession.Did)) } class="font-semibold"> 49 - { params.StudySession.ProfileDisplayName } 50 - </a> 51 - <p class="pill pill-secondary h-fit items-center justify-center gap-1 w-fit hidden sm:flex"> 52 - <i class="w-3.5 h-3.5" data-lucide="star"></i> 53 - <span class="text-xs">Level { params.StudySession.ProfileLevel }</span> 54 - </p> 74 + <div class="flex items-center justify-between"> 75 + <div class="flex items-center gap-3"> 76 + if params.StudySession.BskyProfile.Avatar == "" { 77 + <div class="flex items-center justify-center w-10 h-10 rounded-full bg-primary"> 78 + <i class="w-7 h-7" data-lucide="user"></i> 79 + </div> 80 + } else { 81 + <img src={ params.StudySession.BskyProfile.Avatar } class="w-10 h-10 rounded-full"/> 82 + } 83 + <div> 84 + <div class="flex items-center gap-2"> 85 + <a href={ templ.URL(fmt.Sprintf("/@%s", params.StudySession.Did)) } class="font-semibold"> 86 + { params.StudySession.ProfileDisplayName } 87 + </a> 88 + <p class="pill pill-secondary px-2 py-0.5 h-fit items-center justify-center gap-1 w-fit flex"> 89 + <i class="w-3.5 h-3.5" data-lucide="star"></i> 90 + <span class="text-xs">{ params.StudySession.ProfileLevel }</span> 91 + </p> 92 + </div> 93 + <p class="text-text-muted text-sm">&commat;{ params.StudySession.BskyProfile.Handle }</p> 55 94 </div> 56 - <p class="text-text-muted text-sm">&commat;{ params.StudySession.BskyProfile.Handle }</p> 57 95 </div> 58 - </div> 59 - if params.DoesOwn { 60 - <div class="flex justify-between sm:justify-normal gap-2 w-auto"> 61 - <div class="pill pill-secondary flex items-center gap-1 mr-2 w-fit"> 62 - <i class="w-4 h-4" data-lucide="zap"></i> 63 - <span class="font-medium text-sm">+{ params.StudySession.XpGained }</span> 96 + if params.DoesOwn { 97 + <div class="block sm:hidden"> 98 + @studySessionAction(params) 64 99 </div> 65 - <details class="relative inline-block text-left"> 66 - <summary class="cursor-pointer list-none"> 67 - <div class="btn btn-muted p-2"> 68 - <i class="w-4 h-4 flex-shrink-0" data-lucide="ellipsis"></i> 69 - </div> 70 - </summary> 71 - <div 72 - class="absolute flex flex-col right-0 mt-2 p-1 gap-1 rounded w-32 bg-bg-light border border-bg-dark" 73 - > 74 - <button id="edit-button" type="button" class="w-full"> 75 - <a 76 - href={ templ.URL(fmt.Sprintf("/session/edit/%s", params.StudySession.Rkey)) } 77 - class="text-base text-text flex items-center px-4 py-2 text-sm hover:bg-bg gap-2" 78 - > 79 - <i class="w-4 h-4" data-lucide="square-pen"></i> 80 - Edit 81 - </a> 82 - </button> 83 - <button 84 - class="text-base text-red-600 flex items-center px-4 py-2 text-sm hover:bg-bg gap-2 group" 85 - type="button" 86 - id="delete-button" 87 - hx-disabled-elt="delete-button,#edit-button" 88 - hx-delete={ templ.URL(fmt.Sprintf("/session/%s", params.StudySession.Rkey)) } 89 - > 90 - <i class="w-4 h-4" data-lucide="trash-2"></i> 91 - Delete 92 - <i class="w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" data-lucide="loader-circle"></i> 93 - </button> 94 - </div> 95 - </details> 100 + } 101 + </div> 102 + <div class="flex gap-4"> 103 + <div class="pill pill-primary w-fit"> 104 + { params.StudySession.Language.Flag } 105 + { params.StudySession.Language.Name } 106 + if params.StudySession.Language.NativeName != nil { 107 + ({ *params.StudySession.Language.NativeName }) 108 + } 96 109 </div> 97 - } 98 - </div> 99 - <p class="pill pill-secondary h-fit flex items-center justify-center w-fit gap-1 sm:hidden"> 100 - <i class="w-3.5 h-3.5" data-lucide="star"></i> 101 - <span class="text-xs">Level { params.StudySession.ProfileLevel }</span> 102 - </p> 103 - <div class="flex sm:items-center flex-col sm:flex-row gap-2"> 104 - <div class="pill pill-primary w-fit"> 105 - { params.StudySession.Language.Flag } 106 - { params.StudySession.Language.Name } 107 - if params.StudySession.Language.NativeName != nil { 108 - ({ *params.StudySession.Language.NativeName }) 110 + if params.DoesOwn { 111 + <div class="hidden sm:block"> 112 + @studySessionAction(params) 113 + </div> 109 114 } 110 115 </div> 111 - <span class="text-text-muted hidden sm:block">•</span> 112 - <span class="font-medium">{ params.StudySession.StudySession.Activity.Name }</span> 113 116 </div> 114 - <div class="flex items-center gap-4 text-sm text-text-muted"> 115 - <div class="flex items-center gap-1"> 116 - <i class="w-4 h-4" data-lucide="clock"></i> 117 - <span>{ params.StudySession.StudySession.Duration.String() }</span> 118 - </div> 119 - <div class="flex items-center gap-1"> 120 - <i class="w-4 h-4" data-lucide="calendar"></i> 121 - <span>{ params.StudySession.StudySession.Date.Format("January 2, 2006") }</span> 122 - </div> 123 - </div> 117 + <span class="font-medium">{ params.StudySession.StudySession.Activity.Name }</span> 124 118 if params.StudySession.StudySession.Description != "" { 125 119 <p class="whitespace-pre-wrap leading-relaxed"> 126 120 { params.StudySession.StudySession.Description } 127 121 </p> 128 122 } 129 123 if params.StudySession.Resource != nil { 130 - <hr class="border-gray"/> 131 - <div class="flex flex-col sm:flex-row sm:items-center gap-2 p-2 bg-gray-light rounded-md"> 124 + <a 125 + rel="noopener noreferrer" 126 + target="_blank" 127 + href={ templ.URL(*params.StudySession.Resource.Link) } 128 + aria-label={ fmt.Sprintf("Visit %s", *params.StudySession.Resource.Link) } 129 + title={ fmt.Sprintf("Visit %s", *params.StudySession.Resource.Link) } 130 + class="flex flex-col sm:flex-row sm:items-center gap-2 py-2 px-4 bg-gray-light hover:opacity-75 rounded-md" 131 + > 132 132 <div class="flex items-center gap-2 flex-1 min-w-0"> 133 133 <i class="w-4 h-4 flex-shrink-0" data-lucide={ getResourceIcon(params.StudySession.Resource.Type) }></i> 134 134 <span title={ params.StudySession.Resource.Description } class="font-medium text-sm truncate">{ params.StudySession.Resource.Title }</span> ··· 137 137 {{ parsedURL, err := url.Parse(*params.StudySession.Resource.Link) }} 138 138 if err == nil { 139 139 {{ hostname := strings.TrimPrefix(parsedURL.Hostname(), "www.") }} 140 - <div class="flex justify-between sm:items-center"> 141 - <div title={ *params.StudySession.Resource.Link } class="pill pill-primary"> 142 - { hostname } 143 - </div> 144 - <a 145 - class="btn w-fit" 146 - rel="noopener noreferrer" 147 - target="_blank" 148 - href={ templ.URL(*params.StudySession.Resource.Link) } 149 - aria-label={ fmt.Sprintf("Visit %s", *params.StudySession.Resource.Link) } 150 - title={ fmt.Sprintf("Visit %s", *params.StudySession.Resource.Link) } 151 - > 152 - <i class="w-4 h-4" data-lucide="square-arrow-out-up-right"></i> 153 - </a> 140 + <div title={ *params.StudySession.Resource.Link }> 141 + { hostname } 154 142 </div> 155 143 } 156 144 } 157 - </div> 145 + </a> 158 146 } 159 147 <hr class="border-gray"/> 160 - @NewReactions(NewReactionsProps{ 161 - User: params.User, 162 - SessionDid: params.StudySession.Did, 163 - SessionRkey: params.StudySession.Rkey, 164 - ReactionEvents: params.StudySession.Reactions, 165 - }) 148 + <div class="flex flex-col sm:flex-row justify-between sm:items-center gap-4"> 149 + @NewReactions(NewReactionsProps{ 150 + User: params.User, 151 + SessionDid: params.StudySession.Did, 152 + SessionRkey: params.StudySession.Rkey, 153 + ReactionEvents: params.StudySession.Reactions, 154 + }) 155 + <div class="flex flex-col sm:flex-row sm:items-center gap-2 text-sm text-text-muted"> 156 + <div class="flex items-center gap-1"> 157 + <i class="w-4 h-4" data-lucide="clock"></i> 158 + <span>{ params.StudySession.StudySession.Duration.String() }</span> 159 + </div> 160 + <div class="flex items-center gap-1"> 161 + <i class="w-4 h-4" data-lucide="calendar"></i> 162 + <span>{ params.StudySession.StudySession.Date.Format("January 2, 2006") }</span> 163 + </div> 164 + </div> 165 + </div> 166 166 </div> 167 167 }