Skip to content

[WNMGDS-3191] Note Content Component #3419

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

Merged
merged 44 commits into from
Mar 24, 2025
Merged
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
abffb46
Initial styling and component setup
jack-ryan-nava-pbc Jan 29, 2025
1ee0b46
Adding support for quotations
jack-ryan-nava-pbc Jan 29, 2025
d7694e9
Refining styling
jack-ryan-nava-pbc Jan 30, 2025
f4264e2
[WNMGDS-3183] Validate compatibility with different React versions th…
pwolfert Jan 29, 2025
d43477b
[WNMGDS-3166] Add eMedicare footer web-component (#3405)
tamara-corbalt Jan 31, 2025
b29fbf3
Author and citation logic implemented
jack-ryan-nava-pbc Jan 31, 2025
778c011
Beginnging tests
jack-ryan-nava-pbc Jan 31, 2025
0fe52af
Merge branch 'main' into jryan/WNMGDS-3191-box-content
jack-ryan-nava-pbc Jan 31, 2025
a26c38a
Updated tests, use icon and update display logic a bit
jack-ryan-nava-pbc Feb 3, 2025
0dfcf0f
Export the BoxContent component
jack-ryan-nava-pbc Feb 3, 2025
9a7d854
New VRTs
jack-ryan-nava-pbc Feb 3, 2025
92cd24e
Update props table
jack-ryan-nava-pbc Feb 3, 2025
6d8f332
Update tests clean up logic in BoxQuotation
jack-ryan-nava-pbc Feb 4, 2025
2576e3d
Simple clean up
jack-ryan-nava-pbc Feb 4, 2025
a448676
Make BoxContent more generic
jack-ryan-nava-pbc Feb 4, 2025
4b3c544
Update specs
jack-ryan-nava-pbc Feb 4, 2025
dfb1485
Merge branch 'main' into jryan/WNMGDS-3191-box-content
jack-ryan-nava-pbc Feb 4, 2025
70b27b7
snapshots after refactor
jack-ryan-nava-pbc Feb 4, 2025
93355f1
Support forced colors mode and simplify some vars
jack-ryan-nava-pbc Feb 5, 2025
f00e06a
Allow for additional classes to be added to the box quotation element.
jack-ryan-nava-pbc Feb 5, 2025
d244b22
dont wrap icon in h2 and update specs
jack-ryan-nava-pbc Feb 5, 2025
ced1952
Update snapshots again
jack-ryan-nava-pbc Feb 5, 2025
485d679
Merge branch 'main' into jryan/WNMGDS-3191-box-content
jack-ryan-nava-pbc Feb 6, 2025
20b2062
Update citation styling
jack-ryan-nava-pbc Feb 6, 2025
3597da8
Attempt at nested stories
jack-ryan-nava-pbc Feb 7, 2025
9413587
Separated boxquotation into a separate story
jack-ryan-nava-pbc Feb 7, 2025
2e61ba9
Add underlying html to storybook props update tests and snapshots and…
jack-ryan-nava-pbc Feb 10, 2025
8311618
Merge branch 'main' into jryan/WNMGDS-3191-box-content
jack-ryan-nava-pbc Feb 10, 2025
46befda
Update snapshots
jack-ryan-nava-pbc Feb 11, 2025
758b22a
lessen severity of missing props make a function into a component and…
jack-ryan-nava-pbc Feb 12, 2025
101ec5d
Update failing unit snapshot
jack-ryan-nava-pbc Feb 12, 2025
3aab46b
Merge branch 'main' into jryan/WNMGDS-3191-box-content
jack-ryan-nava-pbc Feb 13, 2025
96a974f
Merge branch 'main' into jryan/WNMGDS-3191-box-content
jack-ryan-nava-pbc Feb 18, 2025
745da8e
Merge branch 'main' into jryan/WNMGDS-3191-box-content
jack-ryan-nava-pbc Feb 19, 2025
3850dd0
Merge branch 'main' into jryan/WNMGDS-3191-box-content
jack-ryan-nava-pbc Feb 27, 2025
250c150
Merge branch 'main' into jryan/WNMGDS-3191-box-content
jack-ryan-nava-pbc Mar 5, 2025
741036e
Merge branch 'main' into jryan/WNMGDS-3191-box-content
jack-ryan-nava-pbc Mar 13, 2025
93cf3dc
The great renaming
jack-ryan-nava-pbc Mar 13, 2025
be5b4a2
Merge branch 'main' into jryan/WNMGDS-3191-box-content
jack-ryan-nava-pbc Mar 18, 2025
c70abef
Rename story
jack-ryan-nava-pbc Mar 19, 2025
74b5466
Merge branch 'main' into jryan/WNMGDS-3191-box-content
jack-ryan-nava-pbc Mar 24, 2025
663f531
Updated test snapshots
jack-ryan-nava-pbc Mar 24, 2025
fdfe1f8
Merge branch 'main' into jryan/WNMGDS-3191-box-content
jack-ryan-nava-pbc Mar 24, 2025
eec298a
Merge branch 'main' into jryan/WNMGDS-3191-box-content
jack-ryan-nava-pbc Mar 24, 2025
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
36 changes: 36 additions & 0 deletions packages/design-system/src/components/NoteBox/NoteBox.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Meta, StoryObj } from '@storybook/react';
import NoteBox from './NoteBox';

