Skip to content

CTA test #496

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions components/Cta/Cta.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { type CtaIconLeftMarginType } from './Cta.types';

export const cta = 'group/cta transition-all';
export const buttonBase = 'block font-normal w-fit no-underline hocus:underline';
// hocus to plum dark gradient instead of solid plum dark to avoid a flash of white background on hocus
export const gradientButtonBase = 'bg-gradient-to-tr hocus:from-plum-dark hocus:to-plum-dark text-white hocus:text-white';
export const textLinkBase = 'block font-semibold w-fit no-underline text-18 md:text-20';
export const gradientTextLinkBase = 'bg-clip-text bg-gradient-to-tr text-transparent hocus:text-transparent';

// Maps to linkButtonStyle props in SbCtaLink. Only used for the Button style.
export const ctaButtonStyles = {
// Primary
'ood-cta__button--primary su-after-bg-white': 'bg-bay-dark text-white hocus:bg-palo-alto hocus:text-white',
// Secondary
'ood-cta__button--secondary su-after-bg-bay-dark su-after-bg-hocus-white': 'bg-white text-bay-dark shadow-bay-dark shadow-[inset_0_0_0_1px] after:text-bay-dark after:bg-bay-dark hocus:bg-bay-dark hocus:text-white hocus:after:text-white ',
// Give Now Button
'su-bg-digital-red su-bg-hocus-plum-dark su-text-white su-text-hocus-white su-after-bg-white su-after-bg-hocus-white': 'bg-digital-red hocus:bg-plum-dark text-white hocus:text-white',
// Secondary Give Now Button
'su-bg-white su-bg-hocus-plum-dark su-text-digital-red su-text-hocus-white su-after-bg-digital-red su-after-bg-hocus-white': 'bg-white hocus:bg-plum-dark text-digital-red hocus:text-white after:bg-digital-red ',
// Ghost Button
'ood-cta__button--ghost su-after-bg-white': 'text-white bg-transparent shadow-white shadow-[inset_0_0_0_1px] transition-shadow hocus:text-white hocus:shadow-[inset_0_0_0_3px]',
// Solid Campaign Plum Button
'su-bg-plum su-bg-hocus-plum-dark su-text-white su-text-hocus-white su-after-bg-white su-after-bg-hocus-white': 'bg-plum hocus:bg-plum-dark text-white hocus:text-white',
// Gradient Campaign Buttons
'su-bg-cardinal-dark-to-spirited-dark su-bg-hocus-plum-dark su-text-white su-text-hocus-white su-after-bg-white su-after-bg-hocus-white su-transition-none': `${gradientButtonBase} from-cardinal-red-dark to-spirited-dark`,
'su-bg-plum-to-digital-red su-bg-hocus-plum-dark su-text-white su-text-hocus-white su-after-bg-white su-after-bg-hocus-white su-transition-none': `${gradientButtonBase} from-plum to-digital-red`,
'su-bg-plum-to-spirited-dark su-bg-hocus-plum-dark su-text-white su-text-hocus-white su-after-bg-white su-after-bg-hocus-white su-transition-none': `${gradientButtonBase} from-plum to-spirited-dark`,
'su-bg-palo-alto-dark-to-palo-verde-dark su-bg-hocus-plum-dark su-text-white su-text-hocus-white su-after-bg-white su-after-bg-hocus-white su-transition-none': `${gradientButtonBase} from-palo-alto-dark to-palo-verde-dark`,
'su-bg-sky-dark-to-olive-dark su-bg-hocus-plum-dark su-text-white su-text-hocus-white su-after-bg-white su-after-bg-hocus-white su-transition-none': `${gradientButtonBase} from-sky-dark to-olive-dark`,
'su-bg-sky-dark-to-bay-dark su-bg-hocus-plum-dark su-text-white su-text-hocus-white su-after-bg-white su-after-bg-hocus-white su-transition-none': `${gradientButtonBase} from-sky-dark to-bay-dark`,
};

