Skip to content

Commit

Permalink
Merge branch 'main' into refactor-decouple-uswds-from-lib-bundle
Browse files Browse the repository at this point in the history
  • Loading branch information
dzole0311 committed Dec 18, 2024
2 parents 4b78de7 + 872c53a commit ac2546c
Show file tree
Hide file tree
Showing 21 changed files with 426 additions and 133 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ jobs:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PEM }}
owner: ${{ github.repository_owner }}
repositories: |
veda-ui
veda-config
- uses: actions/checkout@v4
with:
fetch-depth: 0
Expand Down
3 changes: 3 additions & 0 deletions app/scripts/components/common/banner/banner.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.usa-banner__button:after {
top: 3px;
}
170 changes: 114 additions & 56 deletions app/scripts/components/common/banner/index.tsx
Original file line number Diff line number Diff line change
@@ -1,78 +1,136 @@
import React, { useState } from 'react';
import { Icon } from '@trussworks/react-uswds';
import { decode } from 'he';
import {
USWDSBanner,
USWDSBannerContent
USWDSBannerContent,
USWDSBannerButton,
USWDSBannerFlag,
USWDSBannerHeader,
USWDSBannerIcon,
USWDSBannerGuidance,
USWDSMediaBlockBody
} from '$components/common/uswds/banner';

const BANNER_KEY = 'dismissedBannerUrl';

