Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions .lastsync
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
9014da9bbe05bd45f38841c02e34dcb5a8e3e1d8
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@
"styled-components": "*"
},
"devDependencies": {
"npm-run-all": "^4.1.5",
"@ladle/react": "^2.1.2",
"@openstax/ts-utils": "^1.27.6",
"@openstax/ts-utils": "^1.32.5",
"@playwright/test": "^1.25.0",
"@testing-library/dom": "^10.4.0",
"@types/dompurify": "^3.0.0",
"@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^12.0.0",
"@testing-library/user-event": "^14.5.2",
Expand All @@ -59,6 +59,7 @@
"jest-environment-node": "^29.6.2",
"microbundle": "^0.15.1",
"node-fetch": "<3.0.0",
"npm-run-all": "^4.1.5",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-is": "^16.8.0",
Expand All @@ -72,6 +73,7 @@
"@sentry/react": "^7.120.3",
"classnames": "^2.3.1",
"crypto": "npm:crypto-browserify@^3.12.0",
"dompurify": "^3.0.1",
"react-aria": "^3.37.0",
"react-aria-components": "1.10.1",
"stream": "npm:stream-browserify@^3.0.0"
Expand Down
33 changes: 33 additions & 0 deletions src/components/Banner/Banner.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Banner } from "./Banner";
import renderer from 'react-test-renderer';

describe('Banner', () => {
it('matches snapshot (single message, no dismiss)', () => {
const tree = renderer.create(
<Banner messages={['This is a note']} severity='note' />
).toJSON();
expect(tree).toMatchSnapshot();
});

it('matches snapshot (multiple messages, with dismiss)', () => {
const tree = renderer.create(
<Banner
messages={['This is warning one', 'This is warning two']}
severity='warning'
onDismiss={() => () => alert('dismiss checkout')}
/>
).toJSON();
expect(tree).toMatchSnapshot();
});

it('matches snapshot (error, with dismiss)', () => {
const tree = renderer.create(
<Banner
messages={['This is an error']}
severity='error'
onDismiss={() => () => alert('dismiss checkout')}
/>
).toJSON();
expect(tree).toMatchSnapshot();
});
});
15 changes: 15 additions & 0 deletions src/components/Banner/Banner.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { Banner } from './Banner';

export const Error = () => <Banner messages={['This is an error message']} severity='error' />;

export const Warning = () => <Banner messages={['This is a warning message']} severity='warning' />;

export const Note = () => <Banner messages={['This is a note message']} severity='note' />;

export const MultipleMessages = () => <Banner messages={['First message', 'Second message', 'Third message']} severity='warning' />;

export const Dismissible = () => {
const [visible, setVisible] = React.useState(true);
return visible ? <Banner messages={['This is a dismissible warning message']} severity='warning' onDismiss={() => setVisible(false)} /> : null;
};
54 changes: 54 additions & 0 deletions src/components/Banner/Banner.styles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import styled from 'styled-components';
import { Button, ButtonLink } from '../Button';
import { colors } from '../../theme';

export type BannerSeverity = 'note' | 'warning' | 'error';

export const Severity = styled.span`
font-weight: bold;
text-transform: uppercase;
`;

export const StyledBanner = styled.div<{severity: BannerSeverity}>`
position: relative;
background: ${({severity}) => severity === 'error' ? '#F8E8EA' : '#fff5e0'};
color: ${({severity}) => severity === 'error' ? colors.palette.darkRed : '#976502'};
border: ${({severity}) => severity === 'error' ? `1px solid ${colors.palette.lightRed}` : '1px solid #fdbd3e'};
padding: .6rem 1.6rem;
margin: 0 0 1.6rem 0;
line-height: 2rem;
display: flex;
align-items: center;

a {
text-decoration: none;
color: ${colors.palette.mediumBlue};

&:hover {
text-decoration: underline;
color: ${colors.link.hover}
}
}

${ButtonLink} {
font-size: 1.6rem;
}
`;

