fix(web): notification styles & alignment (#968)

* fix(web): notification icon & indicator colors

* fix(web): notification item text size & weights

* fix(web): notification button styles

* fix(web): notification filter styles

* fix(web): Tab List styles

* fix(web): link button styles

* fix(web): vertical spacing in notifications sidebar

* fix(web): notification sidebar link styles

* refactor(web): change default button border radius to rounded instead of rounded-md

* fix(web): Notification Item alignment with other elements

* refactor(web): add tw color palettes for unraid-green & unraid-red
This commit is contained in:
Pujit Mehrotra
2024-11-25 12:12:26 -05:00
committed by GitHub
parent c36082e82b
commit 547b75a55e
9 changed files with 88 additions and 68 deletions

View File

@@ -43,7 +43,7 @@ const icon = computed<{ component: Component; color: string } | null>(() => {
case Importance.Alert:
return {
component: ShieldExclamationIcon,
color: 'text-red-500',
color: 'text-unraid-red',
};
}
return null;
@@ -73,10 +73,10 @@ watch(overview, (newVal, oldVal) => {
<div
v-if="indicatorLevel"
:class="
cn('absolute top-0 right-0 size-2.5 rounded-full', {
cn('absolute top-0 right-0 size-2.5 rounded-full border border-neutral-800', {
'bg-unraid-red': indicatorLevel === Importance.Alert,
'bg-yellow-500': indicatorLevel === Importance.Warning,
'bg-green-500': indicatorLevel === 'UNREAD',
'bg-yellow-accent': indicatorLevel === Importance.Warning,
'bg-unraid-green': indicatorLevel === 'UNREAD',
})
"
/>

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { Markdown } from '@/helpers/markdown';
import {
ArchiveBoxIcon,
CheckBadgeIcon,
@@ -14,7 +15,6 @@ import {
archiveNotification as archiveMutation,
deleteNotification as deleteMutation,
} from './graphql/notification.query';
import { Markdown } from '@/helpers/markdown';
const props = defineProps<NotificationFragmentFragment>();
@@ -22,7 +22,7 @@ const descriptionMarkup = computedAsync(async () => {
try {
return await Markdown.parse(props.description);
} catch (e) {
console.error(e)
console.error(e);
return props.description;
}
}, '');
@@ -32,17 +32,17 @@ const icon = computed<{ component: Component; color: string } | null>(() => {
case 'INFO':
return {
component: CheckBadgeIcon,
color: 'text-green-500',
color: 'text-unraid-green',
};
case 'WARNING':
return {
component: ExclamationTriangleIcon,
color: 'text-yellow-500',
color: 'text-yellow-accent',
};
case 'ALERT':
return {
component: ShieldExclamationIcon,
color: 'text-red-500',
color: 'text-unraid-red',
};
}
return null;
@@ -66,66 +66,57 @@ const mutationError = computed(() => {
</script>
<template>
<div class="group/item relative w-full py-4 pl-1 flex flex-col gap-2">
<header
class="w-full flex flex-row items-baseline justify-between gap-2 -translate-y-1 group-hover/item:font-medium group-focus/item:font-medium"
>
<h3
class="text-muted-foreground text-[0.875rem] tracking-wide flex flex-row items-baseline gap-2 uppercase"
>
<!-- fixed width hack ensures alignment with other elements regardless of scrollbar presence or width -->
<div class="group/item relative py-5 flex flex-col gap-2 text-base w-[487px]">
<header class="w-full flex flex-row items-baseline justify-between gap-2 -translate-y-1">
<h3 class="tracking-normal flex flex-row items-baseline gap-2 uppercase font-bold">
<!-- the `translate` compensates for extra space added by the `svg` element when rendered -->
<component
:is="icon.component"
v-if="icon"
class="size-5 shrink-0 translate-y-1.5"
class="size-5 shrink-0 translate-y-1"
:class="icon.color"
/>
<span>{{ title }}</span>
</h3>
<div class="shrink-0 flex flex-row items-baseline justify-end gap-2 mt-1">
<p class="text-12px opacity-75">{{ formattedTimestamp }}</p>
<p class="text-gray-500 text-sm">{{ formattedTimestamp }}</p>
</div>
</header>
<h4 class="group-hover/item:font-medium group-focus/item:font-medium">
<h4 class="font-bold">
{{ subject }}
</h4>
<div
class="w-full flex flex-row items-center justify-between gap-2 opacity-75 group-hover/item:opacity-100 group-focus/item:opacity-100"
>
<div class="text-secondary-foreground" v-html="descriptionMarkup" />
<div class="w-full flex flex-row items-center justify-between gap-2">
<div class="" v-html="descriptionMarkup" />
</div>
<p v-if="mutationError" class="text-red-600">Error: {{ mutationError }}</p>
<div class="flex justify-end items-baseline gap-2">
<div class="flex justify-end items-baseline gap-4">
<a v-if="link" :href="link">
<Button type="button" variant="outline" size="xs">
<LinkIcon class="size-3 mr-1 text-muted-foreground/80" />
<span class="text-sm text-muted-foreground mt-0.5">View</span>
<Button type="button" variant="outline">
<LinkIcon class="size-4 mr-2" />
<span class="text-sm">View</span>
</Button>
</a>
<Button
v-if="type === NotificationType.Unread"
:disabled="archive.loading"
class="relative z-20 rounded"
size="xs"
@click="archive.mutate"
>
<ArchiveBoxIcon class="size-3 mr-1" />
<span class="text-sm mt-0.5">Archive</span>
<ArchiveBoxIcon class="size-4 mr-2" />
<span class="text-sm">Archive</span>
</Button>
<Button
v-if="type === NotificationType.Archive"
:disabled="deleteNotification.loading"
class="relative z-20 rounded"
size="xs"
@click="deleteNotification.mutate"
>
<TrashIcon class="size-3 mr-1" />
<span class="text-sm mt-0.5">Delete</span>
<TrashIcon class="size-4 mr-2" />
<span class="text-sm">Delete</span>
</Button>
</div>
</div>

View File

@@ -77,7 +77,7 @@ async function onLoadMore() {
<div
v-if="notifications?.length > 0"
v-infinite-scroll="[onLoadMore, { canLoadMore: () => canLoadMore }]"
class="divide-y divide-gray-200 overflow-y-auto pl-7 pr-4 h-full"
class="divide-y divide-gray-200 overflow-y-auto h-full pl-7"
>
<NotificationsItem
v-for="notification in notifications"

View File

@@ -33,14 +33,15 @@ const confirmAndDeleteAll = async () => {
</SheetTrigger>
<!-- We remove the horizontal padding from the container to keep the NotificationList's scrollbar in the right place -->
<SheetContent :to="teleportTarget" class="w-full sm:max-w-[540px] h-screen px-0">
<div class="flex flex-col h-full gap-3">
<SheetHeader class="ml-1 px-6 flex items-baseline gap-0">
<SheetContent
:to="teleportTarget"
class="w-full max-w-[100vw] sm:max-w-[540px] h-screen px-0 bg-[#f2f2f2]"
>
<div class="flex flex-col h-full gap-5">
<SheetHeader class="ml-1 px-6 flex items-baseline gap-1">
<SheetTitle class="text-2xl">Notifications</SheetTitle>
<a href="/Settings/Notifications">
<Button variant="link" size="sm" class="text-muted-foreground text-base p-0">
Edit Settings
</Button>
<Button variant="link" size="sm" class="p-0 h-auto"> Edit Settings </Button>
</a>
</SheetHeader>
@@ -48,7 +49,7 @@ const confirmAndDeleteAll = async () => {
<!-- this is necessary because flex items have a default min-height: auto, -->
<!-- which means they won't shrink below the height of their content, even if you use flex-1 or other flex properties. -->
<Tabs default-value="unread" class="flex-1 flex flex-col min-h-0" activation-mode="manual">
<div class="flex flex-row justify-between items-center flex-wrap gap-2 px-6">
<div class="flex flex-row justify-between items-center flex-wrap gap-5 px-6">
<TabsList class="ml-[1px]">
<TabsTrigger value="unread"> Unread </TabsTrigger>
<TabsTrigger value="archived"> Archived </TabsTrigger>
@@ -58,7 +59,7 @@ const confirmAndDeleteAll = async () => {
:disabled="loadingArchiveAll"
variant="link"
size="sm"
class="text-muted-foreground text-base p-0"
class="text-foreground hover:text-destructive transition-none"
@click="confirmAndArchiveAll"
>
Archive All
@@ -69,7 +70,7 @@ const confirmAndDeleteAll = async () => {
:disabled="loadingDeleteAll"
variant="link"
size="sm"
class="text-muted-foreground text-base p-0"
class="text-foreground hover:text-destructive transition-none"
@click="confirmAndDeleteAll"
>
Delete All
@@ -83,8 +84,8 @@ const confirmAndDeleteAll = async () => {
}
"
>
<SelectTrigger class="bg-secondary border-0 h-auto">
<SelectValue class="text-muted-foreground" placeholder="Filter" />
<SelectTrigger class="h-auto">
<SelectValue class="text-gray-400 leading-6" placeholder="Filter By" />
</SelectTrigger>
<SelectContent :to="teleportTarget">
<SelectGroup>
@@ -98,11 +99,11 @@ const confirmAndDeleteAll = async () => {
</Select>
</div>
<TabsContent value="unread" class="flex-1 min-h-0 mt-3">
<TabsContent value="unread" class="flex-1 min-h-0 mt-5">
<NotificationsList :importance="importance" :type="NotificationType.Unread" />
</TabsContent>
<TabsContent value="archived" class="flex-1 min-h-0 mt-3">
<TabsContent value="archived" class="flex-1 min-h-0 mt-5">
<NotificationsList :importance="importance" :type="NotificationType.Archive" />
</TabsContent>
</Tabs>

View File

@@ -1,25 +1,22 @@
import { type VariantProps, cva } from 'class-variance-authority'
import { cva, type VariantProps } from 'class-variance-authority';
export { default as Button } from './Button.vue'
export { default as Button } from './Button.vue';
export const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
'inline-flex items-center justify-center whitespace-nowrap rounded text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline:
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-10 px-4 py-2',
xs: 'h-7 rounded px-2',
default: 'px-4 py-2',
xs: 'h-7 px-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
@@ -29,7 +26,7 @@ export const buttonVariants = cva(
variant: 'default',
size: 'default',
},
},
)
}
);
export type ButtonVariants = VariantProps<typeof buttonVariants>
export type ButtonVariants = VariantProps<typeof buttonVariants>;

View File

@@ -19,7 +19,7 @@ const forwardedProps = useForwardProps(delegatedProps)
<SelectTrigger
v-bind="forwardedProps"
:class="cn(
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:truncate text-start',
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-4.5 py-3 text-base ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:truncate text-start',
props.class,
)"
>

View File

@@ -16,7 +16,7 @@ const delegatedProps = computed(() => {
<TabsList
v-bind="delegatedProps"
:class="cn(
'inline-flex items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
'inline-flex items-center justify-center rounded-md bg-input p-1.5 text-foreground',
props.class,
)"
>

View File

@@ -18,7 +18,7 @@ const forwardedProps = useForwardProps(delegatedProps)
<TabsTrigger
v-bind="forwardedProps"
:class="cn(
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-base font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
'inline-flex items-center justify-center whitespace-nowrap rounded px-4.5 py-2.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
props.class,
)"
>

View File

@@ -2,7 +2,6 @@ import 'dotenv/config';
import type { Config } from 'tailwindcss';
import type { PluginAPI } from 'tailwindcss/types/config';
// @ts-expect-error - just trying to get this to build @fixme
export default <Partial<Config>>{
darkMode: ['class'],
@@ -45,9 +44,40 @@ export default <Partial<Config>>{
'grey-lightest': '#f2f2f2',
white: '#ffffff',
// unraid colors
'yellow-accent': '#E9BF41',
'orange-dark': '#f15a2c',
orange: '#ff8c2f',
'unraid-red': '#E22828',
// palettes generated from https://uicolors.app/create
'unraid-red': {
DEFAULT: '#E22828',
'50': '#fef2f2',
'100': '#ffe1e1',
'200': '#ffc9c9',
'300': '#fea3a3',
'400': '#fc6d6d',
'500': '#f43f3f',
'600': '#e22828',
'700': '#bd1818',
'800': '#9c1818',
'900': '#821a1a',
'950': '#470808',
},
'unraid-green': {
DEFAULT: '#63A659',
'50': '#f5f9f4',
'100': '#e7f3e5',
'200': '#d0e6cc',
'300': '#aad1a4',
'400': '#7db474',
'500': '#63a659',
'600': '#457b3e',
'700': '#396134',
'800': '#314e2d',
'900': '#284126',
'950': '#122211',
},
alpha: 'var(--color-alpha)',
beta: 'var(--color-beta)',
@@ -101,6 +131,7 @@ export default <Partial<Config>>{
'30px': '30px',
},
spacing: {
'4.5': '1.125rem',
'-8px': '-8px',
'2px': '2px',
'4px': '4px',