Skip to content
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

Initial build of service-list web component #1475

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 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
57 changes: 57 additions & 0 deletions packages/storybook/stories/va-service-list.stories.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import { getWebComponentDocs, propStructure, StoryDocs } from './wc-helpers';

const serviceListDocs = getWebComponentDocs('va-service-list');

export default {
title: 'Components/Service list',
bellepx0 marked this conversation as resolved.
Show resolved Hide resolved
id: 'components/va-service-list',
parameters: {
componentSubtitle: 'va-service-list web component',
docs: {
page: () => <StoryDocs storyDefault={Default} data={serviceListDocs} />,
},
},
};

const Template = (args) => {
const ref = React.useRef(null);
const { serviceDetails, icon, serviceName, serviceStatus, action, optionalLink } = args

React.useEffect(() => {
if (ref.current) {
Object.entries(args).forEach(([key, value]) => {
ref.current[key] = value;
});
}
}, [args]);

return (
<va-service-list
ref={ref}
key={JSON.stringify(args)}
serviceDetails={JSON.stringify(serviceDetails)}
icon={icon}
serviceName={serviceName}
serviceStatus={serviceStatus}
action={JSON.stringify(action)}
optionalLink={optionalLink}
/>
);
};


export const Default = Template.bind({});
Default.args = {
serviceDetails: {
'Approved on': 'May 5, 2011',
'Program': 'Post-9/11 GI Bill',
'Eligibility': '70%',
},
icon: 'school',
serviceName: 'Education',
serviceStatus: 'Eligible',
action: { href: 'https://www.va.gov/education', text: 'Verify income' },
optionalLink: 'https://www.va.gov',
};
Default.argTypes = propStructure(serviceListDocs);
45 changes: 45 additions & 0 deletions packages/web-components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1540,6 +1540,19 @@ export namespace Components {
*/
"width"?: string;
}
/**
* @componentName Service List
* @maturityCategory use with caution
adamwhitlock1 marked this conversation as resolved.
Show resolved Hide resolved
* @maturityLevel candidate
*/
interface VaServiceList {
"action": any;
"icon": string;
"optionalLink": string;
"serviceDetails": any;
"serviceName": string;
"serviceStatus": string;
}
/**
* @componentName Statement of truth
* @maturityCategory caution
Expand Down Expand Up @@ -3020,6 +3033,17 @@ declare global {
prototype: HTMLVaSelectElement;
new (): HTMLVaSelectElement;
};
/**
* @componentName Service List
* @maturityCategory use with caution
* @maturityLevel candidate
*/
interface HTMLVaServiceListElement extends Components.VaServiceList, HTMLStencilElement {
}
var HTMLVaServiceListElement: {
prototype: HTMLVaServiceListElement;
new (): HTMLVaServiceListElement;
};
interface HTMLVaStatementOfTruthElementEventMap {
"vaInputChange": any;
"vaInputBlur": any;
Expand Down Expand Up @@ -3213,6 +3237,7 @@ declare global {
"va-search-input": HTMLVaSearchInputElement;
"va-segmented-progress-bar": HTMLVaSegmentedProgressBarElement;
"va-select": HTMLVaSelectElement;
"va-service-list": HTMLVaServiceListElement;
"va-statement-of-truth": HTMLVaStatementOfTruthElement;
"va-summary-box": HTMLVaSummaryBoxElement;
"va-table": HTMLVaTableElement;
Expand Down Expand Up @@ -4991,6 +5016,19 @@ declare namespace LocalJSX {
*/
"width"?: string;
}
/**
* @componentName Service List
* @maturityCategory use with caution
* @maturityLevel candidate
*/
interface VaServiceList {
"action"?: any;
"icon"?: string;
"optionalLink"?: string;
"serviceDetails"?: any;
"serviceName"?: string;
"serviceStatus"?: string;
}
/**
* @componentName Statement of truth
* @maturityCategory caution
Expand Down Expand Up @@ -5434,6 +5472,7 @@ declare namespace LocalJSX {
"va-search-input": VaSearchInput;
"va-segmented-progress-bar": VaSegmentedProgressBar;
"va-select": VaSelect;
"va-service-list": VaServiceList;
"va-statement-of-truth": VaStatementOfTruth;
"va-summary-box": VaSummaryBox;
"va-table": VaTable;
Expand Down Expand Up @@ -5758,6 +5797,12 @@ declare module "@stencil/core" {
* @translations Spanish
*/
"va-select": LocalJSX.VaSelect & JSXBase.HTMLAttributes<HTMLVaSelectElement>;
/**
* @componentName Service List
* @maturityCategory use with caution
* @maturityLevel candidate
*/
"va-service-list": LocalJSX.VaServiceList & JSXBase.HTMLAttributes<HTMLVaServiceListElement>;
/**
* @componentName Statement of truth
* @maturityCategory caution
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { newE2EPage } from '@stencil/core/testing';

describe('va-service-list', () => {
/**
* Helper function to set up the component with dynamic props
*/
async function setupComponent({
serviceDetails = `{
"Approved on": "May 11, 2011",
"Program": "Post-9/11 GI Bill",
"Eligibility": "70%"
}`,
action = `{
"href": "https://www.va.gov/education",
"text": "Verify income"
}`,
icon = "school",
serviceName = "Education",
serviceStatus = "Eligible",
optionalLink = "https://va.gov",
} = {}) {
const page = await newE2EPage();
await page.setContent(`<va-service-list></va-service-list>`);

const elementHandle = await page.$('va-service-list');

await page.evaluate(
(el, props) => {
el.serviceDetails = props.serviceDetails;
el.action = props.action;
el.icon = props.icon;
el.serviceName = props.serviceName;
el.serviceStatus = props.serviceStatus;
el.optionalLink = props.optionalLink;
},
elementHandle,
{ serviceDetails, action, icon, serviceName, serviceStatus, optionalLink }
);

await page.waitForChanges();
await page.evaluate(() => new Promise((resolve) => setTimeout(resolve, 5000)));

return { page, elementHandle };
}

it('renders correctly with action bar when serviceStatus is Eligible', async () => {
const { page, elementHandle } = await setupComponent({ serviceStatus: "Eligible" });

const shadowRootExists = await page.evaluate((el) => !!el.shadowRoot, elementHandle);
expect(shadowRootExists).toBe(true);

const shadowInnerHTML = await page.evaluate((el) => el.shadowRoot.innerHTML, elementHandle);
expect(shadowInnerHTML.replace(/\s+/g, ' ')).toEqualHtml(`
<div class="service-list">
<a href="/">
<div class="header">
<slot name="icon">
<va-icon class="icon hydrated"></va-icon>
</slot>
<slot name="service-name">
<h3 class="service-name">Education</h3>
</slot>
<va-icon class="chevron-icon hydrated"></va-icon>
</div>
</a>
<div class="action-bar">
<va-link-action class="hydrated"></va-link-action>
</div>
<div class="status">
<span class="usa-label">Eligible</span>
</div>
<ul class="service-list-items">
<li>
<div>
<strong>APPROVED ON:</strong> May 11, 2011
</div>
</li>
<li>
<div>
<strong>PROGRAM:</strong> Post-9/11 GI Bill
</div>
</li>
<li>
<div>
<strong>ELIGIBILITY:</strong> 70%
</div>
</li>
</ul>
<slot name="optional-link">
<va-link class="hydrated"></va-link>
</slot>
</div>
`);
});

it('does NOT render action bar when serviceStatus is NOT Eligible"', async () => {
const { page, elementHandle } = await setupComponent({ serviceStatus: "Ineligible" });

const shadowInnerHTML = await page.evaluate((el) => el.shadowRoot.innerHTML, elementHandle);

expect(shadowInnerHTML).not.toContain('<div class="action-bar">');
});

});














Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
@use '../../../css-library/src/stylesheets/formation-overrides/elements/labels' as *;
@import '../../mixins/uswds-error-border.scss';
@import '../../mixins/focusable.css';
@import '../../mixins/headers.css';