const meta: Meta = {
component: NoteBox,
argTypes: {
children: { control: 'text' },
bordered: { control: 'boolean', defaultValue: true },
heading: { control: 'text' },
headingLevel: { control: 'text' },
},
parameters: {
docs: {
underlyingHtmlElements: ['aside'],
},
},
};

export default meta;
type Story = StoryObj<typeof NoteBox>;

const NoteBoxTemplate: Story = {
render: ({ ...args }) => {
return <NoteBox {...args}>{args.children}</NoteBox>;
},
};

export const Default = {
...NoteBoxTemplate,
args: {
heading: 'The Inflation Reduction Act',
children:
"The Inflation Reduction Act keeps these savings and lower costs through 2025. If you qualify for savings, you'll find out the lower costs when you shop for plans.",
bordered: true,
},
};
95 changes: 95 additions & 0 deletions packages/design-system/src/components/NoteBox/NoteBox.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { QuotationMarkIcon } from '../Icons';
import NoteBox from './NoteBox';
import { NoteBoxQuotation } from './NoteBoxQuotation';
import { render, screen } from '@testing-library/react';

const defaultProps = {
children: 'This is foo text. Bar!',
heading: 'Test Heading',
};

function renderNoteBox(props = {}) {
return render(<NoteBox {...defaultProps} {...props} />);
}

describe('NoteBox', () => {
it('renders as complementary content', () => {
renderNoteBox();
expect(screen.getByRole('complementary')).toMatchSnapshot();
});

it('renders heading properly', () => {
renderNoteBox();
const headingElement = screen.getByText(/Test Heading/);
expect(headingElement).toBeInTheDocument();
});

it('renders just child content', () => {
renderNoteBox({ heading: null });
const headingElement = screen.queryByText(/Test Heading/);
const childElement = screen.getByText(/This is foo text. Bar!/);
expect(childElement).toBeInTheDocument();
expect(headingElement).not.toBeInTheDocument();
});

it('renders border properly', () => {
const { container } = renderNoteBox({ bordered: true });
expect(container.firstChild).toHaveClass('ds-c-note-box ds-c-note-box--bordered');
});

it('renders quote option properly', () => {
renderNoteBox({
heading: <QuotationMarkIcon />,
children: (
<NoteBoxQuotation author="Test Author" citation="Test Citation">
This is foo text. Bar!
</NoteBoxQuotation>
),
});
const quoteElement = screen.getByText(/This is foo text. Bar!/);
expect(quoteElement).toBeInTheDocument();
// Per Design request citation wins out over author if both are provided
const authorElement = screen.queryByText(/Test Author/);
expect(authorElement).not.toBeInTheDocument();
const citationElement = screen.getByText(/Test Citation/);
expect(citationElement).toBeInTheDocument();
expect(screen.getByRole('complementary')).toMatchSnapshot();
});

it('renders a quote with only author', () => {
renderNoteBox({
heading: <QuotationMarkIcon />,
children: <NoteBoxQuotation author="Test Author">This is foo text. Bar!</NoteBoxQuotation>,
});
const authorElement = screen.getByText(/Test Author/);
expect(authorElement).toBeInTheDocument();
});

it('renders a quote with only citation', () => {
renderNoteBox({
heading: <QuotationMarkIcon />,
children: (
<NoteBoxQuotation citation="Test Citation">This is foo text. Bar!</NoteBoxQuotation>
),
});
const citationElement = screen.getByText(/Test Citation/);
expect(citationElement).toBeInTheDocument();
});

it('renders children properly', () => {
renderNoteBox();
const childrenElement = screen.getByText(/This is foo text. Bar!/);
expect(childrenElement).toBeInTheDocument();
});

it('applies className properly', () => {
const { container } = renderNoteBox({ className: 'test-class' });
expect(container.firstChild).toHaveClass('test-class');
});

it('applies heading level properly', () => {
renderNoteBox({ heading: 'Test Heading', headingLevel: '3' });
const headingElement = screen.getByRole('heading', { level: 3 });
expect(headingElement).toBeInTheDocument();
});
});
52 changes: 52 additions & 0 deletions packages/design-system/src/components/NoteBox/NoteBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type * as React from 'react';
import { FunctionComponent } from 'react';
import classNames from 'classnames';