function hasExpired(expiryDatetime) {
const expiryDate = new Date(expiryDatetime);
const currentDate = new Date();
return !!(currentDate > expiryDate);
interface Guidance {
left?: GuidanceContent;
right?: GuidanceContent;
}

enum BannerType {
info = 'info',
warning = 'warning'
interface GuidanceContent {
icon?: string;
iconAlt?: string;
title?: string;
text?: string;
}

const infoTypeFlag = BannerType.info;
interface BannerProps {
appTitle: string;
expires: Date;
url: string;
text: string;
type?: BannerType;
headerText?: string;
headerActionText?: string;
ariaLabel?: string;
flagImgAlt?: string;
leftGuidance?: GuidanceContent;
rightGuidance?: GuidanceContent;
className?: string;
defaultIsOpen?: boolean;
contentId?: string;
}

const DEFAULT_HEADER_TEXT =
'An official website of the United States government';

const DEFAULT_HEADER_ACTION_TEXT = "Here's how you know";

const DEFAULT_GUIDANCE: Guidance = {
left: {
title: 'Official websites use .gov',
text: 'A .gov website belongs to an official government organization in the United States.',
iconAlt: 'Dot gov icon',
icon: '/img/icon-dot-gov.svg'
},
right: {
title: 'Secure .gov websites use HTTPS',
text: `
A <strong>lock</strong> or <strong>https://</strong> means you've safely
connected to the .gov website. Share sensitive information only on
official, secure websites.
`,
iconAlt: 'HTTPS icon',
icon: '/img/icon-https.svg'
}
};

const GuidanceBlock = ({
content,
className
}: {
content: GuidanceContent;
className?: string;
}) => (
<USWDSBannerGuidance className={className}>
<USWDSBannerIcon src={content.icon} alt={content.iconAlt ?? ''} />
<USWDSMediaBlockBody>
<p>
<strong>{content.title}</strong>
<br />
<span
dangerouslySetInnerHTML={{ __html: decode(content.text ?? '') }}
/>
</p>
</USWDSMediaBlockBody>
</USWDSBannerGuidance>
);

export default function Banner({
appTitle,
expires,
url,
text,
type = infoTypeFlag
headerText,
headerActionText,
ariaLabel,
flagImgAlt = '',
leftGuidance,
rightGuidance,
className = '',
defaultIsOpen = false,
contentId = 'gov-banner-content'
}: BannerProps) {
const [isOpen, setIsOpen] = useState(defaultIsOpen);

const showBanner = localStorage.getItem(BANNER_KEY) !== url;
const [isOpen, setIsOpen] = useState(showBanner && !hasExpired(expires));
const leftContent = {
...DEFAULT_GUIDANCE.left,
...leftGuidance
} as GuidanceContent;

function onClose() {
localStorage.setItem(BANNER_KEY, url);
setIsOpen(false);
}
const rightContent = {
...DEFAULT_GUIDANCE.right,
...rightGuidance
} as GuidanceContent;

return (
<div>
{isOpen && (
<div className='position-relative'>
<USWDSBanner
aria-label={appTitle}
className={type !== infoTypeFlag ? 'bg-secondary-lighter' : ''}
>
<a href={url} target='_blank' rel='noreferrer'>
<USWDSBannerContent
className='padding-top-1 padding-bottom-1'
isOpen={true}
>
<div dangerouslySetInnerHTML={{ __html: text }} />
<USWDSBanner
aria-label={ariaLabel ?? DEFAULT_HEADER_TEXT}
className={className}
>
<USWDSBannerHeader
isOpen={isOpen}
flagImg={
<USWDSBannerFlag src='/img/us_flag_small.png' alt={flagImgAlt} />
}
headerText={headerText ?? DEFAULT_HEADER_TEXT}
headerActionText={headerActionText ?? DEFAULT_HEADER_ACTION_TEXT}
>
<USWDSBannerButton
isOpen={isOpen}
onClick={() => setIsOpen((prev) => !prev)}
aria-controls={contentId}
>
{headerActionText ?? DEFAULT_HEADER_ACTION_TEXT}
</USWDSBannerButton>
</USWDSBannerHeader>

</USWDSBannerContent>
</a>
</USWDSBanner>
<div className='position-absolute top-0 right-0 margin-right-3 height-full display-flex'>
<button
className='usa-button usa-button--unstyled'
type='button'
aria-label='Close Banner'
onClick={onClose}
>
<Icon.Close />
</button>
</div>
<USWDSBannerContent id={contentId} isOpen={isOpen}>
<div className='grid-row grid-gap-lg'>
<GuidanceBlock content={leftContent} className='tablet:grid-col-6' />
<GuidanceBlock content={rightContent} className='tablet:grid-col-6' />
</div>
)}
</div>
</USWDSBannerContent>
</USWDSBanner>
);
}
13 changes: 10 additions & 3 deletions app/scripts/components/common/layout-root/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@ import { useDeepCompareEffect } from 'use-deep-compare';
import styled from 'styled-components';
import { Outlet } from 'react-router';
import { reveal } from '@devseed-ui/animation';
import { getBannerFromVedaConfig, getCookieConsentFromVedaConfig } from 'veda';
import {
getBannerFromVedaConfig,
getCookieConsentFromVedaConfig,
getSiteAlertFromVedaConfig
} from 'veda';
import MetaTags from '../meta-tags';
import PageFooter from '../page-footer';
const Banner = React.lazy(() => import('../banner'));
const SiteAlert = React.lazy(() => import('../site-alert'));
const CookieConsent = React.lazy(() => import('../cookie-consent'));

import { LayoutRootContext } from './context';
Expand Down Expand Up @@ -50,6 +55,7 @@ const PageBody = styled.div`
function LayoutRoot(props: { children?: ReactNode }) {
const cookieConsentContent = getCookieConsentFromVedaConfig();
const bannerContent = getBannerFromVedaConfig();
const siteAlertContent = getSiteAlertFromVedaConfig();
const { children } = props;
const [displayCookieConsentForm, setDisplayCookieConsentForm] =
useState<boolean>(true);
Expand All @@ -74,8 +80,9 @@ function LayoutRoot(props: { children?: ReactNode }) {
description={description || appDescription}
thumbnail={thumbnail}
/>
{bannerContent && (
<Banner appTitle={bannerContent.title} {...bannerContent} />
{bannerContent && <Banner {...bannerContent} />}
{siteAlertContent && (
<SiteAlert appTitle={siteAlertContent.title} {...siteAlertContent} />
)}
<NavWrapper
mainNavItems={mainNavItems}
Expand Down
81 changes: 81 additions & 0 deletions app/scripts/components/common/site-alert/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React, { useState } from 'react';
import { Icon } from '@trussworks/react-uswds';
import { decode } from 'he';
import { USWDSSiteAlert } from '$components/common/uswds/site-alert';

const ALERT_KEY = 'dismissedSiteAlertUrl';

function hasExpired(expiryDatetime?: Date): boolean {
if (!expiryDatetime) return false;
const expiryDate = new Date(expiryDatetime);
const currentDate = new Date();
return !!(currentDate > expiryDate);
}

enum SiteAlertType {
info = 'info',
emergency = 'emergency'
}

const infoTypeFlag = SiteAlertType.info;

interface SiteAlertProps {
appTitle: string;
expires?: Date;
content: string;
type?: SiteAlertType;
heading?: string;
showIcon?: boolean;
slim?: boolean;
className?: string;
}

export default function SiteAlert({
appTitle,
expires,
content,
type = infoTypeFlag,
heading,
showIcon = true,
slim = false,
className = ''
}: SiteAlertProps) {
const showAlert = localStorage.getItem(ALERT_KEY) !== content;
const [isOpen, setIsOpen] = useState(showAlert && !hasExpired(expires));

function onClose() {
localStorage.setItem(ALERT_KEY, content);
setIsOpen(false);
}

return (
<div>
{isOpen && (
<div className='position-relative'>
<USWDSSiteAlert
aria-label={`${appTitle} site alert`}
variant={type}
heading={heading}
showIcon={showIcon}
slim={slim}
className={`${className} ${
type !== infoTypeFlag ? 'bg-secondary-lighter' : ''
}`}
>
<div dangerouslySetInnerHTML={{ __html: decode(content) }} />
</USWDSSiteAlert>
<div className='position-absolute top-0 right-0 margin-right-3 height-full display-flex'>
<button
className='usa-button usa-button--unstyled'
type='button'
aria-label={`Close ${appTitle} site alert`}
onClick={onClose}
>
<Icon.Close />
</button>
</div>
</div>
)}
</div>
);
}
10 changes: 0 additions & 10 deletions app/scripts/components/common/uswds/banner.tsx

This file was deleted.

42 changes: 42 additions & 0 deletions app/scripts/components/common/uswds/banner/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import {
Banner,
BannerContent,
BannerButton,
BannerFlag,
BannerHeader,
BannerIcon,
BannerGuidance, MediaBlockBody
} from '@trussworks/react-uswds';

export function USWDSBanner(props) {
return <Banner {...props} />;
}

export function USWDSBannerContent(props) {
return <BannerContent {...props} />;
}

export function USWDSBannerButton(props) {
return <BannerButton {...props} />;
}

export function USWDSBannerFlag(props) {
return <BannerFlag {...props} />;
}

export function USWDSBannerHeader(props) {
return <BannerHeader {...props} />;
}

export function USWDSBannerIcon(props) {
return <BannerIcon {...props} />;
}

export function USWDSBannerGuidance(props) {
return <BannerGuidance {...props} />;
}

export function USWDSMediaBlockBody(props) {
return <MediaBlockBody {...props} />;
}
6 changes: 6 additions & 0 deletions app/scripts/components/common/uswds/site-alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import { SiteAlert } from '@trussworks/react-uswds';

export function USWDSSiteAlert(props) {
return <SiteAlert {...props} />;
}
Loading

0 comments on commit ac2546c

Please sign in to comment.