Skip to content

Commit 8fb8a14

Browse files
authored
Merge pull request #149 from US-CBP/feature/cbp-modal
Feature/cbp modal
2 parents c741387 + af30d80 commit 8fb8a14

File tree

11 files changed

+515
-79
lines changed

11 files changed

+515
-79
lines changed

dependencies.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Dependencies
2+
3+
The follow major dependencies are used in this repo and devops pipeline:
4+
5+
* StencilJS
6+
* Storybook
7+
* Vite
8+
* Bitovi GitHub Action to publish Storybook to GitHub Pages: https://github.com/bitovi/github-actions-storybook-to-github-pages

packages/react-components/components/stencil-generated/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const CbpButton = /*@__PURE__*/createReactComponent<JSX.CbpButton, HTMLCb
1616
export const CbpCard = /*@__PURE__*/createReactComponent<JSX.CbpCard, HTMLCbpCardElement>('cbp-card');
1717
export const CbpChip = /*@__PURE__*/createReactComponent<JSX.CbpChip, HTMLCbpChipElement>('cbp-chip');
1818
export const CbpContainer = /*@__PURE__*/createReactComponent<JSX.CbpContainer, HTMLCbpContainerElement>('cbp-container');
19+
export const CbpDialog = /*@__PURE__*/createReactComponent<JSX.CbpDialog, HTMLCbpDialogElement>('cbp-dialog');
1920
export const CbpDrawer = /*@__PURE__*/createReactComponent<JSX.CbpDrawer, HTMLCbpDrawerElement>('cbp-drawer');
2021
export const CbpExpand = /*@__PURE__*/createReactComponent<JSX.CbpExpand, HTMLCbpExpandElement>('cbp-expand');
2122
export const CbpFlex = /*@__PURE__*/createReactComponent<JSX.CbpFlex, HTMLCbpFlexElement>('cbp-flex');
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
:root {
2+
--cbp-dialog-color: var(--cbp-color-text-darkest);
3+
--cbp-dialog-color-dark: var(--cbp-color-text-lightest);
4+
5+
--cbp-dialog-color-bg: var(--cbp-color-white);
6+
--cbp-dialog-color-bg-dark: var(--cbp-color-gray-cool-60);
7+
8+
--cbp-dialog-padding: var(--cbp-space-5x);
9+
--cbp-dialog-border-radius: var(--cbp-border-radius-softer);
10+
--cbp-dialog-shadow: var(--cbp-shadow-level-5-center);
11+
--cbp-dialog-width: 20rem;
12+
--cbp-dialog-height: auto;
13+
}
14+
15+
/*
16+
* Dark Mode - display dark design based on mode or context
17+
*/
18+
[data-cbp-theme=light] cbp-dialog[context*=dark],
19+
[data-cbp-theme=dark] cbp-dialog:not([context=dark-inverts]):not([context=light-always]) {
20+
--cbp-dialog-color: var(--cbp-dialog-color-dark);
21+
--cbp-dialog-color-bg: var(--cbp-dialog-color-bg-dark);
22+
}
23+
24+
cbp-dialog {
25+
display: none;
26+
position: fixed;
27+
left: 0;
28+
top: 0;
29+
width: 100%;
30+
height: 100%;
31+
z-index: 999;
32+
overflow: hidden;
33+
background-color: rgba(0, 0, 0, 0.3); // backdrop
34+
35+
&[open] {
36+
display: flex;
37+
justify-content: center;
38+
align-items: center;
39+
overflow: hidden;
40+
}
41+
42+
&[color=danger] {
43+
--cbp-dialog-color-bg: var(--cbp-color-danger-lighter);
44+
--cbp-dialog-color-bg-dark: var(--cbp-color-danger-darker);
45+
}
46+
47+
div[role=dialog] {
48+
opacity: 1;
49+
display: flex;
50+
flex-direction: column;
51+
position: fixed;
52+
overflow-y: auto;
53+
z-index: 9;
54+
width: var(--cbp-dialog-width);
55+
height: var(--cbp-dialog-height);
56+
max-width: 100%;
57+
max-height: 90vh;
58+
box-shadow: var(--cbp-dialog-shadow);
59+
border-radius: var(--cbp-dialog-border-radius);
60+
61+
.cbp-dialog-body {
62+
color: var(--cbp-dialog-color);
63+
background-color: var(--cbp-dialog-color-bg);
64+
padding: var(--cbp-dialog-padding);
65+
border-radius: var(--cbp-dialog-border-radius) var(--cbp-dialog-border-radius) 0 0;
66+
}
67+
68+
[slot=cbp-dialog-actions] {
69+
display: flex;
70+
71+
cbp-button {
72+
--cbp-button-border-radius: 0;
73+
width: 100%;
74+
75+
&:first-child {
76+
--cbp-button-border-radius: 0 0 0 var(--cbp-border-radius-softer);
77+
}
78+
79+
&:last-child {
80+
--cbp-button-border-radius: 0 0 var(--cbp-border-radius-softer) 0;
81+
}
82+
83+
&:first-child:last-child {
84+
--cbp-button-border-radius: 0 0 var(--cbp-border-radius-softer) var(--cbp-border-radius-softer);
85+
}
86+
87+
button,
88+
a {
89+
padding-block: var(--cbp-space-3x);
90+
width: 100%;
91+
}
92+
}
93+
}
94+
}
95+
}
96+
97+
98+
@media (max-width: 37.5em) {
99+
cbp-dialog {
100+
&[open] {
101+
align-items: end;
102+
}
103+
104+
div[role=dialog] {
105+
margin-bottom: var(--cbp-space-5x);
106+
}
107+
}
108+
}
109+
110+
@media print {
111+
cbp-dialog[open] div[role=dialog] {
112+
position: absolute;
113+
left: 0;
114+
top: 0;
115+
width: 100%;
116+
height: 100%;
117+
z-index: 999;
118+
background-color: var(--cbp-dialog-background-color)
119+
}
120+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Meta } from '@storybook/addon-docs';
2+
3+
<Meta title="Components/Dialog/Specifications" />
4+
5+
# cbp-dialog
6+
7+
## Purpose
8+
9+
The Dialog component represents a dialog overlaid on top of the web page, which can be used similar to an alert/confirm dialog or contain a small form.
10+
11+
## Functional Requirements
12+
13+
* The Dialog component will cover the page in the background with a translucent backdrop to put visual focus on the dialog, which is centered in the display on larger screens.
14+
* At small screen sizes, the Dialog shall be positioned near the bottom of the screen.
15+
* Furthermore, the underlying page will not be accessible while the dialog is open.
16+
* The Dialog component defines a danger color variants as well, based on design tokens.
17+
* The Dialog may optionally contain a title and/or actions in the form of buttons (or links).
18+
19+
## Technical Specifications
20+
21+
### User Interactions
22+
23+
* In most cases, a control on the page should be visible to reveal the Dialog. This is typically a Button component and is not part of the dialog component.
24+
* When an action button in the dialog is activated by user interaction, it emits a custom event (native to the button, not dialog) that can be listened for.
25+
26+
### Responsiveness
27+
28+
* The default width of the dialog is `20rem` (320px), which should fit on a mobile device.
29+
* Under 600px (37.5rem), the dialog will be positioned near the bottom of the viewport for easier thumb access.
30+
* The dialog has `max-width: 100%` and `max-height: 90vh` so that it will never stretch larger than the viewport.
31+
32+
### Accessibility
33+
34+
* The dialog has `role="dialog"` and `aria-modal="true"` applied to it automatically.
35+
* The dialog should have an accessible label specified via the `accessibilityText` property, which applies an `aria-label` on the `role="dialog"` element.
36+
* When the dialog is opened, or if it is open by default on page load, focus will be sent to the first focusable element within the dialog.
37+
* TODO: When the dialog is open, focus shall be trapped within the dialog and the background page is not accessible.
38+
* TODO: When the dialog is closed, focus shall be sent back to the control used to open it.
39+
40+
### Additional Notes and Considerations
41+
42+
TBD.
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
export default {
2+
title: 'Components/Dialog',
3+
tags: ['autodocs'],
4+
argTypes: {
5+
uid: {
6+
description: 'A unique `id` applied to the dialog and referenced by the control.',
7+
control: 'text',
8+
},
9+
open: {
10+
description: 'Specifies whether the drawer is open or closed.',
11+
control: 'boolean',
12+
},
13+
title: {
14+
name: 'Title (slotted)',
15+
description: 'Set the title in the banner area of the card',
16+
control: 'text',
17+
},
18+
content: {
19+
name: 'Body Text (slotted)',
20+
description: 'Set the body text of the card',
21+
control: 'text',
22+
},
23+
accessibilityText: {
24+
description: 'Accessibility text is required to label the dialog and is applied as an `aria-label`.',
25+
control: 'text',
26+
},
27+
color: {
28+
control: 'select',
29+
options: ['default', 'danger'],
30+
},
31+
actionsLayout: {
32+
name: 'Actions Layout',
33+
description: 'Choose actions layout of the dialog component',
34+
control: 'radio',
35+
options: ['single', 'double', 'triple'],
36+
},
37+
actionsConfig: {
38+
name: 'Dialog Actions',
39+
description: 'Configure card button labels and colors. Available button colors: `primary`, `secondary`, `tertiary` and `danger`',
40+
control: 'object',
41+
},
42+
sx: {
43+
description: 'Supports adding inline styles as an object of key-value pairs comprised of CSS properties and values. Values should reference design tokens when possible.',
44+
control: 'object',
45+
},
46+
},
47+
};
48+
49+
50+
const renderActions = (layout, { btn1, btn2, btn3 }) => {
51+
if (layout === 'double') {
52+
return `
53+
<div slot="cbp-dialog-actions">
54+
<cbp-button tag="${btn1.tag}" ${btn1.tag == 'a' ? `href="#"` : ''} fill="solid" color="${btn1.color}" aria-describedby="card-heading-1">${btn1.label}</cbp-button>
55+
<cbp-button tag="${btn2.tag}" ${btn2.tag == 'a' ? `href="#"` : ''} fill="solid" color="${btn2.color}" aria-describedby="card-heading-1">${btn2.label}</cbp-button>
56+
</div>
57+
`;
58+
} else if (layout === 'triple') {
59+
return `
60+
<div slot="cbp-dialog-actions">
61+
<cbp-button tag="${btn1.tag}" ${btn1.tag == 'a' ? `href="#"` : ''} fill="solid" color="${btn1.color}" aria-describedby="card-heading-1">${btn1.label}</cbp-button>
62+
<cbp-button tag="${btn2.tag}" ${btn2.tag == 'a' ? `href="#"` : ''} fill="solid" color="${btn2.color}" aria-describedby="card-heading-1">${btn2.label}</cbp-button>
63+
<cbp-button tag="${btn3.tag}" ${btn3.tag == 'a' ? `href="#"` : ''} fill="solid" color="${btn3.color}" aria-describedby="card-heading-1">${btn3.label}</cbp-button>
64+
</div>
65+
`;
66+
} else {
67+
return `
68+
<div slot="cbp-dialog-actions">
69+
<cbp-button tag="${btn1.tag}" ${btn1.tag == 'a' ? `href="#"` : ''} fill="solid" color="${btn1.color}" aria-describedby="card-heading-1">${btn1.label}</cbp-button>
70+
</div>
71+
`;
72+
}
73+
};
74+
75+
const Template = ({ title, content, color, open, uid, accessibilityText, actionsLayout, actionsConfig, sx }) => {
76+
return `
77+
<cbp-button
78+
type="button"
79+
color="secondary"
80+
accessibility-text="Open Drawer"
81+
target-prop="open"
82+
controls=${uid}
83+
>
84+
<cbp-icon name="bars"></cbp-icon>
85+
</cbp-button>
86+
87+
<cbp-dialog
88+
${open ? `open=${open}` : ''}
89+
${accessibilityText ? `accessibility-text=${accessibilityText}` : ''}
90+
${color && color != 'default' ? `color=${color}` : ''}
91+
${sx ? `sx=${JSON.stringify(sx)}` : ''}
92+
${uid ? `uid=${uid}` : ''}
93+
>
94+
<cbp-typography
95+
slot="cbp-dialog-header"
96+
tag="h2"
97+
variant="heading-dialog"
98+
divider="underline"
99+
>
100+
${title}
101+
</cbp-typography>
102+
103+
<cbp-typography
104+
slot="cbp-dialog-body"
105+
tag="div"
106+
variant="heading-xs"
107+
>
108+
${content}
109+
</cbp-typography>
110+
111+
${renderActions(actionsLayout, actionsConfig)}
112+
</cbp-dialog>
113+
`;
114+
};
115+
116+
export const Dialog = Template.bind({});
117+
Dialog.args = {
118+
title: 'Dialog Title',
119+
content: 'Here is an example of some body text for this dialog.',
120+
uid: 'dialog',
121+
actionsLayout: 'single',
122+
actionsConfig: {
123+
btn1: {
124+
label: 'Action 1',
125+
tag: 'button',
126+
color: 'primary',
127+
},
128+
btn2: {
129+
label: 'Action 2',
130+
tag: 'button',
131+
color: 'secondary',
132+
},
133+
btn3: {
134+
label: 'Action 3',
135+
tag: 'button',
136+
color: 'tertiary',
137+
},
138+
},
139+
};

0 commit comments

Comments
 (0)