export const CloseButton = styled(Button)<{severity: BannerSeverity}>`
color: ${({severity}) => severity === 'error' ? colors.palette.darkRed : '#976502'};
overflow: visible;
background: none;
border: none;
padding: 0;
font: inherit;
cursor: pointer;
outline: inherit;
box-shadow: none;
margin-left: 2.4rem;

&:not([disabled]):hover,
&:not([disabled]):active {
background: none;
}
`;
23 changes: 23 additions & 0 deletions src/components/Banner/Banner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { DismissIcon } from "../DismissIcon";
import { Html } from "../Html";
import { CloseButton, Severity, StyledBanner, BannerSeverity } from "./Banner.styles";

export const Banner = (props: {messages: string[]; severity: BannerSeverity; onDismiss?: () => void}) => {
const numWarnings = props.messages.length;

return <StyledBanner severity={props.severity}>
<div>
{props.severity !== 'error' ? <Severity>{props.severity === 'note' ? 'Note: ' : 'Warning: '}</Severity> : null}
{props.messages.map((message, i) =>
<Html block={numWarnings > 1} key={i}>
{numWarnings > 1 ? `[${i + 1} of ${numWarnings}]: ${message}`: message}
</Html>
)}
</div>
{props.onDismiss
? <CloseButton severity={props.severity} onClick={props.onDismiss} aria-label='dismiss'>
<DismissIcon aria-hidden='true' focusable='false' />
</CloseButton>
: null}
</StyledBanner>;
};
16 changes: 16 additions & 0 deletions src/components/Banner/GracePeriodNotification.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { GracePeriodNotification } from "./GracePeriodNotification";
import renderer from 'react-test-renderer';

describe('GracePeriodNotification', () => {
it('matches snapshot', () => {
const tree = renderer.create(
<GracePeriodNotification
date='December 31, 2024'
paymentsFaqUrl='https://help.openstax.org/s/article/Assignable-Payments-FAQS'
handleCheckout={() => () => alert('handle checkout')}
onDismiss={() => () => alert('dismiss checkout')}
/>
).toJSON();
expect(tree).toMatchSnapshot();
});
});
9 changes: 9 additions & 0 deletions src/components/Banner/GracePeriodNotification.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { GracePeriodNotification } from "./GracePeriodNotification";

export const Default = () =>
<GracePeriodNotification
date='December 31, 2024'
paymentsFaqUrl='https://help.openstax.org/s/article/Assignable-Payments-FAQS'
handleCheckout={() => alert('handle checkout')}
onDismiss={() => alert('dismiss')}
/>;
18 changes: 18 additions & 0 deletions src/components/Banner/GracePeriodNotification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ButtonLink } from "../Button";
import { CloseButton, StyledBanner } from "./Banner.styles";
import { DismissIcon } from "../DismissIcon";


