Skip to content

Commit

Permalink
DS-989 | Use modals for changemaker card content for all breakpoints (#…
Browse files Browse the repository at this point in the history
…373)

* Use modal for all breakpoints

* modal functionality and styles

* Change close button border to red; clean up

* Use headlessui description without nesting; update PR template

* Remove unnecessary code
  • Loading branch information
yvonnetangsu authored Dec 11, 2024
1 parent fa4ca7b commit 70138b1
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 114 deletions.
2 changes: 1 addition & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
3. Verify...

# Associated Issues and/or People
- JIRA ticket(s) - GIVCAMP-
- JIRA ticket(s) - DS-
- Other PRs
- Any other contextual information that might be helpful (e.g., description of a bug that this PR fixes, new functionality that it adds, etc.)
- Anyone who should be notified? (`@mention` them here)
2 changes: 1 addition & 1 deletion components/AnnotatedImage/AnnotatedImage.styles.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const root = 'relative self-start';
export const root = 'relative self-start annotated-image';
export const imageWrapper = 'relative @container';
export const ul = 'list-unstyled';
export const li = 'mb-0';
2 changes: 0 additions & 2 deletions components/AnnotatedImage/ImageHotspot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export const ImageHotspot = ({
descriptionSize,
}: SbImageHotspotType) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const buttonRef = useRef<HTMLButtonElement>(null);
const [isClicked, setIsClicked] = useState(false);
const panelRef = useRef<HTMLDivElement>(null);