// Maps to linkTextColor prop in SbCtaLink. Only used for the text link style.
export const ctaTextColors = {
'su-text-digital-red su-after-bg-digital-red su-text-hocus-sky-dark su-after-bg-hocus-sky-dark': 'text-digital-red hocus:text-sky-dark',
'su-text-white su-text-hocus-white su-hocus-underline su-after-bg-white su-after-bg-hocus-white': 'text-white hocus:text-white',
// Gradient text links for Campaign pages
'ood-cta__link-gradient su-bg-sky-dark-to-bay-dark su-after-bg-sky-dark-to-bay-dark': `${gradientTextLinkBase} from-sky-dark to-bay-dark *:[&_svg]:text-bay-dark *:[&_svg]:hocus:text-bay-dark`,
'ood-cta__link-gradient su-bg-cardinal-dark-to-spirited-dark su-after-bg-cardinal-dark-to-spirited-dark': `${gradientTextLinkBase} from-cardinal-red-dark to-spirited-dark *:[&_svg]:text-spirited-dark *:[&_svg]:hocus:text-spirited-dark`,
'ood-cta__link-gradient su-bg-plum-to-digital-red su-after-bg-plum-to-digital-red': `${gradientTextLinkBase} from-plum to-digital-red *:[&_svg]:text-digital-red *:[&_svg]:hocus:text-digital-red`,
'ood-cta__link-gradient su-bg-plum-to-spirited-dark su-after-bg-plum-to-spirited-dark': `${gradientTextLinkBase} from-plum to-spirited-dark *:[&_svg]:text-spirited-dark *:[&_svg]:hocus:text-spirited-dark`,
'ood-cta__link-gradient su-bg-palo-alto-dark-to-palo-verde-dark su-after-bg-palo-alto-dark-to-palo-verde-dark': `${gradientTextLinkBase} from-palo-alto-dark to-palo-verde-dark *:[&_svg]:text-palo-verde-dark *:[&_svg]:hocus:text-palo-verde-dark`,
'ood-cta__link-gradient su-bg-sky-dark-to-olive-dark su-after-bg-sky-dark-to-olive-dark': `${gradientTextLinkBase} from-sky-dark to-olive-dark *:[&_svg]:text-olive-dark *:[&_svg]:hocus:text-olive-dark`,
// Has an extra su-after-bg-sky-dark-to-bay-dark in SB, but it seems to work here without the dupe string
'ood-cta__link-gradient su-bg-sky-dark-to-bay-dark': `${gradientTextLinkBase} from-sky-dark to-bay-dark *:[&_svg]:text-bay-dark *:[&_svg]:hocus:text-bay-dark`,
/**
* Campaign page only solid text colors - seems on live site the intent was to use plum-dark as the hocus color, but it was overridden by the base link hocus color
* Here we honor the original intent by using plum-dark as the hocus color
*/
'su-text-lagunita-dark su-after-bg-lagunita-dark su-text-hocus-plum-dark su-after-bg-hocus-plum-dark': 'text-lagunita-dark hocus:text-plum-dark',
'su-text-palo-verde su-after-bg-palo-verde su-text-hocus-plum-dark su-after-bg-hocus-plum-dark': 'text-palo-verde hocus:text-plum-dark',
'su-text-plum su-after-bg-plum su-text-hocus-plum-dark su-after-bg-hocus-plum-dark': 'text-plum hocus:text-plum-dark',
'su-text-brick su-after-bg-brick su-text-hocus-plum-dark su-after-bg-hocus-plum-dark': 'text-brick hocus:text-plum-dark',
'su-text-cardinal-red su-after-bg-cardinal-red su-text-hocus-plum-dark su-after-bg-hocus-plum-dark': 'text-cardinal-red hocus:text-plum-dark',
'su-text-palo-alto su-after-bg-palo-alto su-text-hocus-plum-dark su-after-bg-hocus-plum-dark': 'text-palo-alto hocus:text-plum-dark',
'su-text-bay-dark su-after-bg-bay-dark su-text-hocus-plum-dark su-after-bg-hocus-plum-dark': 'text-bay-dark hocus:text-plum-dark',
'su-text-sky-dark su-after-bg-sky-dark su-text-hocus-plum-dark su-after-bg-hocus-plum-dark': 'text-sky-dark hocus:text-plum-dark',
'su-text-lagunita su-after-bg-lagunita su-text-hocus-plum-dark su-after-bg-hocus-plum-dark': 'text-lagunita hocus:text-plum-dark',
};

// Additional CTA variants we use for this site, e.g., as subcomponents for other components. These include styles for sizes, colors, icon styles, and other properties.
export const ctaVariants = {
'local-footer': 'text-digital-red hocus:text-black underline leading-snug font-normal text-16 md:text-18 *:[&_svg]:hocus:text-digital-red',
};

