Adds dashboard booking and notification components
Introduces reusable view components for bookings and notifications Implements dynamic rendering of booking items and lists Adds corresponding CSS styles for new dashboard components Enhances dashboard user interface with interactive elements
This commit is contained in:
parent
9b2ace7bc0
commit
abcf8ee75e
15 changed files with 648 additions and 14 deletions
BIN
.workbench/image.png
Normal file
BIN
.workbench/image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
|
|
@ -0,0 +1,17 @@
|
|||
@model PlanTempus.Application.Features.Dashboard.Components.BookingItemViewModel
|
||||
|
||||
<swp-booking-item data-key="@Model.Key" class="@Model.Status">
|
||||
<swp-booking-time>
|
||||
<swp-time-start>@Model.TimeStart</swp-time-start>
|
||||
<swp-time-end>@Model.TimeEnd</swp-time-end>
|
||||
</swp-booking-time>
|
||||
<swp-booking-indicator class="@Model.IndicatorColor"></swp-booking-indicator>
|
||||
<swp-booking-details>
|
||||
<swp-booking-service>@Model.Service</swp-booking-service>
|
||||
<swp-booking-customer>@Model.CustomerName</swp-booking-customer>
|
||||
</swp-booking-details>
|
||||
<swp-booking-employee title="@Model.EmployeeName">
|
||||
<swp-avatar-small>@Model.EmployeeInitials</swp-avatar-small>
|
||||
</swp-booking-employee>
|
||||
<swp-booking-status class="@Model.Status">@Model.StatusText</swp-booking-status>
|
||||
</swp-booking-item>
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace PlanTempus.Application.Features.Dashboard.Components;
|
||||
|
||||
public class BookingItemViewComponent : ViewComponent
|
||||
{
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var model = BookingItemCatalog.Get(key);
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class BookingItemViewModel
|
||||
{
|
||||
public required string Key { get; init; }
|
||||
public required string TimeStart { get; init; }
|
||||
public required string TimeEnd { get; init; }
|
||||
public required string Service { get; init; }
|
||||
public required string CustomerName { get; init; }
|
||||
public required string EmployeeInitials { get; init; }
|
||||
public required string EmployeeName { get; init; }
|
||||
public required string Status { get; init; }
|
||||
public string? IndicatorColor { get; init; }
|
||||
|
||||
public string StatusText => Status switch
|
||||
{
|
||||
"completed" => "Gennemført",
|
||||
"inprogress" => "I gang",
|
||||
"confirmed" => "Bekræftet",
|
||||
"pending" => "Afventer",
|
||||
_ => Status
|
||||
};
|
||||
}
|
||||
|
||||
public static class BookingItemCatalog
|
||||
{
|
||||
private static readonly Dictionary<string, BookingItemViewModel> Bookings = new()
|
||||
{
|
||||
["booking-1"] = new BookingItemViewModel
|
||||
{
|
||||
Key = "booking-1",
|
||||
TimeStart = "08:00",
|
||||
TimeEnd = "08:30",
|
||||
Service = "Herreklip",
|
||||
CustomerName = "Thomas Berg",
|
||||
EmployeeInitials = "MH",
|
||||
EmployeeName = "Maria Hansen",
|
||||
Status = "completed"
|
||||
},
|
||||
["booking-2"] = new BookingItemViewModel
|
||||
{
|
||||
Key = "booking-2",
|
||||
TimeStart = "08:30",
|
||||
TimeEnd = "09:00",
|
||||
Service = "Dameklip",
|
||||
CustomerName = "Katrine Holm",
|
||||
EmployeeInitials = "AS",
|
||||
EmployeeName = "Anna Sørensen",
|
||||
Status = "completed"
|
||||
},
|
||||
["booking-3"] = new BookingItemViewModel
|
||||
{
|
||||
Key = "booking-3",
|
||||
TimeStart = "09:00",
|
||||
TimeEnd = "09:30",
|
||||
Service = "Skægtrimning",
|
||||
CustomerName = "Mikkel Skov",
|
||||
EmployeeInitials = "PK",
|
||||
EmployeeName = "Peter Kristensen",
|
||||
Status = "completed"
|
||||
},
|
||||
["booking-4"] = new BookingItemViewModel
|
||||
{
|
||||
Key = "booking-4",
|
||||
TimeStart = "10:30",
|
||||
TimeEnd = "11:00",
|
||||
Service = "Herreklip",
|
||||
CustomerName = "Jonas Petersen",
|
||||
EmployeeInitials = "MH",
|
||||
EmployeeName = "Maria Hansen",
|
||||
Status = "inprogress",
|
||||
IndicatorColor = "blue"
|
||||
},
|
||||
["booking-5"] = new BookingItemViewModel
|
||||
{
|
||||
Key = "booking-5",
|
||||
TimeStart = "10:00",
|
||||
TimeEnd = "11:00",
|
||||
Service = "Føn + Styling",
|
||||
CustomerName = "Rikke Dam",
|
||||
EmployeeInitials = "LJ",
|
||||
EmployeeName = "Louise Jensen",
|
||||
Status = "inprogress",
|
||||
IndicatorColor = "purple"
|
||||
},
|
||||
["booking-6"] = new BookingItemViewModel
|
||||
{
|
||||
Key = "booking-6",
|
||||
TimeStart = "11:00",
|
||||
TimeEnd = "12:00",
|
||||
Service = "Balayage",
|
||||
CustomerName = "Emma Christensen",
|
||||
EmployeeInitials = "AS",
|
||||
EmployeeName = "Anna Sørensen",
|
||||
Status = "confirmed",
|
||||
IndicatorColor = "teal"
|
||||
}
|
||||
};
|
||||
|
||||
public static BookingItemViewModel Get(string key)
|
||||
{
|
||||
if (Bookings.TryGetValue(key, out var booking))
|
||||
return booking;
|
||||
|
||||
throw new KeyNotFoundException($"BookingItem with key '{key}' not found");
|
||||
}
|
||||
|
||||
public static IEnumerable<string> AllKeys => Bookings.Keys;
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
@model PlanTempus.Application.Features.Dashboard.Components.BookingListViewModel
|
||||
|
||||
<swp-card data-key="@Model.Key">
|
||||
<swp-card-header>
|
||||
<swp-card-title>
|
||||
<i class="ph ph-calendar-check"></i>
|
||||
@Model.Title
|
||||
</swp-card-title>
|
||||
<swp-card-action>Se alle</swp-card-action>
|
||||
</swp-card-header>
|
||||
<swp-current-time>
|
||||
<i class="ph ph-clock"></i>
|
||||
<span>Nu: <swp-time>@Model.CurrentTime</swp-time></span>
|
||||
</swp-current-time>
|
||||
<swp-card-content>
|
||||
<swp-booking-list>
|
||||
@foreach (var bookingKey in Model.BookingKeys)
|
||||
{
|
||||
@await Component.InvokeAsync("BookingItem", bookingKey)
|
||||
}
|
||||
</swp-booking-list>
|
||||
</swp-card-content>
|
||||
</swp-card>
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace PlanTempus.Application.Features.Dashboard.Components;
|
||||
|
||||
public class BookingListViewComponent : ViewComponent
|
||||
{
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var model = BookingListCatalog.Get(key);
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class BookingListViewModel
|
||||
{
|
||||
public required string Key { get; init; }
|
||||
public required string Title { get; init; }
|
||||
public required string CurrentTime { get; init; }
|
||||
public required IReadOnlyList<string> BookingKeys { get; init; }
|
||||
}
|
||||
|
||||
public static class BookingListCatalog
|
||||
{
|
||||
private static readonly Dictionary<string, BookingListViewModel> Lists = new()
|
||||
{
|
||||
["todays-bookings"] = new BookingListViewModel
|
||||
{
|
||||
Key = "todays-bookings",
|
||||
Title = "Dagens bookinger",
|
||||
CurrentTime = "10:45",
|
||||
BookingKeys = ["booking-1", "booking-2", "booking-3", "booking-4", "booking-5", "booking-6"]
|
||||
}
|
||||
};
|
||||
|
||||
public static BookingListViewModel Get(string key)
|
||||
{
|
||||
if (Lists.TryGetValue(key, out var list))
|
||||
return list;
|
||||
|
||||
throw new KeyNotFoundException($"BookingList with key '{key}' not found");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
@model PlanTempus.Application.Features.Dashboard.Components.NotificationItemViewModel
|
||||
|
||||
<swp-notification-item data-key="@Model.Key" class="@(Model.IsUnread ? "unread" : "")">
|
||||
<swp-notification-icon>
|
||||
<i class="ph ph-@Model.Icon"></i>
|
||||
</swp-notification-icon>
|
||||
<swp-notification-content>
|
||||
<swp-notification-text>
|
||||
<strong>@Model.Title</strong> @Model.Text
|
||||
</swp-notification-text>
|
||||
<swp-notification-time>@Model.Time</swp-notification-time>
|
||||
</swp-notification-content>
|
||||
</swp-notification-item>
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace PlanTempus.Application.Features.Dashboard.Components;
|
||||
|
||||
public class NotificationItemViewComponent : ViewComponent
|
||||
{
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var model = NotificationItemCatalog.Get(key);
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class NotificationItemViewModel
|
||||
{
|
||||
public required string Key { get; init; }
|
||||
public required string Icon { get; init; }
|
||||
public required string Title { get; init; }
|
||||
public required string Text { get; init; }
|
||||
public required string Time { get; init; }
|
||||
public bool IsUnread { get; init; }
|
||||
}
|
||||
|
||||
public static class NotificationItemCatalog
|
||||
{
|
||||
private static readonly Dictionary<string, NotificationItemViewModel> Notifications = new()
|
||||
{
|
||||
["notif-1"] = new NotificationItemViewModel
|
||||
{
|
||||
Key = "notif-1",
|
||||
Icon = "calendar-plus",
|
||||
Title = "Ny booking",
|
||||
Text = "fra Emma Christensen til Balayage",
|
||||
Time = "For 15 min. siden",
|
||||
IsUnread = true
|
||||
},
|
||||
["notif-2"] = new NotificationItemViewModel
|
||||
{
|
||||
Key = "notif-2",
|
||||
Icon = "star",
|
||||
Title = "Ny anmeldelse",
|
||||
Text = "5 stjerner fra Sofie Nielsen",
|
||||
Time = "For 1 time siden",
|
||||
IsUnread = true
|
||||
},
|
||||
["notif-3"] = new NotificationItemViewModel
|
||||
{
|
||||
Key = "notif-3",
|
||||
Icon = "x",
|
||||
Title = "Aflysning",
|
||||
Text = "Mette Hansen aflyste kl. 15:00",
|
||||
Time = "For 2 timer siden",
|
||||
IsUnread = false
|
||||
},
|
||||
["notif-4"] = new NotificationItemViewModel
|
||||
{
|
||||
Key = "notif-4",
|
||||
Icon = "check",
|
||||
Title = "Bekræftet",
|
||||
Text = "Louise Andersen bekræftede kl. 13:00",
|
||||
Time = "I går kl. 18:30",
|
||||
IsUnread = false
|
||||
}
|
||||
};
|
||||
|
||||
public static NotificationItemViewModel Get(string key)
|
||||
{
|
||||
if (Notifications.TryGetValue(key, out var notification))
|
||||
return notification;
|
||||
|
||||
throw new KeyNotFoundException($"NotificationItem with key '{key}' not found");
|
||||
}
|
||||
|
||||
public static IEnumerable<string> AllKeys => Notifications.Keys;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
@model PlanTempus.Application.Features.Dashboard.Components.NotificationListViewModel
|
||||
|
||||
<swp-card data-key="@Model.Key">
|
||||
<swp-card-header>
|
||||
<swp-card-title>
|
||||
<i class="ph ph-bell"></i>
|
||||
@Model.Title
|
||||
</swp-card-title>
|
||||
<swp-card-action>@Model.ActionText</swp-card-action>
|
||||
</swp-card-header>
|
||||
<swp-card-content>
|
||||
<swp-notification-list>
|
||||
@foreach (var notificationKey in Model.NotificationKeys)
|
||||
{
|
||||
@await Component.InvokeAsync("NotificationItem", notificationKey)
|
||||
}
|
||||
</swp-notification-list>
|
||||
</swp-card-content>
|
||||
</swp-card>
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace PlanTempus.Application.Features.Dashboard.Components;
|
||||
|
||||
public class NotificationListViewComponent : ViewComponent
|
||||
{
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var model = NotificationListCatalog.Get(key);
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class NotificationListViewModel
|
||||
{
|
||||
public required string Key { get; init; }
|
||||
public required string Title { get; init; }
|
||||
public required string ActionText { get; init; }
|
||||
public required IReadOnlyList<string> NotificationKeys { get; init; }
|
||||
}
|
||||
|
||||
public static class NotificationListCatalog
|
||||
{
|
||||
private static readonly Dictionary<string, NotificationListViewModel> Lists = new()
|
||||
{
|
||||
["recent-notifications"] = new NotificationListViewModel
|
||||
{
|
||||
Key = "recent-notifications",
|
||||
Title = "Notifikationer",
|
||||
ActionText = "Marker alle som læst",
|
||||
NotificationKeys = ["notif-1", "notif-2", "notif-3", "notif-4"]
|
||||
}
|
||||
};
|
||||
|
||||
public static NotificationListViewModel Get(string key)
|
||||
{
|
||||
if (Lists.TryGetValue(key, out var list))
|
||||
return list;
|
||||
|
||||
throw new KeyNotFoundException($"NotificationList with key '{key}' not found");
|
||||
}
|
||||
}
|
||||
|
|
@ -31,22 +31,14 @@
|
|||
</swp-ai-insight>
|
||||
</swp-card>
|
||||
|
||||
<!-- Today's Bookings Preview -->
|
||||
<swp-card>
|
||||
<swp-card-header>
|
||||
<swp-card-title>
|
||||
<i class="ph ph-calendar-check"></i>
|
||||
Dagens bookinger
|
||||
</swp-card-title>
|
||||
<swp-card-action>Se alle</swp-card-action>
|
||||
</swp-card-header>
|
||||
<swp-card-content>
|
||||
<p>Booking oversigt kommer her...</p>
|
||||
</swp-card-content>
|
||||
</swp-card>
|
||||
<!-- Today's Bookings -->
|
||||
@await Component.InvokeAsync("BookingList", "todays-bookings")
|
||||
</swp-main-column>
|
||||
|
||||
<swp-side-column>
|
||||
<!-- Notifications -->
|
||||
@await Component.InvokeAsync("NotificationList", "recent-notifications")
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<swp-card>
|
||||
<swp-card-header>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@
|
|||
<!-- Page Components -->
|
||||
<link rel="stylesheet" href="~/css/page.css">
|
||||
<link rel="stylesheet" href="~/css/stats.css">
|
||||
<link rel="stylesheet" href="~/css/bookings.css">
|
||||
<link rel="stylesheet" href="~/css/notifications.css">
|
||||
@await RenderSectionAsync("Styles", required: false)
|
||||
</head>
|
||||
<body class="has-demo-banner">
|
||||
|
|
|
|||
194
PlanTempus.Application/wwwroot/css/bookings.css
Normal file
194
PlanTempus.Application/wwwroot/css/bookings.css
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
/**
|
||||
* Bookings - Booking List & Items
|
||||
*
|
||||
* Styling for booking components on dashboard
|
||||
*/
|
||||
|
||||
/* ===========================================
|
||||
BOOKING LIST
|
||||
=========================================== */
|
||||
swp-booking-list {
|
||||
display: grid;
|
||||
grid-template-columns: 50px 4px 1fr auto auto;
|
||||
gap: var(--spacing-4);
|
||||
padding: 0 var(--card-body-padding);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
BOOKING ITEM
|
||||
=========================================== */
|
||||
swp-booking-item {
|
||||
display: grid;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
align-items: center;
|
||||
padding: var(--spacing-4);
|
||||
background: var(--color-background-alt);
|
||||
border-radius: var(--radius-lg);
|
||||
cursor: pointer;
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
swp-booking-item:hover {
|
||||
background: var(--color-background-hover);
|
||||
}
|
||||
|
||||
swp-booking-item.completed {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
swp-booking-item.completed swp-booking-indicator {
|
||||
background: var(--color-border);
|
||||
}
|
||||
|
||||
swp-booking-item.inprogress {
|
||||
background: color-mix(in srgb, var(--color-teal) 8%, var(--color-background-alt));
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
BOOKING TIME
|
||||
=========================================== */
|
||||
swp-booking-time {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
swp-booking-time swp-time-start {
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
font-family: var(--font-mono);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
swp-booking-time swp-time-end {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-secondary);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
BOOKING INDICATOR
|
||||
=========================================== */
|
||||
swp-booking-indicator {
|
||||
width: 4px;
|
||||
height: 40px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
swp-booking-indicator.teal { background: var(--color-teal); }
|
||||
swp-booking-indicator.blue { background: var(--color-blue); }
|
||||
swp-booking-indicator.purple { background: var(--color-purple); }
|
||||
swp-booking-indicator.amber { background: var(--color-amber); }
|
||||
swp-booking-indicator.green { background: var(--color-green); }
|
||||
|
||||
/* ===========================================
|
||||
BOOKING DETAILS
|
||||
=========================================== */
|
||||
swp-booking-details {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
swp-booking-service {
|
||||
display: block;
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
swp-booking-customer {
|
||||
display: block;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
BOOKING EMPLOYEE
|
||||
=========================================== */
|
||||
swp-booking-employee {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-3);
|
||||
}
|
||||
|
||||
swp-booking-employee swp-avatar-small {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--color-teal);
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
swp-booking-employee span {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
BOOKING STATUS
|
||||
=========================================== */
|
||||
swp-booking-status {
|
||||
padding: var(--spacing-2) var(--spacing-4);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
swp-booking-status.confirmed {
|
||||
background: color-mix(in srgb, var(--color-green) 15%, transparent);
|
||||
color: var(--color-green);
|
||||
}
|
||||
|
||||
swp-booking-status.pending {
|
||||
background: color-mix(in srgb, var(--color-amber) 15%, transparent);
|
||||
color: var(--color-amber);
|
||||
}
|
||||
|
||||
swp-booking-status.inprogress,
|
||||
swp-booking-status.in-progress {
|
||||
background: color-mix(in srgb, var(--color-blue) 15%, transparent);
|
||||
color: var(--color-blue);
|
||||
}
|
||||
|
||||
swp-booking-status.completed {
|
||||
background: var(--color-background-hover);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
CURRENT TIME INDICATOR
|
||||
=========================================== */
|
||||
swp-current-time {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-4);
|
||||
padding: var(--spacing-4) var(--spacing-6);
|
||||
background: color-mix(in srgb, var(--color-teal) 10%, transparent);
|
||||
border-radius: var(--radius-lg);
|
||||
margin: 0 var(--card-body-padding) var(--spacing-4) var(--card-body-padding);
|
||||
}
|
||||
|
||||
swp-current-time i {
|
||||
font-size: var(--font-size-lg);
|
||||
color: var(--color-teal);
|
||||
}
|
||||
|
||||
swp-current-time span {
|
||||
font-size: var(--font-size-md);
|
||||
color: var(--color-teal);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
swp-current-time swp-time {
|
||||
font-family: var(--font-mono);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
|
@ -189,6 +189,9 @@
|
|||
--container-max-width-md: 1200px;
|
||||
--container-max-width-lg: 1400px;
|
||||
|
||||
/* -------- Card Spacing -------- */
|
||||
--card-body-padding: var(--spacing-5);
|
||||
|
||||
/* -------- Calendar Grid -------- */
|
||||
--hour-height: 64px;
|
||||
--time-axis-width: 60px;
|
||||
|
|
|
|||
92
PlanTempus.Application/wwwroot/css/notifications.css
Normal file
92
PlanTempus.Application/wwwroot/css/notifications.css
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* Notifications CSS
|
||||
*
|
||||
* Styling for notification components on dashboard
|
||||
*/
|
||||
|
||||
/* ===========================================
|
||||
NOTIFICATION LIST
|
||||
=========================================== */
|
||||
swp-notification-list {
|
||||
display: grid;
|
||||
grid-template-columns: 50px 1fr;
|
||||
gap: var(--spacing-4) var(--spacing-6);
|
||||
padding: 0 var(--card-body-padding);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
NOTIFICATION ITEM
|
||||
=========================================== */
|
||||
swp-notification-item {
|
||||
display: grid;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
align-items: center;
|
||||
padding: var(--spacing-5) var(--spacing-6);
|
||||
background: var(--color-background-alt);
|
||||
border-radius: var(--radius-xl);
|
||||
cursor: pointer;
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
swp-notification-item:hover {
|
||||
background: var(--color-background-hover);
|
||||
}
|
||||
|
||||
swp-notification-item.unread {
|
||||
background: color-mix(in srgb, var(--color-teal) 5%, var(--color-background-alt));
|
||||
}
|
||||
|
||||
swp-notification-item.unread:hover {
|
||||
background: var(--color-background-hover);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
NOTIFICATION ICON
|
||||
=========================================== */
|
||||
swp-notification-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: var(--color-background-hover);
|
||||
border-radius: var(--radius-xl);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
swp-notification-item.unread swp-notification-icon {
|
||||
background: color-mix(in srgb, var(--color-teal) 15%, transparent);
|
||||
color: var(--color-teal);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
NOTIFICATION CONTENT
|
||||
=========================================== */
|
||||
swp-notification-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
swp-notification-text {
|
||||
display: block;
|
||||
font-size: var(--font-size-md);
|
||||
color: var(--color-text);
|
||||
line-height: var(--line-height-snug);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
swp-notification-text strong {
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
swp-notification-time {
|
||||
display: block;
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-secondary);
|
||||
margin-top: var(--spacing-1);
|
||||
}
|
||||
|
|
@ -94,7 +94,7 @@ swp-card-content {
|
|||
=========================================== */
|
||||
swp-dashboard-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 350px;
|
||||
grid-template-columns: 1fr 380px;
|
||||
gap: var(--spacing-5);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue