diff --git a/sanityv3/schemas/objects/accordionItem.tsx b/sanityv3/schemas/objects/accordionItem.tsx index b5c1c2d36..642f8f7a8 100644 --- a/sanityv3/schemas/objects/accordionItem.tsx +++ b/sanityv3/schemas/objects/accordionItem.tsx @@ -14,6 +14,8 @@ const contentType = configureBlockContent({ h3: false, h4: false, attachment: false, + internalLink: false, + externalLink: false, }) export default { @@ -27,12 +29,28 @@ export default { type: 'string', validation: (Rule: Rule) => Rule.required().error(), }, + { + title: 'Image', + name: 'image', + type: 'image', + description: 'Image will be presented as landscape format', + options: { + hotspot: true, + collapsed: false, + }, + }, { title: 'Content', name: 'content', type: 'array', of: [contentType], }, + { + name: 'links', + type: 'array', + title: 'Links', + of: [{ type: 'linkSelector', title: 'Link' }], + }, ], preview: { select: { diff --git a/sanityv3/schemas/textSnippets.ts b/sanityv3/schemas/textSnippets.ts index f01b26a8d..6a4d0d84c 100644 --- a/sanityv3/schemas/textSnippets.ts +++ b/sanityv3/schemas/textSnippets.ts @@ -13,6 +13,7 @@ export const groups = { pensionForm: { title: 'Pension form', hidden: !Flags.HAS_PENSION_FORM }, form: { title: 'Form', hidden: !Flags.HAS_FORMS }, cookie: { title: 'Cookie' }, + carousel: { title: 'Carousel' }, others: { title: 'Others' }, common: { title: 'Common' }, } @@ -295,7 +296,7 @@ const snippets: textSnippet = { title: 'Pension Category', defaultValue: 'Pension', group: groups.pensionForm, - }, + }, pension_form_select_topic: { title: 'Default Pension Category', defaultValue: 'Pension', @@ -816,6 +817,11 @@ const snippets: textSnippet = { defaultValue: `Please don't enter any personal information`, group: groups.common, }, + carousel_controls: { + title: 'Carousel controls', + defaultValue: 'Carousel controls', + group: groups.carousel, + }, categories: { title: 'Categories', defaultValue: 'Categories', diff --git a/web/components/src/Backgrounds/BackgroundContainer.tsx b/web/components/src/Backgrounds/BackgroundContainer.tsx index a6168cae7..e5023b6b4 100644 --- a/web/components/src/Backgrounds/BackgroundContainer.tsx +++ b/web/components/src/Backgrounds/BackgroundContainer.tsx @@ -5,6 +5,7 @@ import type { BackgroundColours, BackgroundTypes, ImageBackground } from '../../ import { ColouredContainer } from './ColouredContainer' import { ImageBackgroundContainer } from './ImageBackgroundContainer' import { ColorKeyTokens } from '../../../styles/colorKeyToUtilityMap' +import envisTwMerge from '../../../twMerge' const StyledImageBackground = styled(ImageBackgroundContainer)<{ $isInverted: boolean }>` ${({ $isInverted }) => ($isInverted ? inverted : normal)} @@ -57,7 +58,7 @@ export const BackgroundContainer = forwardRef @@ -77,7 +78,7 @@ export const BackgroundContainer = forwardRef {children} diff --git a/web/core/Accordion/Accordion.tsx b/web/core/Accordion/Accordion.tsx index 1ba53c0bc..a071381af 100644 --- a/web/core/Accordion/Accordion.tsx +++ b/web/core/Accordion/Accordion.tsx @@ -9,6 +9,8 @@ interface AccordionMultipleProps extends _Accordion.AccordionMultipleProps { type: 'multiple' } +export type Variants = 'primary' | 'secondary' + export type AccordionProps = { className?: string } & (AccordionSingleProps | AccordionMultipleProps) diff --git a/web/core/Accordion/Content.tsx b/web/core/Accordion/Content.tsx index 81b12b5eb..64e17f5e7 100644 --- a/web/core/Accordion/Content.tsx +++ b/web/core/Accordion/Content.tsx @@ -1,12 +1,15 @@ -import { CSSProperties, forwardRef, useEffect, useMemo, useRef, useState } from 'react' +import { forwardRef, useMemo, useRef } from 'react' import { AccordionContent, AccordionContentProps as _AccordionContentProps } from '@radix-ui/react-accordion' import envisTwMerge from '../../twMerge' import { mergeRefs } from '@equinor/eds-utils' +import { Variants } from './Accordion' -export type AccordionContentProps = _AccordionContentProps +export type AccordionContentProps = { + variant?: Variants +} & _AccordionContentProps export const Content = forwardRef(function Content( - { children, className = '', forceMount, ...rest }, + { variant = 'primary', children, className = '', ...rest }, forwardedRef, ) { const contentRef = useRef(null) @@ -14,8 +17,8 @@ export const Content = forwardRef(functio () => mergeRefs(contentRef, forwardedRef), [contentRef, forwardedRef], ) - const [collapsedHeight, setCollapsedHeight] = useState() + /* const [collapsedHeight, setCollapsedHeight] = useState() useEffect(() => { if (!contentRef.current) { return @@ -23,24 +26,81 @@ export const Content = forwardRef(functio if (contentRef.current && forceMount) { const currentHeight = contentRef.current.clientHeight const height = Math.max(collapsedHeight ?? 0, currentHeight) - setCollapsedHeight(height) } - }, [collapsedHeight, contentRef, forceMount]) + }, [collapsedHeight, contentRef, forceMount]) */ + + const variantClassName: Partial> = { + primary: '', + secondary: '', + } + + /** + * pt-0 + ml-2.5 + border-l + border-dashed + border-energy-red-100 + pl-7 + pr-4 + pb-6 + mb-6 + [&p]:last:mb-0 + flex + flex-col + gap-6 + */ + + const getVariantBody = () => { + switch (variant) { + case 'primary': + return ( +
+ {children} +
+ ) + + default: + return <>{children} + } + } return ( - {children} + {getVariantBody()} ) }) diff --git a/web/core/Accordion/Header.tsx b/web/core/Accordion/Header.tsx index 39746805e..d1247024a 100644 --- a/web/core/Accordion/Header.tsx +++ b/web/core/Accordion/Header.tsx @@ -6,10 +6,14 @@ import { AccordionTriggerProps, } from '@radix-ui/react-accordion' import envisTwMerge from '../../twMerge' -import { chevron_down } from '@equinor/eds-icons' +import { add_circle_filled, add_circle_outlined, chevron_down, remove, remove_outlined } from '@equinor/eds-icons' import { TransformableIcon } from '../../icons/TransformableIcon' +import { Variants } from './Accordion' +import { Typography } from '@core/Typography' export type AccordionHeaderProps = { + hasSectionTitle?: boolean + variant?: Variants headerClassName?: string className?: string } & _AccordionHeaderProps & @@ -23,22 +27,126 @@ export type AccordionHeaderProps = { * @see 🏷️ {@link AccordionHeaderProps} */ export const Header = forwardRef(function Header( - { children, className = '', headerClassName = '', ...rest }, + { variant = 'primary', children, hasSectionTitle = false, className = '', headerClassName = '', ...rest }, ref, ) { + const headerVariantClassName: Partial> = { + primary: '', + secondary: '', + } + const variantClassName: Partial> = { + primary: `flex + items-center + w-full + bg-transparent + sm:py-6 + border-none + cursor-pointer + focus-visible:envis-outline + dark:focus-visible:envis-outline-invert`, + secondary: 'group/trigger w-full flex justify-between border-b py-3 border-moss-green-90', + } + const iconVariantClassName: Partial> = { + primary: '', + secondary: 'rotate-180 group-data-closed/trigger:rotate-0', + } + + const getVariantBody = () => { + switch (variant) { + case 'secondary': + return ( + <> + {children} + + + ) + default: + return ( + <> + + + + + + + + {children} + + + ) + } + } + return ( - - - {children} - - + + + + {getVariantBody()} + + ) }) diff --git a/web/core/Accordion/Item.tsx b/web/core/Accordion/Item.tsx index f443d610c..582715af2 100644 --- a/web/core/Accordion/Item.tsx +++ b/web/core/Accordion/Item.tsx @@ -1,15 +1,22 @@ import { forwardRef } from 'react' import { AccordionItem, AccordionItemProps as _AccordionItemProps } from '@radix-ui/react-accordion' import envisTwMerge from '../../twMerge' +import { Variants } from './Accordion' -export type AccordionItemProps = _AccordionItemProps +export type AccordionItemProps = { + variant?: Variants +} & _AccordionItemProps export const Item = forwardRef(function Item( - { children, className = '', ...rest }, + { variant = 'primary', children, className = '', ...rest }, forwardedRef, ) { + const variantClassName: Partial> = { + primary: 'border-b border-grey-40 dark:border-white-100', + secondary: '', + } return ( - + {children} ) diff --git a/web/core/Accordion/hooks/useDivHeight.ts b/web/core/Accordion/hooks/useDivHeight.ts new file mode 100644 index 000000000..297ab70fe --- /dev/null +++ b/web/core/Accordion/hooks/useDivHeight.ts @@ -0,0 +1,29 @@ +import { useRef, useEffect, useState } from 'react' + +export function useDivHeight() { + const ref = useRef(null) + const [height, setHeight] = useState(0) + + useEffect(() => { + const resizeObserver = new ResizeObserver(([entry]) => { + requestAnimationFrame(() => { + if (!entry) { + return + } + setHeight(entry.target.getBoundingClientRect().height) + }) + }) + + if (ref.current) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore: Do you have any suggestions here Sven + resizeObserver.observe(ref.current) + } + + return () => { + resizeObserver.disconnect() + } + }, []) + + return { ref, height } +} diff --git a/web/core/Carousel/Carousel.tsx b/web/core/Carousel/Carousel.tsx index 8cedeb95b..a653fe83c 100644 --- a/web/core/Carousel/Carousel.tsx +++ b/web/core/Carousel/Carousel.tsx @@ -27,6 +27,7 @@ import { useMediaQuery } from '../../lib/hooks/useMediaQuery' import { CarouselEventItem } from './CarouselEventItem' import { CarouselKeyNumberItem } from './CarouselKeyNumberItem' import { CarouselIframeItem } from './CarouselIframeItem' +import { FormattedMessage } from 'react-intl' export type DisplayModes = 'single' | 'scroll' export type Layouts = 'full' | 'default' @@ -360,7 +361,9 @@ export const Carousel = forwardRef(function Carousel internalAutoRotation ? 'justify-between' : 'justify-end' }`} > -
{`Carousel controls`}
+
+ +
{internalAutoRotation && ( (function Carousel title={`Go to previous`} aria-controls={carouselItemsId} mode="previous" - disabled={displayMode === 'scroll' && scrollPosition === 'start'} + disabled={(displayMode === 'scroll' && scrollPosition === 'start') ?? false} onClick={() => { if (variant === 'image' && displayMode === 'single') { loopSlidePrev() @@ -395,7 +398,7 @@ export const Carousel = forwardRef(function Carousel title={`Go to next`} mode="next" aria-controls={carouselItemsId} - disabled={displayMode === 'scroll' && scrollPosition === 'end'} + disabled={(displayMode === 'scroll' && scrollPosition === 'end') ?? false} onClick={() => { if (variant === 'image' && displayMode === 'single') { loopSlideNext() diff --git a/web/lib/queries/common/pageContentFields.ts b/web/lib/queries/common/pageContentFields.ts index e27516cdd..c0c345d1f 100644 --- a/web/lib/queries/common/pageContentFields.ts +++ b/web/lib/queries/common/pageContentFields.ts @@ -163,10 +163,14 @@ _type == "keyNumbers" =>{ "accordion": accordion[]{ "id": _key, title, + image, content[]{ ..., ${markDefs}, - } + }, + links[]{ + ${linkSelectorFields}, + }, }, "designOptions": { ${background}, diff --git a/web/pageComponents/pageTemplates/shared/SharedPageContent.tsx b/web/pageComponents/pageTemplates/shared/SharedPageContent.tsx index 41fb27160..20df29a2d 100644 --- a/web/pageComponents/pageTemplates/shared/SharedPageContent.tsx +++ b/web/pageComponents/pageTemplates/shared/SharedPageContent.tsx @@ -4,7 +4,6 @@ import FullWidthImage from '../../topicPages/FullWidthImage' import FullWidthVideo from '../../topicPages/FullWidthVideo' import Figure from '../../topicPages/Figure' import PageQuote from '../../topicPages/PageQuote' -import AccordionBlock from '../../topicPages/Accordion/AccordionBlock' import PromoTileArray from '../../../sections/PromoTiles/PromoTileArray' import IFrame from '../../topicPages/IFrame' import Promotion from '../../topicPages/Promotion' @@ -63,6 +62,7 @@ import ImageCarousel from '@sections/ImageCarousel/ImageCarousel' import { AnchorLinkList } from '@sections/AnchorLinkList' import ImageForText from '@sections/ImageForText/ImageForText' import TextWithIconArray from '@sections/TextWithIconArray/TextWithIconArray' +import AccordionBlock from '@sections/AccordionBlock/AccordionBlock' type DefaultComponent = { id?: string diff --git a/web/pageComponents/shared/SanityImage.tsx b/web/pageComponents/shared/SanityImage.tsx index dcc283f6c..38dc72ac2 100644 --- a/web/pageComponents/shared/SanityImage.tsx +++ b/web/pageComponents/shared/SanityImage.tsx @@ -1,9 +1,10 @@ +import { SanityImageObject } from '@sanity/image-url/lib/types/types' import { useSanityLoader } from '../../lib/hooks/useSanityLoader' import Img, { ImageProps } from 'next/image' import type { ImageWithAlt } from 'types' type Props = Omit & { - image: ImageWithAlt + image: ImageWithAlt | SanityImageObject maxWidth?: number aspectRatio?: number alt?: string @@ -46,7 +47,15 @@ const Image = ({ image, aspectRatio, sizes = DEFAULT_SIZES, maxWidth = DEFAULT_M } } - return {image.isDecorative + const getAltText = () => { + if ('alt' in image && image.alt) { + return image.alt + } else { + return '' + } + } + + return {getAltText()} } export default Image diff --git a/web/pageComponents/topicPages/Accordion/Accordion.tsx b/web/pageComponents/topicPages/Accordion/Accordion.tsx index 0eb85bf72..2fd3ca26b 100644 --- a/web/pageComponents/topicPages/Accordion/Accordion.tsx +++ b/web/pageComponents/topicPages/Accordion/Accordion.tsx @@ -34,6 +34,7 @@ type AccordionProps = { } const Accordion = ({ data, id, hasTitle = true, queryParamName }: AccordionProps) => { + console.log('hallo') const router = useRouter() const replaceUrl = useRouterReplace() // Query is an empty object initially https://nextjs.org/docs/routing/dynamic-routes#caveats @@ -51,6 +52,7 @@ const Accordion = ({ data, id, hasTitle = true, queryParamName }: AccordionProps } else { expandedItems = [...indices, toggledIndex].sort() } + console.log('replace url expandedItems', expandedItems) replaceUrl({ [queryParamName]: expandedItems }) } diff --git a/web/pageComponents/topicPages/promotions/pastEvents/PastEventsListItem.tsx b/web/pageComponents/topicPages/promotions/pastEvents/PastEventsListItem.tsx index 25119105f..097fdcde2 100644 --- a/web/pageComponents/topicPages/promotions/pastEvents/PastEventsListItem.tsx +++ b/web/pageComponents/topicPages/promotions/pastEvents/PastEventsListItem.tsx @@ -49,7 +49,7 @@ const PastEventsListItem = forwardRef {(parts) => { return ( -
+
{`${parts[0].value} ${parts[2].value}`} {parts[4].value}
diff --git a/web/sections/AccordionBlock/Accordion.tsx b/web/sections/AccordionBlock/Accordion.tsx new file mode 100644 index 000000000..1eee77e44 --- /dev/null +++ b/web/sections/AccordionBlock/Accordion.tsx @@ -0,0 +1,37 @@ +import { Accordion as EnvisAccordion } from '@core/Accordion' +import type { AccordionListData } from '../../types/index' +import Blocks from '../../pageComponents/shared/portableText/Blocks' +import CallToActions from '@sections/CallToActions' +import Image from '../../pageComponents/shared/SanityImage' + +const { Item, Header, Content } = EnvisAccordion + +type AccordionProps = { + hasSectionTitle?: boolean + data: AccordionListData[] + queryParamName: string + id: string +} + +const Accordion = ({ data, id, hasSectionTitle = true }: AccordionProps) => { + return ( + + {data.map((item) => { + const { id, title: itemTitle, links, content, image } = item + + return ( + +
{itemTitle}
+ + {image && image?.asset && } + {content && } + {links && } + +
+ ) + })} +
+ ) +} + +export default Accordion diff --git a/web/sections/AccordionBlock/AccordionBlock.tsx b/web/sections/AccordionBlock/AccordionBlock.tsx new file mode 100644 index 000000000..4060fcb8a --- /dev/null +++ b/web/sections/AccordionBlock/AccordionBlock.tsx @@ -0,0 +1,63 @@ +import { BackgroundContainer } from '@components' +import Accordion from './Accordion' +import { FAQPageJsonLd } from 'next-seo' + +import type { AccordionData, AccordionListData } from '../../types/index' +import { toPlainText } from '@portabletext/react' +import { Heading, Typography } from '../../core/Typography' +import { twMerge } from 'tailwind-merge' +import IngressText from '../../pageComponents/shared/portableText/IngressText' +import Image, { Ratios } from '../../pageComponents/shared/SanityImage' + +type AccordionBlockProps = { + data: AccordionData + anchor?: string + className?: string +} + +const buildJsonLdElements = (data: AccordionListData[]) => { + return data.map((item) => { + return { + questionName: item.title, + acceptedAnswerText: toPlainText(item.content), + } + }) +} + +const AccordionBlock = ({ data, anchor, className }: AccordionBlockProps) => { + const { title, ingress, designOptions, accordion, id, image, enableStructuredMarkup } = data + + return ( + <> + +
+ {image?.asset && ( +
+ +
+ )} + {title && + (Array.isArray(title) ? ( + + ) : ( + + {title} + + ))} + {ingress && } + {accordion && accordion.length > 0 && ( + + )} +
+
+ {enableStructuredMarkup && accordion && } + + ) +} + +export default AccordionBlock diff --git a/web/tailwind.config.cjs b/web/tailwind.config.cjs index 6d433c660..8d5cbcee5 100644 --- a/web/tailwind.config.cjs +++ b/web/tailwind.config.cjs @@ -351,8 +351,8 @@ module.exports = { zoomIn: 'auto linear zoom-in both', move: 'auto linear move forwards', 'spin-slow': 'spin 3s linear infinite', - slideUp: 'slideUp 3s ease-out', - slideDown: 'slideDown 3s ease-out', + slideUp: 'slideUp 0.2s ease-out', + slideDown: 'slideDown 0.2s ease-out', }, data: { open: 'state~="open"', @@ -361,6 +361,7 @@ module.exports = { vertical: 'orientation~="vertical"', horizontal: 'orientation~="horizontal"', selected: 'selected~="true"', + expanded: 'expanded~="true"', }, flex: { fr: '1 1 1', @@ -489,6 +490,12 @@ module.exports = { 'auto-fill-fr': `repeat(auto-fill, minmax(80px,1fr))`, card: `repeat(auto-fill, minmax(min(100%, theme(spacing.card-minWidth)), theme(spacing.card-maxWidth)))`, }, + scrollMargin: { + topbar: '100px', + }, + aria: { + current: 'current="page"', + }, }, }, variants: { diff --git a/web/templates/newsroom/sanity/NewsroomSanity.tsx b/web/templates/newsroom/sanity/NewsroomSanity.tsx index 62ad18ad2..4f1aa0a29 100644 --- a/web/templates/newsroom/sanity/NewsroomSanity.tsx +++ b/web/templates/newsroom/sanity/NewsroomSanity.tsx @@ -37,7 +37,6 @@ const NewsRoomTemplateSanity = forwardRef(fu fallbackImages, } = pageData || {} - console.log('localNewsPages', localNewsPages) const intl = useIntl() const router = useRouter() const { locale } = router diff --git a/web/types/types.ts b/web/types/types.ts index 6664d0fc3..f8a116d9f 100644 --- a/web/types/types.ts +++ b/web/types/types.ts @@ -1,5 +1,6 @@ import { TeaserImagePosition, TeaserImageSize } from '@components' import { PortableTextBlock } from '@portabletext/types' +import { SanityImageObject } from '@sanity/image-url/lib/types/types' import type { ImageWithCaptionData, ImageWithAlt, @@ -206,7 +207,9 @@ export type QuoteData = { export type AccordionListData = { id: string title: string + image?: SanityImageObject content: PortableTextBlock[] + links: LinkData[] } export type AccordionData = {