// Maps to linkButtonSize prop in SbCtaLink. Only used for the button styles
export const ctaButtonSizes = {
default: 'pt-11 pb-12 px-30 text-18 md:text-20',
'ood-cta__button--medium': 'pt-11 pb-12 px-30 md:py-14 md:px-34 text-20 md:text-24',
'ood-cta__button--large': 'py-16 px-30 md:py-20 md:px-36 text-22 md:text-28',
};

// Maps to linkIcon prop in SbCtaLink
export const ctaIcons = {
'su-link--action': 'chevron-right',
'su-link--jump': 'chevron-down',
'su-link--external': 'external',
'su-link--internal': 'lock',
'su-link--download': 'download',
'su-link--video': 'video',
'su-link--no-icon': '',
};

// Common styles for CTA icons
export const icon = 'inline-block will-change-transform transition-transform stroke-2';

// Icons have left margins
// Only add to this map if left margin is different from default class ml-04em
export const iconLeftMarginDefault = 'ml-04em';
export const iconLeftMargin: CtaIconLeftMarginType = {
'su-link--action': 'ml-03em',
};

// Maps to linkIcon prop in SbCtaLink. Animation preselected based on the icon type
export const iconAnimations = {
'su-link--action': 'group-hover/cta:translate-x-02em group-focus-visible/cta:translate-x-02em',
'su-link--jump': 'group-hover/cta:translate-y-02em group-focus-visible/cta:translate-y-02em',
'su-link--external': 'group-hover/cta:translate-x-01em group-focus-visible/cta:translate-x-01em group-hover/cta:-translate-y-01em group-focus-visible/cta:-translate-y-01em',
'su-link--internal': 'group-hover/cta:fill-current',
'su-link--download': 'group-hover/cta:translate-y-02em group-focus-visible/cta:translate-y-02em',
'su-link--video': 'group-hover/cta:translate-x-02em group-focus-visible/cta:translate-x-02em',
'su-link--no-icon': '',
};

export const ctaAligns = {
left: 'su-text-left',
center: 'su-text-center mx-auto',
right: 'su-text-right ml-auto mr-0',
};
31 changes: 31 additions & 0 deletions components/Cta/Cta.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { type HeroIconProps } from '@/components/HeroIcon';
import { type MarginType } from '@/utilities/datasource';
import * as styles from './Cta.styles';

export type CtaButtonStyleType = keyof typeof styles.ctaButtonStyles;
export type CtaTextColorType = keyof typeof styles.ctaTextColors;
export type CtaButtonSizeType = keyof typeof styles.ctaButtonSizes;
export type CtaVariantType = keyof typeof styles.ctaVariants;
export type CtaAlignType = keyof typeof styles.ctaAligns;

export type CtaIconType = keyof typeof styles.ctaIcons;
export type IconAnimationType = keyof typeof styles.iconAnimations | '';

export type CtaIconLeftMarginType = Partial<{
[Key in CtaIconType]: string;
}>;

export interface CtaCommonProps {
srText?: string;
icon?: CtaIconType;
isButton?: boolean;
textColor?: CtaTextColorType;
buttonSize?: CtaButtonSizeType;
buttonStyle?: CtaButtonStyleType;
variant?: CtaVariantType;
align?: CtaAlignType;
iconProps?: Omit<HeroIconProps, 'icon'> & React.ComponentProps<'svg'>;
mt?: MarginType;
mb?: MarginType;
children?: React.ReactNode;
};
60 changes: 60 additions & 0 deletions components/Cta/CtaButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use client';
import React from 'react';
import { cnb } from 'cnbuilder';
import { CtaContent } from './CtaContent';
import { type CtaCommonProps } from './Cta.types';
import { marginTops, marginBottoms } from '@/utilities/datasource';
import * as styles from './Cta.styles';

export type CtaButtonProps = React.ComponentPropsWithoutRef<'button'> & CtaCommonProps;