export type NoteBoxHeadingLevel = '2' | '3' | '4' | '5' | '6';

interface NoteBoxProps {
/**
* Applies a border to the box content.
*/
bordered?: boolean;
/**
* Content to be displayed within the Box
*/
children: React.ReactNode;
/**
* Additional classes to be added to the component
*/
className?: string;
/**
* Text for the box content heading
*/
heading?: React.ReactNode;
/**
* Heading type to override default `<h2>`.
*/
headingLevel?: NoteBoxHeadingLevel;
}

const NoteBox: FunctionComponent<NoteBoxProps> = (props: NoteBoxProps) => {
const { bordered, children, className, heading, headingLevel = '2' } = props;

const classes = classNames('ds-c-note-box', bordered && 'ds-c-note-box--bordered', className);
let headingElement;
if (typeof heading === 'string') {
const Heading = `h${headingLevel}` as const;
headingElement = <Heading className="ds-c-note-box__heading">{heading}</Heading>;
} else {
headingElement = heading;
}

return (
<aside className={classes}>
<div className="ds-c-note-box__body">
{headingElement}
<div className={heading ? 'ds-c-note-box__text' : ''}>{children}</div>
</div>
</aside>
);
};

export default NoteBox;
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { Meta, StoryObj } from '@storybook/react';
import NoteBox from './NoteBox';
import { NoteBoxQuotation } from './NoteBoxQuotation';
import { QuotationMarkIcon } from '../Icons';

const boxContentMeta: Meta = {
title: 'Components/NoteBoxQuotation',
component: NoteBoxQuotation,
argTypes: {
author: { control: 'text' },
citation: { control: 'text' },
children: { control: 'text' },
},
parameters: {
docs: {
underlyingHtmlElements: ['blockquote', 'figure', 'figcaption', 'cite'],
},
},
};
export default boxContentMeta;
type Story = StoryObj<typeof NoteBoxQuotation>;

const NoteBoxQuotationStory: Story = {
render: ({ ...args }) => {
return (
<NoteBox heading={<QuotationMarkIcon />}>
<NoteBoxQuotation {...args}>{args.children}</NoteBoxQuotation>
</NoteBox>
);
},
};

export const Default = {
...NoteBoxQuotationStory,
args: {
citation: <a href="https://home.treasury.gov/">U.S. Department of the Treasury</a>,
children:
"The U.S. Department of the Treasury's mission is to maintain a strong economy and create economic and job opportunities by promoting the conditions that enable economic growth and stability at home and abroad, strengthen national security by combating threats and protecting the integrity of the financial system, and manage the U.S. Government’s finances and resources effectively.",
},
};

export const WithAuthor = {
...NoteBoxQuotationStory,
args: {
author: 'John Adams',
children: 'Let us dare to read, think, speak and write.',
},
};
72 changes: 72 additions & 0 deletions packages/design-system/src/components/NoteBox/NoteBoxQuotation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type * as React from 'react';
import { FunctionComponent } from 'react';
import { RequireAtLeastOne } from '../utilities/requireAtLeastOne';
import classNames from 'classnames';

