From 980346b98d57233f5e3427f595bcb887fbbb2988 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sat, 6 Aug 2022 19:36:02 -0400 Subject: [PATCH] Use grid layout for tables (#38) - Add icon for current language --- src/PrayerTracker.UI/Church.fs | 89 ++++++++------------- src/PrayerTracker.UI/Layout.fs | 10 +-- src/PrayerTracker.UI/PrayerRequest.fs | 52 +++++++----- src/PrayerTracker.UI/SmallGroup.fs | 110 +++++++++++++++----------- src/PrayerTracker.UI/User.fs | 85 ++++++++++---------- src/PrayerTracker/wwwroot/css/app.css | 56 ++++++------- 6 files changed, 206 insertions(+), 196 deletions(-) diff --git a/src/PrayerTracker.UI/Church.fs b/src/PrayerTracker.UI/Church.fs index 0f8ba8e..2adefb9 100644 --- a/src/PrayerTracker.UI/Church.fs +++ b/src/PrayerTracker.UI/Church.fs @@ -2,6 +2,7 @@ open Giraffe.ViewEngine open Giraffe.ViewEngine.Accessibility +open Giraffe.ViewEngine.Htmx open PrayerTracker open PrayerTracker.Entities open PrayerTracker.ViewModels @@ -58,71 +59,49 @@ let edit (model : EditChurch) ctx viewInfo = /// View for church maintenance page let maintain (churches : Church list) (stats : Map) ctx viewInfo = - let s = I18N.localizer.Force () - let vi = - AppViewInfo.withScopedStyles [ - "#churchList { grid-template-columns: repeat(7, auto); }" - ] viewInfo - let chTbl = + let s = I18N.localizer.Force () + let vi = AppViewInfo.withScopedStyles [ "#churchList { grid-template-columns: repeat(7, auto); }" ] viewInfo + let churchTable = match churches with | [] -> space | _ -> - section [ _id "churchList"; _class "pt-data-list"; _ariaLabel "Church list" ] [ - header [] [ locStr s["Actions"] ] - header [] [ locStr s["Name"] ] - header [] [ locStr s["Location"] ] - header [] [ locStr s["Groups"] ] - header [] [ locStr s["Requests"] ] - header [] [ locStr s["Users"] ] - header [] [ locStr s["Interface?"] ] + section [ _id "churchList"; _class "pt-table"; _ariaLabel "Church list" ] [ + div [ _class "row head" ] [ + header [ _class "cell" ] [ locStr s["Actions"] ] + header [ _class "cell" ] [ locStr s["Name"] ] + header [ _class "cell" ] [ locStr s["Location"] ] + header [ _class "cell" ] [ locStr s["Groups"] ] + header [ _class "cell" ] [ locStr s["Requests"] ] + header [ _class "cell" ] [ locStr s["Users"] ] + header [ _class "cell" ] [ locStr s["Interface?"] ] + ] for church in churches do let churchId = shortGuid church.Id.Value let delAction = $"/church/{churchId}/delete" let delPrompt = s["Are you sure you want to delete this {0}? This action cannot be undone.", $"""{s["Church"].Value.ToLower ()} ({church.Name})"""] div [ _class "row" ] [ - div [] [ - a [ _href $"/church/{churchId}/edit"; _title s["Edit This Church"].Value ] [ icon "edit" ] - a [ _href delAction - _title s["Delete This Church"].Value - _onclick $"return PT.confirmDelete('{delAction}','{delPrompt}')" ] [ - icon "delete_forever" + div [ _class "cell actions" ] [ + a [ _href $"/church/{churchId}/edit"; _title s["Edit This Church"].Value ] [ + iconSized 18 "edit" + ] + a [ _href delAction + _title s["Delete This Church"].Value + _hxPost delAction + _hxConfirm delPrompt.Value ] [ + iconSized 18 "delete_forever" ] ] - div [] [ str church.Name ] - div [] [ str church.City; rawText ", "; str church.State ] - div [ _class "pt-right-text" ] [ rawText (stats[churchId].SmallGroups.ToString "N0") ] - div [ _class "pt-right-text" ] [ rawText (stats[churchId].PrayerRequests.ToString "N0") ] - div [ _class "pt-right-text" ] [ rawText (stats[churchId].Users.ToString "N0") ] - div [ _class "pt-center-text" ] [ locStr s[if church.HasVpsInterface then "Yes" else "No"] ] + div [ _class "cell" ] [ str church.Name ] + div [ _class "cell" ] [ str church.City; rawText ", "; str church.State ] + div [ _class "cell pt-right-text" ] [ rawText (stats[churchId].SmallGroups.ToString "N0") ] + div [ _class "cell pt-right-text" ] [ rawText (stats[churchId].PrayerRequests.ToString "N0") ] + div [ _class "cell pt-right-text" ] [ rawText (stats[churchId].Users.ToString "N0") ] + div [ _class "cell pt-center-text" ] [ + locStr s[if church.HasVpsInterface then "Yes" else "No"] + ] ] ] - // table [ _class "pt-table pt-action-table" ] [ - // tableHeadings s [ "Actions"; "Name"; "Location"; "Groups"; "Requests"; "Users"; "Interface?" ] - // churches - // |> List.map (fun ch -> - // let chId = shortGuid ch.Id.Value - // let delAction = $"/church/{chId}/delete" - // let delPrompt = s["Are you sure you want to delete this {0}? This action cannot be undone.", - // $"""{s["Church"].Value.ToLower ()} ({ch.Name})"""] - // tr [] [ - // td [] [ - // a [ _href $"/church/{chId}/edit"; _title s["Edit This Church"].Value ] [ icon "edit" ] - // a [ _href delAction - // _title s["Delete This Church"].Value - // _onclick $"return PT.confirmDelete('{delAction}','{delPrompt}')" ] [ - // icon "delete_forever" - // ] - // ] - // td [] [ str ch.Name ] - // td [] [ str ch.City; rawText ", "; str ch.State ] - // td [ _class "pt-right-text" ] [ rawText (stats[chId].SmallGroups.ToString "N0") ] - // td [ _class "pt-right-text" ] [ rawText (stats[chId].PrayerRequests.ToString "N0") ] - // td [ _class "pt-right-text" ] [ rawText (stats[chId].Users.ToString "N0") ] - // td [ _class "pt-center-text" ] [ locStr s[if ch.HasVpsInterface then "Yes" else "No"] ] - // ]) - // |> tbody [] - // ] [ div [ _class "pt-center-text" ] [ br [] a [ _href $"/church/{emptyGuid}/edit"; _title s["Add a New Church"].Value ] [ @@ -132,8 +111,10 @@ let maintain (churches : Church list) (stats : Map) ctx vie br [] ] tableSummary churches.Length s - chTbl - form [ _id "DeleteForm"; _action ""; _method "post" ] [ csrfToken ctx ] + form [ _method "post" ] [ + csrfToken ctx + churchTable + ] ] |> Layout.Content.wide |> Layout.standard vi "Maintain Churches" diff --git a/src/PrayerTracker.UI/Layout.fs b/src/PrayerTracker.UI/Layout.fs index 368259e..24b2a93 100644 --- a/src/PrayerTracker.UI/Layout.fs +++ b/src/PrayerTracker.UI/Layout.fs @@ -141,15 +141,15 @@ module Navigation = let s = I18N.localizer.Force () header [ _id "pt-language"; Target.body ] [ div [] [ - span [ _class "u" ] [ locStr s["Language"]; rawText ": " ] + span [ _title s["Language"].Value ] [ icon "record_voice_over"; space ] match langCode () with | "es" -> - locStr s["Spanish"] - rawText "   •   " + strong [] [ locStr s["Spanish"] ] + rawText "     " a [ _href "/language/en" ] [ locStr s["Change to English"] ] | _ -> - locStr s["English"] - rawText "   •   " + strong [] [ locStr s["English"] ] + rawText "     " a [ _href "/language/es" ] [ locStr s["Cambie a Español"] ] ] match m.Group with diff --git a/src/PrayerTracker.UI/PrayerRequest.fs b/src/PrayerTracker.UI/PrayerRequest.fs index d118274..1da8ad9 100644 --- a/src/PrayerTracker.UI/PrayerRequest.fs +++ b/src/PrayerTracker.UI/PrayerRequest.fs @@ -4,6 +4,8 @@ open System open System.IO open Giraffe open Giraffe.ViewEngine +open Giraffe.ViewEngine.Accessibility +open Giraffe.ViewEngine.Htmx open Microsoft.AspNetCore.Http open NodaTime open PrayerTracker @@ -156,10 +158,11 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo = let prefs = model.SmallGroup.Preferences let types = ReferenceList.requestTypeList s |> Map.ofList let updReq (req : PrayerRequest) = - if req.UpdateRequired now prefs.DaysToExpire prefs.LongTermUpdateWeeks then "pt-request-update" else "" + if req.UpdateRequired now prefs.DaysToExpire prefs.LongTermUpdateWeeks then "cell pt-request-update" else "cell" |> _class let reqExp (req : PrayerRequest) = - _class (if req.IsExpired now prefs.DaysToExpire then "pt-request-expired" else "") + _class (if req.IsExpired now prefs.DaysToExpire then "cell pt-request-expired" else "cell") + let vi = AppViewInfo.withScopedStyles [ "#requestList { grid-template-columns: repeat(5, auto); }" ] viewInfo /// Iterate the sequence once, before we render, so we can get the count of it at the top of the table let requests = model.Requests @@ -175,33 +178,34 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo = .Value ] |> String.concat "" - tr [] [ - td [] [ + div [ _class "row" ] [ + div [ _class "cell actions" ] [ a [ _href $"/prayer-request/{reqId}/edit"; _title l["Edit This Prayer Request"].Value ] [ - icon "edit" + iconSized 18 "edit" ] if req.IsExpired now prefs.DaysToExpire then a [ _href $"/prayer-request/{reqId}/restore" _title l["Restore This Inactive Request"].Value ] [ - icon "visibility" + iconSized 18 "visibility" ] else a [ _href $"/prayer-request/{reqId}/expire" _title l["Expire This Request Immediately"].Value ] [ - icon "visibility_off" + iconSized 18 "visibility_off" ] - a [ _href delAction - _title l["Delete This Request"].Value - _onclick $"return PT.confirmDelete('{delAction}','{delPrompt}')" ] [ - icon "delete_forever" + a [ _href delAction + _title l["Delete This Request"].Value + _hxPost delAction + _hxConfirm delPrompt ] [ + iconSized 18 "delete_forever" ] ] - td [ updReq req ] [ + div [ updReq req ] [ str (req.UpdatedDate.ToString(s["MMMM d, yyyy"].Value, Globalization.CultureInfo.CurrentUICulture)) ] - td [] [ locStr types[req.RequestType] ] - td [ reqExp req ] [ str (match req.Requestor with Some r -> r | None -> " ") ] - td [] [ + div [ _class "cell" ] [ locStr types[req.RequestType] ] + div [ reqExp req ] [ str (match req.Requestor with Some r -> r | None -> " ") ] + div [ _class "cell" ] [ match reqText.Length with | len when len < 60 -> rawText reqText | _ -> rawText $"{reqText[0..59]}…" @@ -235,9 +239,18 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo = match requests.Length with | 0 -> () | _ -> - table [ _class "pt-table pt-action-table" ] [ - tableHeadings s [ "Actions"; "Updated Date"; "Type"; "Requestor"; "Request"] - tbody [] requests + form [ _method "post" ] [ + csrfToken ctx + section [ _id "requestList"; _class "pt-table"; _ariaLabel "Prayer request list" ] [ + div [ _class "row head" ] [ + header [ _class "cell" ] [ locStr s["Actions"] ] + header [ _class "cell" ] [ locStr s["Updated Date"] ] + header [ _class "cell" ] [ locStr s["Type"] ] + header [ _class "cell" ] [ locStr s["Requestor"] ] + header [ _class "cell" ] [ locStr s["Request"] ] + ] + yield! requests + ] ] div [ _class "pt-center-text" ] [ br [] @@ -272,10 +285,9 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo = ] | false -> () ] - form [ _id "DeleteForm"; _action ""; _method "post" ] [ csrfToken ctx ] ] |> Layout.Content.wide - |> Layout.standard viewInfo (match model.SearchTerm with Some _ -> "Search Results" | None -> "Maintain Requests") + |> Layout.standard vi (match model.SearchTerm with Some _ -> "Search Results" | None -> "Maintain Requests") /// View for the printable prayer request list diff --git a/src/PrayerTracker.UI/SmallGroup.fs b/src/PrayerTracker.UI/SmallGroup.fs index f5f3c9a..0a13040 100644 --- a/src/PrayerTracker.UI/SmallGroup.fs +++ b/src/PrayerTracker.UI/SmallGroup.fs @@ -1,6 +1,8 @@ module PrayerTracker.Views.SmallGroup open Giraffe.ViewEngine +open Giraffe.ViewEngine.Accessibility +open Giraffe.ViewEngine.Htmx open Microsoft.Extensions.Localization open PrayerTracker open PrayerTracker.Entities @@ -179,33 +181,40 @@ let logOn (groups : SmallGroup list) grpId ctx viewInfo = /// View for the small group maintenance page let maintain (groups : SmallGroup list) ctx viewInfo = - let s = I18N.localizer.Force () - let grpTbl = + let s = I18N.localizer.Force () + let vi = AppViewInfo.withScopedStyles [ "#groupList { grid-template-columns: repeat(4, auto); }" ] viewInfo + let groupTable = match groups with | [] -> space | _ -> - table [ _class "pt-table pt-action-table" ] [ - tableHeadings s [ "Actions"; "Name"; "Church"; "Time Zone"] - groups - |> List.map (fun g -> - let grpId = shortGuid g.Id.Value + section [ _id "groupList"; _class "pt-table"; _ariaLabel "Small group list" ] [ + div [ _class "row head" ] [ + header [ _class "cell" ] [ locStr s["Actions"] ] + header [ _class "cell" ] [ locStr s["Name"] ] + header [ _class "cell" ] [ locStr s["Church"] ] + header [ _class "cell" ] [ locStr s["Time Zone"] ] + ] + for group in groups do + let grpId = shortGuid group.Id.Value let delAction = $"/small-group/{grpId}/delete" let delPrompt = s["Are you sure you want to delete this {0}? This action cannot be undone.", - $"""{s["Small Group"].Value.ToLower ()} ({g.Name})""" ].Value - tr [] [ - td [] [ - a [ _href $"/small-group/{grpId}/edit"; _title s["Edit This Group"].Value ] [ icon "edit" ] - a [ _href delAction - _title s["Delete This Group"].Value - _onclick $"return PT.confirmDelete('{delAction}','{delPrompt}')" ] [ - icon "delete_forever" + $"""{s["Small Group"].Value.ToLower ()} ({group.Name})""" ].Value + div [ _class "row" ] [ + div [ _class "cell actions" ] [ + a [ _href $"/small-group/{grpId}/edit"; _title s["Edit This Group"].Value ] [ + iconSized 18 "edit" + ] + a [ _href delAction + _title s["Delete This Group"].Value + _hxDelete delAction + _hxConfirm delPrompt ] [ + iconSized 18 "delete_forever" ] ] - td [] [ str g.Name ] - td [] [ str g.Church.Name ] - td [] [ locStr (TimeZones.name g.Preferences.TimeZoneId s) ] - ]) - |> tbody [] + div [ _class "cell" ] [ str group.Name ] + div [ _class "cell" ] [ str group.Church.Name ] + div [ _class "cell" ] [ locStr (TimeZones.name group.Preferences.TimeZoneId s) ] + ] ] [ div [ _class "pt-center-text" ] [ br [] @@ -216,45 +225,54 @@ let maintain (groups : SmallGroup list) ctx viewInfo = br [] ] tableSummary groups.Length s - grpTbl - form [ _id "DeleteForm"; _action ""; _method "post" ] [ csrfToken ctx ] + form [ _method "post" ] [ + csrfToken ctx + groupTable + ] ] |> Layout.Content.standard - |> Layout.standard viewInfo "Maintain Groups" + |> Layout.standard vi "Maintain Groups" /// View for the member maintenance page let members (members : Member list) (emailTypes : Map) ctx viewInfo = - let s = I18N.localizer.Force () - let mbrTbl = + let s = I18N.localizer.Force () + let vi = AppViewInfo.withScopedStyles [ "#memberList { grid-template-columns: repeat(4, auto); }" ] viewInfo + let memberTable = match members with | [] -> space | _ -> - table [ _class "pt-table pt-action-table" ] [ - tableHeadings s [ "Actions"; "Name"; "E-mail Address"; "Format"] - members - |> List.map (fun mbr -> + section [ _id "memberList"; _class "pt-table"; _ariaLabel "Small group member list" ] [ + div [ _class "row head" ] [ + header [ _class "cell"] [ locStr s["Actions"] ] + header [ _class "cell"] [ locStr s["Name"] ] + header [ _class "cell"] [ locStr s["E-mail Address"] ] + header [ _class "cell"] [ locStr s["Format"] ] + ] + for mbr in members do let mbrId = shortGuid mbr.Id.Value let delAction = $"/small-group/member/{mbrId}/delete" let delPrompt = s["Are you sure you want to delete this {0}? This action cannot be undone.", s["group member"]] .Value.Replace("?", $" ({mbr.Name})?") - tr [] [ - td [] [ + div [ _class "row" ] [ + div [ _class "cell actions" ] [ a [ _href $"/small-group/member/{mbrId}/edit"; _title s["Edit This Group Member"].Value ] [ - icon "edit" + iconSized 18 "edit" ] - a [ _href delAction - _title s["Delete This Group Member"].Value - _onclick $"return PT.confirmDelete('{delAction}','{delPrompt}')" ] [ - icon "delete_forever" + a [ _href delAction + _title s["Delete This Group Member"].Value + _hxPost delAction + _hxConfirm delPrompt ] [ + iconSized 18 "delete_forever" ] ] - td [] [ str mbr.Name ] - td [] [ str mbr.Email ] - td [] [ locStr emailTypes[defaultArg (mbr.Format |> Option.map EmailFormat.toCode) ""] ] - ]) - |> tbody [] + div [ _class "cell" ] [ str mbr.Name ] + div [ _class "cell" ] [ str mbr.Email ] + div [ _class "cell" ] [ + locStr emailTypes[defaultArg (mbr.Format |> Option.map EmailFormat.toCode) ""] + ] + ] ] [ div [ _class"pt-center-text" ] [ br [] @@ -265,15 +283,15 @@ let members (members : Member list) (emailTypes : Map) br [] ] tableSummary members.Length s - mbrTbl - form [ _id "DeleteForm"; _action ""; _method "post" ] [ csrfToken ctx ] + form [ _method "post" ] [ + csrfToken ctx + memberTable + ] ] |> Layout.Content.standard - |> Layout.standard viewInfo "Maintain Group Members" + |> Layout.standard vi "Maintain Group Members" -open Giraffe.ViewEngine.Accessibility - /// View for the small group overview page let overview model viewInfo = let s = I18N.localizer.Force () diff --git a/src/PrayerTracker.UI/User.fs b/src/PrayerTracker.UI/User.fs index 718ff04..101272d 100644 --- a/src/PrayerTracker.UI/User.fs +++ b/src/PrayerTracker.UI/User.fs @@ -1,6 +1,8 @@ module PrayerTracker.Views.User open Giraffe.ViewEngine +open Giraffe.ViewEngine.Accessibility +open Giraffe.ViewEngine.Htmx open PrayerTracker open PrayerTracker.ViewModels @@ -8,37 +10,33 @@ open PrayerTracker.ViewModels let assignGroups model groups curGroups ctx viewInfo = let s = I18N.localizer.Force () let pageTitle = sprintf "%s • %A" model.UserName s["Assign Groups"] + let vi = AppViewInfo.withScopedStyles [ "#groupList { grid-template-columns: auto; }" ] viewInfo form [ _action "/user/small-groups/save"; _method "post"; _class "pt-center-columns"; Target.content ] [ csrfToken ctx inputField "hidden" (nameof model.UserId) model.UserId [] inputField "hidden" (nameof model.UserName) model.UserName [] - table [ _class "pt-table" ] [ - thead [] [ - tr [] [ - th [] [ rawText " " ] - th [] [ locStr s["Group"] ] - ] + section [ _id "groupList"; _class "pt-table"; _ariaLabel "Assigned small groups" ] [ + div [ _class "row head" ] [ + header [ _class "cell" ] [ locStr s["Group"] ] ] - groups - |> List.map (fun (grpId, grpName) -> - let inputId = $"id-{grpId}" - tr [] [ - td [] [ + for groupId, name in groups do + div [ _class "row" ] [ + div [ _class "cell" ] [ input [ _type "checkbox" _name (nameof model.SmallGroups) - _id inputId - _value grpId - if List.contains grpId curGroups then _checked ] + _id groupId + _value groupId + if List.contains groupId curGroups then _checked ] + space + label [ _for groupId ] [ str name ] ] - td [] [ label [ _for inputId ] [ str grpName ] ] - ]) - |> tbody [] + ] ] div [ _fieldRow ] [ submit [] "save" s["Save Group Assignments"] ] ] |> List.singleton |> Layout.Content.standard - |> Layout.standard viewInfo pageTitle + |> Layout.standard vi pageTitle /// View for the password change page @@ -186,40 +184,45 @@ open PrayerTracker.Entities /// View for the user maintenance page let maintain (users : User list) ctx viewInfo = - let s = I18N.localizer.Force () - let usrTbl = + let s = I18N.localizer.Force () + let vi = AppViewInfo.withScopedStyles [ "#userList { grid-template-columns: repeat(4, auto); }" ] viewInfo + let userTable = match users with | [] -> space | _ -> - table [ _class "pt-table pt-action-table" ] [ - tableHeadings s [ "Actions"; "Name"; "Last Seen"; "Admin?" ] - users - |> List.map (fun user -> + section [ _id "userList"; _class "pt-table"; _ariaLabel "User list" ] [ + div [ _class "row head" ] [ + header [ _class "cell" ] [ locStr s["Actions"] ] + header [ _class "cell" ] [ locStr s["Name"] ] + header [ _class "cell" ] [ locStr s["Last Seen"] ] + header [ _class "cell" ] [ locStr s["Admin?"] ] + ] + for user in users do let userId = shortGuid user.Id.Value let delAction = $"/user/{userId}/delete" let delPrompt = s["Are you sure you want to delete this {0}? This action cannot be undone.", $"""{s["User"].Value.ToLower ()} ({user.Name})"""].Value - tr [] [ - td [] [ - a [ _href $"/user/{userId}/edit"; _title s["Edit This User"].Value ] [ icon "edit" ] + div [ _class "row" ] [ + div [ _class "cell actions" ] [ + a [ _href $"/user/{userId}/edit"; _title s["Edit This User"].Value ] [ iconSized 18 "edit" ] a [ _href $"/user/{userId}/small-groups"; _title s["Assign Groups to This User"].Value ] [ - icon "group" + iconSized 18 "group" ] - a [ _href delAction - _title s["Delete This User"].Value - _onclick $"return PT.confirmDelete('{delAction}','{delPrompt}')" ] [ - icon "delete_forever" + a [ _href delAction + _title s["Delete This User"].Value + _hxPost delAction + _hxConfirm delPrompt ] [ + iconSized 18 "delete_forever" ] ] - td [] [ str user.Name ] - td [] [ + div [ _class "cell" ] [ str user.Name ] + div [ _class "cell" ] [ str (match user.LastSeen with Some dt -> dt.ToString s["MMMM d, yyyy"] | None -> "--") ] - td [ _class "pt-center-text" ] [ + div [ _class "cell pt-center-text" ] [ if user.IsAdmin then strong [] [ locStr s["Yes"] ] else locStr s["No"] ] - ]) - |> tbody [] + ] ] [ div [ _class "pt-center-text" ] [ br [] @@ -230,8 +233,10 @@ let maintain (users : User list) ctx viewInfo = br [] ] tableSummary users.Length s - usrTbl - form [ _id "DeleteForm"; _action ""; _method "post" ] [ csrfToken ctx ] + form [ _method "post" ] [ + csrfToken ctx + userTable + ] ] |> Layout.Content.standard - |> Layout.standard viewInfo "Maintain Users" + |> Layout.standard vi "Maintain Users" diff --git a/src/PrayerTracker/wwwroot/css/app.css b/src/PrayerTracker/wwwroot/css/app.css index 55e5e64..7039f6a 100644 --- a/src/PrayerTracker/wwwroot/css/app.css +++ b/src/PrayerTracker/wwwroot/css/app.css @@ -263,54 +263,48 @@ footer a:hover { text-align: right; } -.pt-data-list { +.pt-table { display: grid; justify-content: center; - grid-row-gap: .5rem; - grid-column-gap: 1rem; } -.pt-data-list .row { +.pt-table .row.head, +.pt-table .row { display: contents; } -.pt-data-list .row:hover > * { - background-color: purple; -} - -.pt-table { - margin: auto; - border-collapse: collapse; -} -.pt-table thead { - background-image: linear-gradient(to bottom, var(--dark), var(--lighter-dark)); - color: white; -} -.pt-table tbody tr:hover { +.pt-table .row:hover > * { background-color: #eee; } -.pt-table tr th, -.pt-table tr td { +.pt-table .row.head .cell { + background-image: linear-gradient(to bottom, var(--dark), var(--lighter-dark)); + font-weight: bold; + color: white; + text-align: center; + font-size: .85rem; +} +.pt-table .row.head .cell:first-of-type { + border-top-left-radius: .5rem; +} +.pt-table .row.head .cell:last-of-type { + border-top-right-radius: .5rem; +} +.pt-table .cell { padding: .25rem .5rem; + border-bottom: dotted 1px var(--lighter-dark); } -.pt-table tbody tr td { - border-bottom: solid 1px var(--lighter-dark); +.pt-table .cell.actions { + border-bottom-color: var(--background); } -.pt-action-table tbody tr td:first-child { - white-space: nowrap; - border-bottom: 0; -} -.pt-action-table tbody tr td:first-child a { +.pt-table .cell.actions a { background-color: gray; color: white; border-radius: .5rem; - padding: .125rem .5rem .5rem .5rem; + padding: 0 .5rem .25rem; margin: 0 .125rem; } -.pt-action-table tbody tr:hover td:first-child a { +.pt-table .row:hover .cell.actions a { background-color: var(--dark); } -.pt-action-table tbody tr td:first-child a:hover { - border-bottom: 0; -} + /* TODO: Figure out nice CSS transitions for these; these don't work */ .pt-fadeable { height: 0;