Alert banner

This commit is contained in:
Henrik Larsson 2023-08-15 13:32:36 +02:00
parent ffdc12fcca
commit 4245628da1
9 changed files with 138 additions and 48 deletions

View File

@ -0,0 +1,13 @@
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
export function AlertBanner() {
return (
<Alert variant="destructive">
<ExclamationTriangleIcon className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertDescription>Your session has expired. Please log in again.</AlertDescription>
</Alert>
);
}

View File

@ -26,7 +26,7 @@ export default async function Header({ locale }: HeaderProps) {
return ( return (
<HeaderRoot> <HeaderRoot>
<div className="relative flex flex-col border-b border-ui-border bg-app"> <div className="relative flex flex-col border-b border-ui-border bg-app">
<div className="relative flex h-14 w-full items-center justify-between px-4 py-2 lg:h-16 lg:px-8 lg:py-3 2xl:px-16"> <div className="relative flex w-full items-center justify-between px-4 py-2 lg:px-8 2xl:px-16">
<div className="-translate-x-2 transform md:hidden"> <div className="-translate-x-2 transform md:hidden">
<Suspense fallback={<OpenMobileMenu />}> <Suspense fallback={<OpenMobileMenu />}>
<MobileMenuModal items={mainMenu} /> <MobileMenuModal items={mainMenu} />

View File

@ -8,6 +8,8 @@ interface HeroProps {
label?: string; label?: string;
title: string; title: string;
image: object | any; image: object | any;
color?: string;
overlay?: boolean;
link: { link: {
title: string; title: string;
reference: { reference: {
@ -26,7 +28,7 @@ const heroSize = {
halfScreen: 'aspect-square max-h-[50vh] lg:aspect-auto lg:min-h-[50vh]' halfScreen: 'aspect-square max-h-[50vh] lg:aspect-auto lg:min-h-[50vh]'
}; };
const Hero = ({ variant, title, text, label, image, link }: HeroProps) => { const Hero = ({ variant, title, text, label, image, link, color, overlay }: HeroProps) => {
const heroClass = heroSize[variant as HeroSize] || heroSize.fullScreen; const heroClass = heroSize[variant as HeroSize] || heroSize.fullScreen;
return ( return (
@ -43,7 +45,12 @@ const Hero = ({ variant, title, text, label, image, link }: HeroProps) => {
fill fill
/> />
)} )}
<div className="absolute bottom-5 left-4 z-50 flex max-w-md flex-col items-start text-high-contrast lg:bottom-8 lg:left-8 lg:max-w-2xl 2xl:bottom-16 2xl:left-16"> {overlay && <div className="absolute inset-0 z-10 h-full w-full bg-black/70" />}
<div
className={`${
color === 'dark' ? 'text-high-contrast' : 'text-white'
} items-star absolute bottom-5 left-4 z-50 flex max-w-md flex-col lg:bottom-8 lg:left-8 lg:max-w-2xl 2xl:bottom-16 2xl:left-16`}
>
{label && ( {label && (
<Text className="mb-1 lg:mb-2" variant="label"> <Text className="mb-1 lg:mb-2" variant="label">
{label} {label}

View File

@ -1,9 +1,9 @@
'use client' 'use client';
import SanityImage from '../../ui/sanity-image'; import SanityImage from '../../ui/sanity-image';
interface USPSectionProps { interface USPSectionProps {
usps: [] | any usps: [] | any;
} }
const USPSection = ({ usps }: USPSectionProps) => { const USPSection = ({ usps }: USPSectionProps) => {
@ -12,35 +12,33 @@ const USPSection = ({ usps }: USPSectionProps) => {
return ( return (
<div className="px-4 lg:px-8 2xl:px-16"> <div className="px-4 lg:px-8 2xl:px-16">
<div <div
className={`w-full grid grid-cols-2 gap-x-4 gap-y-6 lg:gap-8 2xl:gap-x-16 ${desktopGridLayout}`} className={`grid w-full grid-cols-2 gap-x-4 gap-y-6 lg:gap-8 2xl:gap-x-16 ${desktopGridLayout}`}
> >
{usps.map((usp: any, index: number) => ( {usps.map((usp: any, index: number) => (
<div <div key={index} className={`flex w-full flex-col items-center text-center`}>
key={index} {usp?.image && (
className={`w-full flex flex-col items-center text-center`} <div className="h-20 w-20 lg:h-24 lg:w-24">
> <SanityImage
<div className="w-20 h-20 lg:w-24 lg:h-24"> className="object-cover"
<SanityImage image={usp?.image}
className="object-cover" alt={usp.name || 'USP image'}
image={usp?.image} width={96}
alt={usp.name || 'USP image'} height={96}
width={96} sizes="96px"
height={96} />
sizes="96px" </div>
/> )}
</div> <h2 className="mt-4 text-xl lg:mt-8 lg:text-2xl">{usp.title}</h2>
<h2 className="text-xl mt-4 lg:mt-8 lg:text-2xl">{usp.title}</h2>
{usp.text && ( {usp.text && (
<p className="text-sm mt-2 text-low-contrast max-w-xs lg:text-base lg:mt-4"> <p className="mt-2 max-w-xs text-sm text-low-contrast lg:mt-4 lg:text-base">
{usp.text} {usp.text}
</p> </p>
)} )}
</div> </div>
))} ))}
</div> </div>
</div> </div>
) );
} };
export default USPSection export default USPSection;

49
components/ui/alert.tsx Normal file
View File

@ -0,0 +1,49 @@
import { cva, type VariantProps } from 'class-variance-authority';
import * as React from 'react';
import { cn } from '@/lib/utils';
const alertVariants = cva(
'relative w-full border px-4 py-2 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',
{
variants: {
variant: {
default: 'bg-background text-foreground',
destructive:
'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive'
}
},
defaultVariants: {
variant: 'default'
}
}
);
const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props} />
));
Alert.displayName = 'Alert';
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn('mb-1 font-medium leading-none tracking-tight', className)}
{...props}
/>
)
);
AlertTitle.displayName = 'AlertTitle';
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn('text-sm [&_p]:leading-relaxed', className)} {...props} />
));
AlertDescription.displayName = 'AlertDescription';
export { Alert, AlertDescription, AlertTitle };

View File

@ -70,7 +70,7 @@ const Text: FunctionComponent<TextProps> = ({
['max-w-prose font-display text-2xl font-extrabold leading-none md:text-3xl md:leading-none lg:text-4xl lg:leading-none']: ['max-w-prose font-display text-2xl font-extrabold leading-none md:text-3xl md:leading-none lg:text-4xl lg:leading-none']:
variant === 'sectionHeading', variant === 'sectionHeading',
['text-sm leading-tight lg:text-base']: variant === 'listChildHeading', ['text-sm leading-tight lg:text-base']: variant === 'listChildHeading',
['max-w-prose text-lg text-high-contrast lg:text-xl']: variant === 'label', ['max-w-prose text-lg lg:text-xl']: variant === 'label',
['max-w-prose']: variant === 'paragraph' ['max-w-prose']: variant === 'paragraph'
}, },
className className

View File

@ -33,6 +33,8 @@ export const modules = `
variant, variant,
headingLevel, headingLevel,
text, text,
color,
overlay,
link { link {
title, title,
reference->{title, slug, "locale": language} reference->{title, slug, "locale": language}

View File

@ -1,7 +1,6 @@
import {StarIcon} from '@sanity/icons' import { StarIcon } from '@sanity/icons';
import {defineField} from 'sanity' import { defineField } from 'sanity';
import {languages} from '../../languages' import { languages } from '../../languages';
import {validateImage} from '../../utils/validation'
export default defineField({ export default defineField({
name: 'usp', name: 'usp',
@ -13,7 +12,7 @@ export default defineField({
name: 'language', name: 'language',
type: 'string', type: 'string',
readOnly: true, readOnly: true,
description: 'Language of this document.', description: 'Language of this document.'
// hidden: true, // hidden: true,
}), }),
// Title // Title
@ -22,15 +21,14 @@ export default defineField({
title: 'Title', title: 'Title',
type: 'string', type: 'string',
description: 'USP title', description: 'USP title',
validation: (Rule) => Rule.required(), validation: (Rule) => Rule.required()
}), }),
// Image // Image
defineField({ defineField({
name: 'image', name: 'image',
title: 'Image', title: 'Image',
type: 'mainImage', type: 'mainImage',
description: 'USP icon', description: 'USP icon'
validation: (Rule) => validateImage(Rule, true),
}), }),
// Text // Text
defineField({ defineField({
@ -38,25 +36,25 @@ export default defineField({
title: 'Text', title: 'Text',
type: 'text', type: 'text',
description: 'Small text displayed below title.', description: 'Small text displayed below title.',
rows: 5, rows: 5
}), })
], ],
preview: { preview: {
select: { select: {
title: 'title', title: 'title',
image: 'image', image: 'image',
language: 'language', language: 'language'
}, },
prepare(selection) { prepare(selection) {
const {image, title, language} = selection const { image, title, language } = selection;
const currentLang = languages.find((lang) => lang.id === language) const currentLang = languages.find((lang) => lang.id === language);
return { return {
media: image, media: image,
title, title,
subtitle: `${currentLang ? currentLang.title : ''}`, subtitle: `${currentLang ? currentLang.title : ''}`
} };
}, }
}, }
}) });

View File

@ -1,5 +1,5 @@
import {defineField} from 'sanity' import { StarIcon } from '@sanity/icons'
import {StarIcon} from '@sanity/icons' import { defineField } from 'sanity'
import { validateImage } from '../../utils/validation' import { validateImage } from '../../utils/validation'
export default defineField({ export default defineField({
@ -58,6 +58,30 @@ export default defineField({
layout: 'radio', layout: 'radio',
}, },
}, },
{
name: 'color',
type: 'string',
title: 'Color',
initialValue: 'dark',
fieldset: 'settings',
description: 'Set appropriate color depending on image characteristics.',
options: {
list: [
{title: 'Dark', value: 'dark'},
{title: 'Light', value: 'light'},
],
layout: 'radio',
},
},
{
name: 'overlay',
type: 'boolean',
title: 'Overlay?',
fieldset: 'settings',
description: 'Adds a dark overlay to the image.',
initialValue: false,
validation: (Rule) => Rule.required(),
},
{ {
name: 'label', name: 'label',
type: 'string', type: 'string',
@ -101,7 +125,6 @@ export default defineField({
title: 'Link', title: 'Link',
description: 'Link to internal page.', description: 'Link to internal page.',
options: { options: {
collapsed: true,
collapsible: true, collapsible: true,
}, },
}, },