export const CtaButton = React.forwardRef<HTMLButtonElement, CtaButtonProps>((props, ref) => {
const {
type = 'button',
isButton,
buttonStyle = 'ood-cta__button--primary su-after-bg-white',
buttonSize = 'default',
textColor = 'su-text-digital-red su-after-bg-digital-red su-text-hocus-sky-dark su-after-bg-hocus-sky-dark',
variant,
align,
icon,
iconProps,
srText,
mt,
mb,
children,
className,
...rest
} = props;

return (
<button
{...rest}
type={type}
ref={ref as React.ForwardedRef<HTMLButtonElement>}
className={cnb(
styles.cta,
styles.ctaAligns[align],
isButton ? styles.buttonBase : '',
!isButton && !variant ? styles.textLinkBase : '',
isButton ? styles.ctaButtonStyles[buttonStyle] : '',
isButton ? styles.ctaButtonSizes[buttonSize] : '',
!isButton && !variant ? styles.ctaTextColors[textColor] : '',
variant ? styles.ctaVariants[variant] : '',
mt ? marginTops[mt] : '',
mb ? marginBottoms[mb] : '',
className,
)}
>
<CtaContent
buttonStyle={buttonStyle}
icon={icon}
iconProps={iconProps}
srText={srText}
align={align}
>
{children}
</CtaContent>
</button>
);
});
37 changes: 37 additions & 0 deletions components/Cta/CtaContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { cnb } from 'cnbuilder';
import { HeroIcon, type IconType } from '@/components/HeroIcon';
import { SrOnlyText } from '@/components/Typography';
import * as styles from './Cta.styles';
import * as types from './Cta.types';

type CtaContentProps = Omit<types.CtaCommonProps, 'buttonSize' | 'textColor'>;

export const CtaContent = ({
icon = 'su-link--action',
iconProps,
srText,
children,
}: CtaContentProps) => {
const heroIcon = icon ? styles.ctaIcons[icon] as IconType : undefined;
const iconMarginLeft = children && icon ? styles.iconLeftMargin[icon] || styles.iconLeftMarginDefault : '';
const { className: iconClasses, ...iProps } = iconProps || {};
const iconAnimate = icon ? styles.iconAnimations[icon] : '';

return (
<>
{children}
{/* Use this whitespace-nowrap trick so icon won't get pushed to the next line on its own */}
{heroIcon && (
<span className="whitespace-nowrap">
&#65279;
<HeroIcon
icon={heroIcon}
className={cnb(styles.icon, iconAnimate, iconMarginLeft, iconClasses)}
{...iProps}
/>
</span>
)}
{srText && <SrOnlyText>{srText}</SrOnlyText>}
</>
);
};
78 changes: 78 additions & 0 deletions components/Cta/CtaExternalLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
'use client';
import React, { useEffect, useState } from 'react';
import { cnb } from 'cnbuilder';
import { CtaContent } from './CtaContent';
import { type CtaCommonProps } from './Cta.types';
import { type SbLinkType } from '../Storyblok/Storyblok.types';
import { marginTops, marginBottoms } from '@/utilities/datasource';
import * as styles from './Cta.styles';
import useUTMs from '@/hooks/useUTMs';

export type CtaExternalLinkProps = React.ComponentPropsWithoutRef<'a'> & CtaCommonProps & {
sbLink?: SbLinkType;
href?: string;
rel?: string;
};

export const CtaExternalLink = React.forwardRef<HTMLAnchorElement, CtaExternalLinkProps>((props, ref) => {
const {
isButton,
buttonStyle = 'ood-cta__button--primary su-after-bg-white',
buttonSize = 'default',
textColor = 'su-text-digital-red su-after-bg-digital-red su-text-hocus-sky-dark su-after-bg-hocus-sky-dark',
variant,
icon,
iconProps,
align,
srText,
rel,
mt,
mb,
children,
className,
href,
...rest
} = props;

// Add UTM params to Stanford URLs.
const { isStanfordUrl, addUTMsToUrl } = useUTMs();
const [myHref, setMyHref] = useState<string>(href);
useEffect(() => {
if (typeof window === 'undefined') return;
if (isStanfordUrl(href)) {
setMyHref(addUTMsToUrl(href));
}
}, [href, isStanfordUrl, addUTMsToUrl]);

return (
<a
{...rest}
href={myHref}
rel={rel}
ref={ref as React.ForwardedRef<HTMLAnchorElement>}
className={cnb(
styles.cta,
styles.ctaAligns[align],
isButton ? styles.buttonBase : '',
!isButton && !variant ? styles.textLinkBase : '',
isButton ? styles.ctaButtonStyles[buttonStyle] : '',
isButton ? styles.ctaButtonSizes[buttonSize] : '',
!isButton && !variant ? styles.ctaTextColors[textColor] : '',
variant ? styles.ctaVariants[variant] : '',
mt ? marginTops[mt] : '',
mb ? marginBottoms[mb] : '',
className,
)}
>
<CtaContent
buttonStyle={buttonStyle}
icon={icon}
iconProps={iconProps}
srText={srText}
align={align}
>
{children}
</CtaContent>
</a>
);
});
Loading