Expand Down Expand Up @@ -58,7 +57,6 @@ export const ImageHotspot = ({
<div className={styles.hotspotWrapper} style={{ top: `${y * 100}%`, left: `${x * 100}%` }}>
<button
type="button"
ref={buttonRef}
onClick={handleClick}
aria-haspopup="dialog"
aria-label={`Open modal ${ariaLabel || heading}`}
Expand Down
24 changes: 12 additions & 12 deletions components/ChangemakerCard/ChangemakerCard.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { cnb } from 'cnbuilder';
* It caused a weird bug when you have the dialog open and click around sometimes
* it will bring the body content under the dialog overlay to the front even though it is not focusable.
*/
export const root = (isHorizontal: boolean) => cnb('relative w-full mx-auto break-words max-w-[29rem]', {
export const root = (isHorizontal: boolean) => cnb('relative w-full mx-auto break-words max-w-[29rem] bg-black', {
'sm:max-w-300 lg:max-w-[35rem]': !isHorizontal,
'sm:max-w-550 md:max-w-600 xl:max-w-1000 2xl:max-w-1200': isHorizontal,
});
Expand All @@ -30,22 +30,22 @@ export const heading = (isHorizontal: boolean) => cnb('mb-02em mt-auto text-30 w
});
export const subhead = 'text-19 md:text-21 xl:max-w-prose mx-auto whitespace-pre-line';

export const cardContent = (isHorizontal: boolean) => cnb('hidden sm:block absolute size-full top-0 left-0 aria-hidden:opacity-0 opacity-100 backdrop-blur-sm transition-opacity duration-500 bg-gradient-to-b from-gc-black/60 to-gc-black/90 *:*:*:!mb-1em', {
'px-20 py-30 pb-120 3xl:pt-48 3xl:px-36' : !isHorizontal,
'rs-pt-3 rs-px-3 pb-150': isHorizontal,
});
export const contentWrapper = (isHorizontal: boolean) => isHorizontal && 'xl:columns-2 gap-x-38 xl:first:*:*:*:mt-0';

export const button = 'group absolute size-full top-0 left-0';
export const icon = (isHorizontal: boolean) => cnb('absolute bottom-40 right-36 text-white size-65 border-2 border-white rounded-full p-16 group-hocus-visible:border-dashed group-aria-expanded:rotate-45 transition-transform group-hocus-visible:bg-gc-black/80 transition-color', {
'md:bottom-58 2xl:bottom-61 md:right-45': isHorizontal,
});

// Modal styles
export const dialog = 'relative z-[100]';
export const srOnly = 'sr-only';
export const dialogOverlay = 'fixed inset-0 bg-gc-black/80 backdrop-blur-md w-screen';
export const dialogWrapper = 'fixed inset-0 w-screen overflow-y-auto overscroll-contain';
export const dialogPanel = 'relative cc w-screen min-h-screen inset-0 pt-20 pb-60 text-white';
export const modalClose = 'block mr-0 ml-auto rs-mb-2 p-9 border-2 border-white rounded-full hocus:border-dashed transition-transform';
export const dialogOverlay = 'fixed inset-0 bg-gc-black/60 backdrop-blur-lg w-screen';
export const dialogWrapper = 'fixed inset-0 sm:py-30 w-screen overflow-y-auto overscroll-contain overflow-x-hidden';
export const dialogPanel = 'relative sm:cc flex w-screen min-h-screen inset-0 break-words justify-center items-start sm:items-center';
export const dialogContentWrapper = 'relative flex flex-col items-center justify-center max-w-prose-wide text-white bg-black-true/60';

export const modalClose = 'block ml-auto rs-mb-4 mr-20 mt-20 p-9 border-2 border-digital-red-xlight rounded-full hocus-visible:border-dashed hocus-visible:border-white hocus-visible:rotate-90 transition-transform';
export const modalIcon = 'text-white size-26';
export const modalTextWrapper = 'rs-pr-4 rs-pb-6 w-[75ch] max-w-[100vw] md:max-w-full';
export const modalHeader = 'rs-mb-3 border-l-[1.2rem] md:border-l-[1.8rem] border-digital-red-light';
export const modalHeading = 'mb-02em leading-tight ml-22 md:ml-40 2xl:ml-43 type-3 font-serif';
export const modalSubhead = 'block font-semibold big-paragraph leading-tight ml-22 md:ml-40 2xl:ml-43';
export const nestedBloksWrapper = 'rs-pl-4';
138 changes: 44 additions & 94 deletions components/ChangemakerCard/ChangemakerCard.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
'use client';
import { cnb } from 'cnbuilder';
import { useId, useRef, useState } from 'react';
import { useRef, useState } from 'react';
import {
Description, Dialog, DialogPanel, DialogTitle, Transition, TransitionChild,
} from '@headlessui/react';
import { useMediaQuery, useOnClickOutside, useToggle } from 'usehooks-ts';
import { useOnClickOutside } from 'usehooks-ts';
import { AnimateInView, type AnimationType } from '@/components/Animate';
import { Heading, type HeadingType, Text } from '@/components/Typography';
import { FlexBox } from '@/components/FlexBox';
import { HeroIcon } from '@/components/HeroIcon';
import useEscape from '@/hooks/useEscape';
import { config } from '@/utilities/config';
import { getProcessedImage } from '@/utilities/getProcessedImage';
import * as styles from './ChangemakerCard.styles';

Expand Down Expand Up @@ -41,58 +39,23 @@ export const ChangemakerCard = ({
className,
...props
}: ChangemakerCardProps) => {
const isNotPhone = useMediaQuery(`(min-width: ${config.breakpoints.sm}px)`);

const cardRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
const contentRef = useRef<HTMLDivElement>(null);
const contentId = useId();
const headingId = useId();
const [isShown, toggle, setIsShown] = useToggle();

// For the mobile modal
const [isModalOpen, setIsModalOpen] = useState(false);
const panelRef = useRef<HTMLDivElement>(null);

const handleClick = () => {
// If it's not XS breakpoint, toggle the card content and if it is shown, focus on contentRef
if (isNotPhone) {
toggle();
if (!isShown) {
contentRef.current?.focus();
}
} else {
// Open the modal if it's XS breakpoint
setIsModalOpen(true);
}
};

// If card content is shown, clicking outside it will dismiss it
useOnClickOutside(cardRef, () => {
if (isShown) {
setIsShown(false);
}
});

// If card content is shown, pressing escape will dismiss it
// and focus on the button
useEscape(() => {
if (isShown) {
setIsShown(false);
buttonRef.current?.focus();
}
useOnClickOutside(panelRef, () => {
setIsModalOpen(false);
});

return (
<>
<AnimateInView animation={animation} delay={delay}>
<article
ref={cardRef}
className={cnb('changemaker-card', styles.root(isHorizontal), className)}
{...props}
>
<div className={styles.cardInner(isHorizontal)}>
{/* Front of the card */}
<div aria-hidden={isShown} className={styles.cardFront}>
<div className={styles.cardFront}>
{!!imageSrc && (
<div className={styles.imageWrapper(isHorizontal)}>
{/* No need to use different sources for vertical cards because
Expand Down Expand Up @@ -147,44 +110,25 @@ export const ChangemakerCard = ({
</div>
)}
<FlexBox direction="col" className={styles.info(isHorizontal)}>
{heading && (
<Heading
as={headingLevel || 'h3'}
id={headingId}
leading="tight"
align="center"
color="white"
className={styles.heading(isHorizontal)}
>
{heading}
</Heading>
)}
<Heading
as={headingLevel || 'h3'}
leading="tight"
align="center"
color="white"
className={styles.heading(isHorizontal)}
>
{heading}
</Heading>
{subheading && (
<Text align="center" leading="display" color="white" className={styles.subhead}>{subheading}</Text>
)}
</FlexBox>
</div>
{/* Content layer */}
{/* Content is displayed on an overlay over the card for all breakpoint except XS */}
<FlexBox
id={contentId}
aria-labelledby={headingId}
direction="col"
className={styles.cardContent(isHorizontal)}
aria-hidden={!isShown}
>
<div ref={contentRef} tabIndex={isShown ? 0 : -1} className={styles.contentWrapper(isHorizontal)}>
{children}
</div>
</FlexBox>
<button
ref={buttonRef}
type="button"
onClick={handleClick}
aria-label={isShown ? 'Dismiss' : `Read more about ${heading}`}
aria-controls={isNotPhone ? contentId : undefined}
aria-expanded={isShown || isModalOpen}
aria-haspopup={isNotPhone ? undefined : 'dialog'}
onClick={() => setIsModalOpen(true)}
aria-label={`Read more about ${heading}`}
aria-haspopup="dialog"
className={styles.button}
>
<HeroIcon
Expand All @@ -197,7 +141,7 @@ export const ChangemakerCard = ({
</div>
</article>
</AnimateInView>
{/* Content is displayed in a modal for XS breakpoint only */}
{/* Content is displayed in a modal */}
<Transition show={isModalOpen}>
<Dialog onClose={() => setIsModalOpen(false)} className={styles.dialog}>
<TransitionChild
Expand All @@ -220,25 +164,31 @@ export const ChangemakerCard = ({
>
<div className={styles.dialogWrapper}>
<DialogPanel className={styles.dialogPanel}>
<button
type="button"
aria-label="Close modal"
onClick={() => setIsModalOpen(false)}
className={styles.modalClose}
>
<HeroIcon
noBaseStyle
focusable="false"
strokeWidth={2}
icon='close'
className={styles.modalIcon}
/>
</button>
<DialogTitle className={styles.srOnly}>{heading}</DialogTitle>
{subheading && (
<Description className={styles.srOnly}>{subheading}</Description>
)}
{children}
<div ref={panelRef} className={styles.dialogContentWrapper}>
<button
type="button"
aria-label="Close modal"
onClick={() => setIsModalOpen(false)}
className={styles.modalClose}
>
<HeroIcon
noBaseStyle
focusable="false"
strokeWidth={2}
icon='close'
className={styles.modalIcon}
/>
</button>
<div className={styles.modalTextWrapper}>
<header className={styles.modalHeader}>
<DialogTitle className={styles.modalHeading}>{heading}</DialogTitle>
{subheading &&
<Description as="span" className={styles.modalSubhead}>{subheading}</Description>
}
</header>
<div className={styles.nestedBloksWrapper}>{children}</div>
</div>
</div>
</DialogPanel>
</div>
</TransitionChild>
Expand Down
3 changes: 2 additions & 1 deletion components/RichText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
*/

export type RichTextBaseFontSizeType = 'default' | 'card' | 'changemaker' | 'changemakerHorizontal' | 'big';
export type RichTextLinkColorType = 'unset' | 'white' | 'digital-red-xlight';

export type RichTextProps = {
wysiwyg: StoryblokRichtext;
Expand All @@ -28,7 +29,7 @@ export type RichTextProps = {
type?: 'default' | 'card';
baseFontSize?: RichTextBaseFontSizeType;
textColor?: 'black' | 'white' | 'black-70';
linkColor?: 'unset' | 'white' | 'digital-red-xlight';
linkColor?: RichTextLinkColorType;
bgColor?: 'black' | 'black-50' | 'black-60' | 'black-70' | 'white' | 'none';
textAlign?: TextAlignType;
className?: string;
Expand Down
12 changes: 10 additions & 2 deletions components/Storyblok/SbCardWysiwyg.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type StoryblokRichtext } from 'storyblok-rich-text-react-renderer-ts';
import { storyblokEditable } from '@storyblok/react/rsc';
import { RichText } from '../RichText';
import { RichText, type RichTextLinkColorType } from '../RichText';

/**
* This is used only as a sub-component currently
Expand All @@ -14,6 +14,7 @@ type SbCardWysiwygProps = {
isLightText?: boolean;
},
baseFontSize?: 'default' | 'changemaker' | 'changemakerHorizontal' | 'card';
linkColor?: RichTextLinkColorType;
};

export const SbCardWysiwyg = ({
Expand All @@ -23,8 +24,15 @@ export const SbCardWysiwyg = ({
},
blok,
baseFontSize,
linkColor,
}: SbCardWysiwygProps) => (
<div {...storyblokEditable(blok)}>
<RichText baseFontSize={baseFontSize} type="card" wysiwyg={content} textColor={isLightText ? 'white' : 'black'} />
<RichText
baseFontSize={baseFontSize}
type="card"
wysiwyg={content}
textColor={isLightText ? 'white' : 'black'}
linkColor={linkColor}
/>
</div>
);
2 changes: 1 addition & 1 deletion components/Storyblok/SbChangemakerCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const SbChangemakerCard = ({
},
blok,
}: SbChangemakerCardProps) => {
const Body = !!getNumBloks(body) ? <CreateBloks blokSection={body} baseFontSize={isHorizontal ? 'changemakerHorizontal' : 'changemaker'} /> : undefined;
const Body = !!getNumBloks(body) ? <CreateBloks blokSection={body} linkColor="digital-red-xlight" /> : undefined;

return (
<ChangemakerCard
Expand Down

0 comments on commit 70138b1

Please sign in to comment.