mirror of
https://github.com/vercel/commerce.git
synced 2025-05-19 16:07:01 +00:00
Alert banner
This commit is contained in:
parent
ffdc12fcca
commit
4245628da1
13
components/layout/header/alert-banner/alert-banner.tsx
Normal file
13
components/layout/header/alert-banner/alert-banner.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
@ -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} />
|
||||||
|
@ -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}
|
||||||
|
@ -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
49
components/ui/alert.tsx
Normal 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 };
|
@ -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
|
||||||
|
@ -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}
|
||||||
|
@ -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 : ''}`
|
||||||
}
|
};
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
})
|
});
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user