export const GracePeriodNotification =
(props: {date: string; paymentsFaqUrl: string; handleCheckout: () => void; onDismiss: () => void}) =>
<StyledBanner severity='warning'>
<div>
<span>Note: </span>
Your free access to this course expires on {props.date}. Purchase extended course access <ButtonLink
onClick={props.handleCheckout}>here</ButtonLink> or read more about payments on our <a href={props.paymentsFaqUrl}
target="_blank" rel="noreferrer">FAQ page</a>.
</div>
<CloseButton severity='warning' onClick={props.onDismiss} aria-label='dismiss'>
<DismissIcon aria-hidden='true' focusable='false' />
</CloseButton>
</StyledBanner>;
136 changes: 136 additions & 0 deletions src/components/Banner/__snapshots__/Banner.spec.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Banner matches snapshot (error, with dismiss) 1`] = `
<div
className="sc-jSMfEi iGEqwr"
>
<div>
<span
dangerouslySetInnerHTML={
Object {
"__html": "This is an error",
}
}
/>
</div>
<button
aria-label="dismiss"
className="sc-bczRLJ sc-gKXOVf dOOknK dwyZcw"
onClick={[Function]}
severity="error"
>
<svg
aria-hidden="true"
focusable="false"
height="15px"
version="1.1"
viewBox="0 0 15 15"
width="15px"
>
<g
fill="none"
fillRule="evenodd"
stroke="none"
strokeWidth="1"
>
<g
fill="currentColor"
transform="translate(-302.000000, -18.000000)"
>
<g
transform="translate(302.000000, 18.000000)"
>
<path
d="M7.5,5.41522791 L12.0331524,0.579865364 C12.3077536,0.286957429 12.7165503,0.24816296 12.946282,0.493210121 L13.9861449,1.60239723 C14.2158766,1.84744439 14.1795068,2.28349422 13.9049056,2.57640216 L9.37175324,7.41176471 L13.9049056,12.2471273 C14.1795068,12.5400352 14.2158766,12.976085 13.9861449,13.2211322 L12.946282,14.3303193 C12.7165503,14.5753665 12.3077536,14.536572 12.0331524,14.243664 L7.5,9.4083015 L2.96684761,14.243664 C2.69224642,14.536572 2.2834497,14.5753665 2.05371799,14.3303193 L1.01385508,13.2211322 C0.784123363,12.976085 0.820493178,12.5400352 1.09509437,12.2471273 L5.62824676,7.41176471 L1.09509437,2.57640216 C0.820493178,2.28349422 0.784123363,1.84744439 1.01385508,1.60239723 L2.05371799,0.493210121 C2.2834497,0.24816296 2.69224642,0.286957429 2.96684761,0.579865364 L7.5,5.41522791 Z"
/>
</g>
</g>
</g>
</svg>
</button>
</div>
`;

exports[`Banner matches snapshot (multiple messages, with dismiss) 1`] = `
<div
className="sc-jSMfEi lmuhoy"
>
<div>
<span
className="sc-eCYdqJ EIRbC"
>
Warning:
</span>
<div
dangerouslySetInnerHTML={
Object {
"__html": "[1 of 2]: This is warning one",
}
}
/>
<div
dangerouslySetInnerHTML={
Object {
"__html": "[2 of 2]: This is warning two",
}
}
/>
</div>
<button
aria-label="dismiss"
className="sc-bczRLJ sc-gKXOVf dOOknK fGvwkl"
onClick={[Function]}
severity="warning"
>
<svg
aria-hidden="true"
focusable="false"
height="15px"
version="1.1"
viewBox="0 0 15 15"
width="15px"
>
<g
fill="none"
fillRule="evenodd"
stroke="none"
strokeWidth="1"
>
<g
fill="currentColor"
transform="translate(-302.000000, -18.000000)"
>
<g
transform="translate(302.000000, 18.000000)"
>
<path
d="M7.5,5.41522791 L12.0331524,0.579865364 C12.3077536,0.286957429 12.7165503,0.24816296 12.946282,0.493210121 L13.9861449,1.60239723 C14.2158766,1.84744439 14.1795068,2.28349422 13.9049056,2.57640216 L9.37175324,7.41176471 L13.9049056,12.2471273 C14.1795068,12.5400352 14.2158766,12.976085 13.9861449,13.2211322 L12.946282,14.3303193 C12.7165503,14.5753665 12.3077536,14.536572 12.0331524,14.243664 L7.5,9.4083015 L2.96684761,14.243664 C2.69224642,14.536572 2.2834497,14.5753665 2.05371799,14.3303193 L1.01385508,13.2211322 C0.784123363,12.976085 0.820493178,12.5400352 1.09509437,12.2471273 L5.62824676,7.41176471 L1.09509437,2.57640216 C0.820493178,2.28349422 0.784123363,1.84744439 1.01385508,1.60239723 L2.05371799,0.493210121 C2.2834497,0.24816296 2.69224642,0.286957429 2.96684761,0.579865364 L7.5,5.41522791 Z"
/>
</g>
</g>
</g>
</svg>
</button>
</div>
`;

exports[`Banner matches snapshot (single message, no dismiss) 1`] = `
<div
className="sc-jSMfEi lmuhoy"
>
<div>
<span
className="sc-eCYdqJ EIRbC"
>
Note:
</span>
<span
dangerouslySetInnerHTML={
Object {
"__html": "This is a note",
}
}
/>
</div>
</div>
`;
Loading
Loading