:host {
display: block;
}
.service-list{
padding: 1rem 2rem 2rem 1rem;
border: 1px solid #757575;
adamwhitlock1 marked this conversation as resolved.
Show resolved Hide resolved

a {
text-decoration: none;
color: unset;
}
.header {
display: flex;
flex-direction: row;
align-items: center;

.icon {
// color: unset;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
height: 40px !important;
width: 40px !important;
min-width: 40px !important;
max-width: 40px !important;
color: #ffffff !important;
background-color: #162e51 !important;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: How do we make the background color dynamic? Looks like the designs have several different background colors shown for different icons.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the icon prop to the class so we can define the background colors for each icon directly in the SCSS file. There might be an alternative approach like dynamically setting the background color inline using a prop such as vads-color-hub-life-insurance, with life-insurance being the passed prop. But I’m keeping it simple for now and we can revisit and adjust the implementation later if needed when integrating this into vets-website

}

.service-name {
margin-left: 1rem;
color: #005ea2;
}
.chevron-icon {
margin-left: auto;
font-size: 24px;
}
}
.status {
margin-top: 1rem;
}
.action-bar {
padding: 0.5rem 1rem;
margin-top: 1rem;
background-color: $vads-color-error-lighter;
}
ul {
list-style-type: none;
padding-left: 0;
}
}
Loading
Loading