diff --git a/sanityv3/schemas/index.ts b/sanityv3/schemas/index.ts index c1d8eb62f..5d55d6a83 100644 --- a/sanityv3/schemas/index.ts +++ b/sanityv3/schemas/index.ts @@ -25,6 +25,7 @@ import fullWidthVideo from './objects/fullWidthVideo' import iframe from './objects/iframe' import imageWithAlt from './objects/imageWithAlt' import imageWithAltAndCaption from './objects/imageWithAltAndCaption' +import carouselImage from './objects/carouselImage' import largeTable from './objects/largeTable' import linkSelector from './objects/linkSelector' import menuGroup from './objects/menuGroup' @@ -139,6 +140,7 @@ const RemainingSchemas = [ internalServerError, imageWithAlt, imageWithAltAndCaption, + carouselImage, pullQuote, factbox, relatedLinks, diff --git a/sanityv3/schemas/objects/carouselImage.tsx b/sanityv3/schemas/objects/carouselImage.tsx new file mode 100644 index 000000000..85b31aefb --- /dev/null +++ b/sanityv3/schemas/objects/carouselImage.tsx @@ -0,0 +1,66 @@ +import { ImageWithAlt } from './imageWithAlt' +import type { Reference } from 'sanity' +import { Rule } from 'sanity' + + +export type CarouselImage = { + _type: 'carouselImage' + image: ImageWithAlt + captionPositionUnderImage?: boolean + action?: Reference[] +} + +export default { + name: 'carouselImage', + title: 'Image with options', + type: 'object', + options: { + collapsed: false, + }, + fields: [ + { + name: 'image', + title: 'Image with alt', + type: 'imageWithAlt', + }, + { + name: 'caption', + title: 'Image caption', + type: 'string', + }, + { + name: 'attribution', + title: 'Credit', + type: 'string', + }, + { + type: 'boolean', + name: 'captionPositionUnderImage', + title: 'Position caption and credit under image', + description: 'Toggle to display caption and credit under the image.', + initialValue: false, + }, + { + name: 'action', + title: 'Link', + type: 'array', + of: [{ type: 'linkSelector', title: 'Link' }], + description: 'Optional link associated with the image.', + validation: (Rule: Rule) => Rule.max(1).error('Only one action is permitted'), + }, + ], + preview: { + select: { + imageUrl: 'image.asset.url', + alt: 'image.alt', + caption: 'caption', + }, + prepare({ imageUrl, caption, alt }: { imageUrl: string; alt: string; caption: string }) { + return { + title: alt || 'No alt text', + subtitle: caption || 'No caption', + media: {alt}, + } + }, + }, +} diff --git a/sanityv3/schemas/objects/imageCarousel.tsx b/sanityv3/schemas/objects/imageCarousel.tsx index 5311f31bb..706c7c56f 100644 --- a/sanityv3/schemas/objects/imageCarousel.tsx +++ b/sanityv3/schemas/objects/imageCarousel.tsx @@ -71,7 +71,9 @@ export default { name: 'items', description: 'Add images for the carousel', title: 'Carousel items', - of: [{ type: 'imageWithAltAndCaption' }], + of: [{ type: 'imageWithAltAndCaption' }, + { type: 'carouselImage' } + ], validation: (Rule: Rule) => Rule.required().min(2), }, { diff --git a/web/core/Carousel/Carousel.tsx b/web/core/Carousel/Carousel.tsx index a653fe83c..1b8aee973 100644 --- a/web/core/Carousel/Carousel.tsx +++ b/web/core/Carousel/Carousel.tsx @@ -266,6 +266,7 @@ export const Carousel = forwardRef(function Carousel displayMode={displayMode} aria-label={ariaLabel} active={i === currentIndex} + action={(item as ImageCarouselItem).action} {...(variant === 'image' && displayMode === 'single' && { style: { @@ -357,9 +358,9 @@ export const Carousel = forwardRef(function Carousel variant === 'image' && displayMode === 'single' ? 'w-[var(--image-carousel-card-w-sm)] md:w-[var(--image-carousel-card-w-md)] lg:w-[var(--image-carousel-card-w-lg)] mx-auto col-start-1 col-end-1 row-start-2 row-end-2' : '' - } pt-6 pb-2 ${items.length === 3 ? 'lg:hidden' : ''} flex ${ + } pb-2 ${items.length === 3 ? 'lg:hidden' : ''} flex ${ internalAutoRotation ? 'justify-between' : 'justify-end' - }`} + } absolute bottom-10 left-0 right-0 z-10 min-w-0 mx-layout-sm`} >
diff --git a/web/core/Carousel/CarouselImageItem.tsx b/web/core/Carousel/CarouselImageItem.tsx index bb94cd8b4..56628d13a 100644 --- a/web/core/Carousel/CarouselImageItem.tsx +++ b/web/core/Carousel/CarouselImageItem.tsx @@ -1,8 +1,9 @@ import envisTwMerge from '../../twMerge' import Image from '../../pageComponents/shared/SanityImage' -import { ImageWithAlt, ImageWithCaptionData } from '../../types/index' +import { ImageWithAlt, ImageWithCaptionData, LinkData } from '../../types/index' import { DisplayModes } from './Carousel' import { forwardRef, HTMLAttributes } from 'react' +import GridLinkArrow from '@sections/Grid/GridLinkArrow' type CarouselImageItemProps = { image?: ImageWithAlt | ImageWithCaptionData @@ -12,10 +13,21 @@ type CarouselImageItemProps = { caption?: string attribution?: string active?: boolean + captionPositionUnderImage?: boolean + action?: LinkData } & HTMLAttributes - export const CarouselImageItem = forwardRef(function CarouselImageItem( - { active = false, image, caption, attribution, displayMode = 'single', className = '', ...rest }, + { + active = false, + image, + caption, + attribution, + displayMode = 'single', + className = '', + action, + captionPositionUnderImage, + ...rest + }, ref, ) { return ( @@ -27,49 +39,34 @@ export const CarouselImageItem = forwardRef - {caption || attribution ? ( -
- -
-
- {caption && {caption}} - {attribution && {attribution}} -
+ {/* Image Section */} +
+ + +
+ + {/* Caption Section */} + {(caption || attribution) && ( +
+
+ {caption && {caption}} + {attribution && {attribution}}
- ) : ( - )} - ) -}) + ); +}); \ No newline at end of file diff --git a/web/lib/queries/common/imageCarouselFields.ts b/web/lib/queries/common/imageCarouselFields.ts index c0d1e31c6..e30e164f8 100644 --- a/web/lib/queries/common/imageCarouselFields.ts +++ b/web/lib/queries/common/imageCarouselFields.ts @@ -1,3 +1,4 @@ +import linkSelectorFields from './actions/linkSelectorFields' import background from './background' export const imageCarouselFields = /* groq */ ` @@ -6,15 +7,19 @@ export const imageCarouselFields = /* groq */ ` title, ingress, hideTitle, - items[] { + items[]{ + ..., "id": _key, - ... + action[0]{ + ${linkSelectorFields}, + } }, "options": { autoplay, delay }, - "designOptions": { + captionPositionUnderImage, + "designOptions": { ${background} }, ` diff --git a/web/sections/Grid/GridLinkArrow.tsx b/web/sections/Grid/GridLinkArrow.tsx index 6aadad850..e32776892 100644 --- a/web/sections/Grid/GridLinkArrow.tsx +++ b/web/sections/Grid/GridLinkArrow.tsx @@ -1,25 +1,26 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import { twMerge } from 'tailwind-merge' -import { getUrlFromAction } from '../../common/helpers' -import { BaseLink } from '@core/Link' -import { getLocaleFromName } from '../../lib/localization' -import { ArrowRight } from '../../icons' -import { LinkData } from '../../types/index' -import { forwardRef } from 'react' +import { twMerge } from 'tailwind-merge'; +import { getUrlFromAction } from '../../common/helpers'; +import { BaseLink } from '@core/Link'; +import { getLocaleFromName } from '../../lib/localization'; +import { ArrowRight } from '../../icons'; +import { LinkData } from '../../types/index'; +import { forwardRef } from 'react'; type GridLinkArrowProps = { - action?: LinkData - className?: string - bgColor?: string -} + action?: LinkData; + className?: string; + bgColor?: string; + variant?: "square" | "circle" ; +}; const GridLinkArrow = forwardRef(function GridLinkArrow( - { action, className = '', bgColor }, + { action, className = '', bgColor, variant = 'square' }, ref, ) { - const url = action && getUrlFromAction(action) + const url = action && getUrlFromAction(action); - const variantClassName = () => { + const bgClassName = () => { switch (bgColor) { case 'bg-yellow-50': case 'bg-green-50': @@ -27,13 +28,22 @@ const GridLinkArrow = forwardRef(function Gr case 'bg-mist-blue-100': case 'bg-moss-green-50': case 'bg-spruce-wood-90': - return `text-slate-80 hover:bg-slate-80 hover:text-white-100 focus-visible:bg-slate-80 focus-visible:text-white-100` + return `text-slate-80 hover:bg-slate-80 hover:text-white-100 focus-visible:bg-slate-80 focus-visible:text-white-100`; case 'bg-white-100': - return `text-slate-80 hover:bg-grey-50 hover:text-white-100 focus-visible:bg-grey-50 focus-visible:text-white-100` + return `text-slate-80 hover:bg-grey-50 hover:text-white-100 focus-visible:bg-grey-50 focus-visible:text-white-100`; case 'bg-blue-50': case 'bg-slate-80': default: - return `text-white-100 hover:bg-white-100 hover:text-slate-80 focus-visible:bg-white-100 focus-visible:text-slate-80` + return `text-white-100 hover:bg-white-100 hover:text-slate-80 focus-visible:bg-white-100 focus-visible:text-slate-80`; + } + }; + + const variantClassName = () => { + switch (variant) { + case 'circle': + return `m-1 p-2 hover:rounded-full`; + default: + return ``; } } @@ -51,24 +61,26 @@ const GridLinkArrow = forwardRef(function Gr href={url as string} {...(action.link?.lang && { locale: getLocaleFromName(action.link?.lang) })} type={action.type} - className={`group - py-2 - px-4 - focus:outline-none - ${variantClassName()} - focus-visible:envis-outline - dark:focus-visible:envis-outline - `} + className={twMerge( + `group + py-2 + px-4 + focus:outline-none + ${bgClassName()} + ${variantClassName()} + focus-visible:envis-outline + dark:focus-visible:envis-outline`, + )} > {`${action.label} ${ action.extension ? `(${action.extension.toUpperCase()})` : '' }`} - +
)} - ) -}) + ); +}); -export default GridLinkArrow +export default GridLinkArrow; \ No newline at end of file diff --git a/web/types/imageTypes.ts b/web/types/imageTypes.ts index 06ade2a03..a507e3e47 100644 --- a/web/types/imageTypes.ts +++ b/web/types/imageTypes.ts @@ -65,4 +65,6 @@ export type ImageCarouselData = { export type ImageCarouselItem = { id: string + captionPositionUnderImage?: boolean + action?: any } & ImageWithCaptionData