interface MinimumNoteBoxQuotationProps {
/**
* Provide an author for the quote. It is required if a citation is not provided.
*/
author?: string;
/**
* Content to be displayed within the Box Quotation
*/
children: React.ReactNode;
/**
* Provide a citation for the quote. A citation is the title of a cited creative work. This can be a website, book chapter, but not an author. This component can accept more complex HTML in addition to strings, so passing in a link directly to the citation source is possible.
*/
citation?: React.ReactNode;
/**
* Additional classes to be added to the component
*/
className?: string;
}

export type NoteBoxQuotationProps = RequireAtLeastOne<
MinimumNoteBoxQuotationProps,
'citation' | 'author'
>;

export const NoteBoxQuotation: FunctionComponent<NoteBoxQuotationProps> = (
props: NoteBoxQuotationProps
) => {
const { author, children, citation, className } = props;

const classes = classNames('ds-c-note-box-quotation', className);

const CaptionContent: FunctionComponent = () => {
// We want to prioritize citations over authors, so if both are present only render the citation.
if (citation) {
return (
<cite className="ds-c-note-box-quotation--citation">
{`\u2014`} {citation}
</cite>
);
}
// If citation is not present, but author is, render the author.
if (author) {
return (
<>
{`\u2014`} {author}
</>
);
}
if (process.env.NODE_ENV !== 'production') {
if (!citation && !author) {
console.warn(
`[Warning]: You must include either an author prop or a citation prop on your NoteBoxQuotation component.`
);
}
}
return null;
};

return (
<figure className={classes}>
<blockquote className="ds-c-note-box-quotation--blockquote">{children}</blockquote>
<figcaption className="ds-c-note-box-quotation--caption">
<CaptionContent />
</figcaption>
</figure>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`NoteBox renders as complementary content 1`] = `
<aside
class="ds-c-note-box"
>
<div
class="ds-c-note-box__body"
>
<h2
class="ds-c-note-box__heading"
>
Test Heading
</h2>
<div
class="ds-c-note-box__text"
>
This is foo text. Bar!
</div>
</div>
</aside>
`;

exports[`NoteBox renders quote option properly 1`] = `
<aside
class="ds-c-note-box"
>
<div
class="ds-c-note-box__body"
>
<svg
aria-hidden="true"
class="ds-c-icon ds-c-icon--left-quote "
viewBox="2 0 40 32"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3 12.5C3 6.69875 7.67478 2 13.4464 2H14.1429C15.6837 2 16.9286 3.25125 16.9286 4.8C16.9286 6.34875 15.6837 7.6 14.1429 7.6H13.4464C10.7565 7.6 8.57143 9.79625 8.57143 12.5V13.2H14.1429C17.2158 13.2 19.7143 15.7113 19.7143 18.8V24.4C19.7143 27.4887 17.2158 30 14.1429 30H8.57143C5.49844 30 3 27.4887 3 24.4V12.5ZM25.2857 12.5C25.2857 6.69875 29.9605 2 35.7321 2H36.4286C37.9694 2 39.2143 3.25125 39.2143 4.8C39.2143 6.34875 37.9694 7.6 36.4286 7.6H35.7321C33.0422 7.6 30.8571 9.79625 30.8571 12.5V13.2H36.4286C39.5016 13.2 42 15.7113 42 18.8V24.4C42 27.4887 39.5016 30 36.4286 30H30.8571C27.7842 30 25.2857 27.4887 25.2857 24.4V12.5Z"
/>
</svg>
<div
class="ds-c-note-box__text"
>
<figure
class="ds-c-note-box-quotation"
>
<blockquote
class="ds-c-note-box-quotation--blockquote"
>
This is foo text. Bar!
</blockquote>
<figcaption
class="ds-c-note-box-quotation--caption"
>
<cite
class="ds-c-note-box-quotation--citation"
>

Test Citation
</cite>
</figcaption>
</figure>
</div>
</div>
</aside>
`;
1 change: 1 addition & 0 deletions packages/design-system/src/components/NoteBox/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './NoteBox';
1 change: 1 addition & 0 deletions packages/design-system/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ export * from './Accordion';
export * from './Alert';
export * from './Autocomplete';
export * from './Badge';
export * from './NoteBox';
export * from './Button';
export * from './ChoiceList';
export * from './DateField';
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> &
{
[K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>;
}[Keys];
Loading