-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #217 from US-CBP/Toast
Toast component
- Loading branch information
Showing
5 changed files
with
384 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
152 changes: 152 additions & 0 deletions
152
packages/web-components/src/components/cbp-toast/cbp-toast.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
/** | ||
* @Prop --cbp-toast-color-bg: var(--cbp-color-info-dark); | ||
* @Prop --cbp-toast-color-bg-dark: var(--cbp-color-info-lighter); | ||
* @Prop --cbp-toast-color: var(--cbp-color-text-lighter); | ||
* @Prop --cbp-toast-color-dark: var(--cbp-color-text-darker); | ||
* @Prop --cbp-toast-color-content: var(--cbp-color-text-lighter); | ||
* @Prop --cbp-toast-color-content-dark: var(--cbp-color-text-darkest); | ||
* @Prop --cbp-toast-color-bg-sidebar: var(--cbp-color-info-darker); | ||
* @Prop --cbp-toast-color-bg-sidebar-dark: var(--cbp-color-info-light); | ||
* @Prop --cbp-toast-color-icon-sidebar: var(--cbp-color-text-light); | ||
* @Prop --cbp-toast-color-icon-sidebar-dark: var(--cbp-color-text-darker); | ||
*/ | ||
:root{ | ||
|
||
--cbp-toast-color-bg: var(--cbp-color-info-dark); | ||
--cbp-toast-color-bg-dark: var(--cbp-color-info-lighter); | ||
--cbp-toast-color: var(--cbp-color-text-lighter); | ||
--cbp-toast-color-dark: var(--cbp-color-text-darker); | ||
--cbp-toast-color-content: var(--cbp-color-text-lighter); | ||
--cbp-toast-color-content-dark: var(--cbp-color-text-darkest); | ||
--cbp-toast-color-bg-sidebar: var(--cbp-color-info-darker); | ||
--cbp-toast-color-bg-sidebar-dark: var(--cbp-color-info-light); | ||
--cbp-toast-color-icon-sidebar: var(--cbp-color-text-light); | ||
--cbp-toast-color-icon-sidebar-dark: var(--cbp-color-text-darker); | ||
|
||
} | ||
|
||
[data-cbp-theme=light] cbp-toast[context*=dark]:not([context=light-always]), | ||
[data-cbp-theme=dark] cbp-toast:not([context=dark-inverts]):not([context=light-always]) { | ||
|
||
--cbp-toast-color-bg: var(--cbp-toast-color-bg-dark); | ||
--cbp-toast-color: var(--cbp-toast-color-dark); | ||
--cbp-toast-color-content: var(--cbp-toast-color-content-dark); | ||
--cbp-toast-color-bg-sidebar: var(--cbp-toast-color-bg-sidebar-dark); | ||
--cbp-toast-color-icon-sidebar: var(--cbp-toast-color-icon-sidebar-dark); | ||
|
||
} | ||
|
||
|
||
@keyframes show { | ||
0%{ | ||
opacity: 1; | ||
transform: translateX(100%); | ||
} | ||
|
||
} | ||
|
||
@keyframes dismiss { | ||
99%{ | ||
transform: translateX(200%); | ||
} | ||
100%{ | ||
visibility: hidden; | ||
} | ||
} | ||
|
||
cbp-toast { | ||
display: flex; | ||
margin-inline-start: auto; | ||
margin-block-end: var(--cbp-space-6x); | ||
width: min(100%, 30rem); | ||
background-color: var(--cbp-toast-color-bg); | ||
color: var(--cbp-toast-color); | ||
box-shadow: 0px 0px 18px 2px rgba(0, 0, 0, 0.3); | ||
border-radius: var(--cbp-border-radius-softer); | ||
|
||
animation: show 600ms 100ms ease-in-out forwards; | ||
|
||
&:not([open]){ | ||
animation: dismiss 1s ease-in-out forwards; | ||
} | ||
|
||
.cbp-toast-sidebar{ | ||
display: flex; | ||
align-items: center; | ||
padding: var(--cbp-space-4x); | ||
background-color: var(--cbp-toast-color-bg-sidebar); | ||
border-top-left-radius: var(--cbp-border-radius-softer);; | ||
border-bottom-left-radius: var(--cbp-border-radius-softer);; | ||
|
||
[slot="cbp-toast-icon"]{ | ||
color: var(--cbp-toast-color-icon-sidebar); | ||
} | ||
} | ||
|
||
.cbp-toast-title{ | ||
font-weight: var(--cbp-font-weight-medium); | ||
font-size: var(--cbp-font-size-heading-sm); | ||
line-height: var(--cbp-line-height-sm); | ||
} | ||
|
||
.cbp-toast-container{ | ||
padding: var(--cbp-space-3x); | ||
} | ||
|
||
.cbp-toast-content{ | ||
font-weight: var(--cbp-font-weight-regular); | ||
font-size: var(--cbp-font-size-body); | ||
line-height: var(--cbp-line-height-xs); | ||
color: var(--cbp-toast-color-content); | ||
} | ||
|
||
.cbp-toast-button-bar{ | ||
display: flex; | ||
justify-content: center; | ||
|
||
& > div{ | ||
display: flex; | ||
gap: var(--cbp-space-4x); | ||
} | ||
|
||
cbp-button[fill=ghost][color=secondary]{ //match button color to text color for toast | ||
--cbp-button-color: var(--cbp-toast-color); | ||
--cbp-button-color-dark: var(--cbp-toast-color-dark); | ||
} | ||
} | ||
|
||
&[color='info']{ | ||
--cbp-toast-color-bg: var(--cbp-color-info-dark); | ||
--cbp-toast-color-bg-dark: var(--cbp-color-info-lighter); | ||
--cbp-toast-color-bg-sidebar: var(--cbp-color-info-darker); | ||
--cbp-toast-color-bg-sidebar-dark: var(--cbp-color-info-light); | ||
} | ||
|
||
&[color='success']{ | ||
--cbp-toast-color-bg: var(--cbp-color-success-base); | ||
--cbp-toast-color-bg-dark: var(--cbp-color-success-lighter); | ||
--cbp-toast-color-bg-sidebar: var(--cbp-color-success-darker); | ||
--cbp-toast-color-bg-sidebar-dark: var(--cbp-color-success-light); | ||
} | ||
|
||
&[color='warning']{ | ||
--cbp-toast-color-bg: var(--cbp-color-warning-base); | ||
--cbp-toast-color-bg-dark: var(--cbp-color-warning-base); | ||
--cbp-toast-color-bg-sidebar: var(--cbp-color-warning-darker); | ||
--cbp-toast-color-bg-sidebar-dark: var(--cbp-color-warning-darker); | ||
--cbp-toast-color: var(--cbp-color-text-darker); | ||
--cbp-toast-color-dark: var(--cbp-color-text-darker); | ||
--cbp-toast-color-content: var(--cbp-color-text-darker); | ||
--cbp-toast-color-content-dark: var(--cbp-color-text-darker); | ||
--cbp-toast-color-icon-sidebar-dark: var(--cbp-color-text-light); | ||
} | ||
|
||
&[color='danger']{ | ||
|
||
--cbp-toast-color-bg: var(--cbp-color-danger-dark); | ||
--cbp-toast-color-bg-dark: var(--cbp-color-danger-lighter); | ||
--cbp-toast-color-bg-sidebar: var(--cbp-color-danger-darker); | ||
--cbp-toast-color-bg-sidebar-dark: var(--cbp-color-danger-light); | ||
} | ||
|
||
} |
41 changes: 41 additions & 0 deletions
41
packages/web-components/src/components/cbp-toast/cbp-toast.specs.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { Meta } from '@storybook/addon-docs'; | ||
|
||
<Meta title="Components/Toast/Specifications" /> | ||
|
||
# cbp-toast | ||
|
||
## Purpose | ||
|
||
* Toast is intended to temporarialy display messages to users such as error or warning messages about an form input, or confrimation of a file upload. It is the intent of the designer that for things | ||
like error messages to also be populated elsewhere in app so that they don't get 'lost'. | ||
|
||
## Functional Requirements | ||
|
||
* Toast expects for users to define the below props/slots | ||
*Props: | ||
*Icon: sets the name prop for the icon in the sidebar. default value of 'user' | ||
*timer: provides 3 options (3sec, 5sec, or 10sec) which determines how long the toast will be visible. if this is not set the toast will persist, which is intended for development, not final | ||
User experience | ||
*color: provides 4 options (info, success, warning, & danger) defaulting to info. determines the color palette used for the background & text color for the toast | ||
*Slots: | ||
*title: Slot for the title section of the Toast, intended for text or typography tags | ||
*content: Slot for the content of the toast, used for description of error or progress bar for example. | ||
*buttons: Slot intended for 1 or 2 buttons, UX design intended for these to be a dismiss (of the toast) and/or a button to link to log/more detailed section explaining the toast | ||
|
||
## Technical Specifications | ||
|
||
### User Interactions | ||
|
||
* Toast is intended to temporarialy draw attention to items/actions for the user as such they are meant to have an internal timer to display & have the displayed content in a log elsewhere for user to | ||
review after the toast is gone. | ||
|
||
### Responsiveness | ||
|
||
* Toast's width is set to the max-content of the toast, with a max-width on the toast of 50% of the viewportS | ||
|
||
### Accessibility | ||
|
||
* Toast being on a timer is an Accessibility concern, which is why it is advised to have a log of toasts perserved elsewhere in the app as mentioned above | ||
* Slotted content is Accessibile via keyboard, primarily use being the slotted buttons | ||
|
||
### Additional Notes and Considerations |
121 changes: 121 additions & 0 deletions
121
packages/web-components/src/components/cbp-toast/cbp-toast.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
export default { | ||
title: 'Components/Toast', | ||
tags: ['autodocs'], | ||
argTypes: { | ||
|
||
duration: { | ||
control: 'select', | ||
options: [3, 5, 10] | ||
}, | ||
color: { | ||
control: 'select', | ||
options: ['info', 'danger', 'success', 'warning'] | ||
}, | ||
context : { | ||
control: 'select', | ||
options: [ "light-inverts", "light-always", "dark-inverts", "dark-always"] | ||
}, | ||
sx: { | ||
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.', | ||
control: 'object', | ||
}, | ||
}, | ||
}; | ||
|
||
const Template = ({ open, icon, title, content, buttons, duration, color, context, sx }) => { | ||
return ` | ||
<cbp-toast | ||
${open ? `open` : ''} | ||
${color ? `color=${color}` : ''} | ||
${duration ? `duration=${duration}` : ''} | ||
${icon ? `icon=${icon}` : ''} | ||
${context && context != 'light-inverts' ? `context=${context}` : ''} | ||
${sx ? `sx=${JSON.stringify(sx)}` : ''} | ||
> | ||
<div slot="cbp-toast-icon"><cbp-icon size='2rem' name=${icon}></cbp-icon></div> | ||
<div slot="cbp-toast-title">${title}</div> | ||
${content} | ||
<div slot="cbp-toast-buttons">${buttons}</div> | ||
</cbp-toast> | ||
`; | ||
}; | ||
|
||
export const Toast = Template.bind({}); | ||
|
||
Toast.args = { | ||
open: true, | ||
icon: `user`, | ||
title: 'Test Toast Title', | ||
content: 'Notification Description - A rule you are following just fired.', | ||
buttons: `<cbp-button type="button" fill="ghost" color="secondary"> Dismiss </cbp-button> <cbp-button type="button" fill="ghost" color="secondary"> Default 2</cbp-button>` | ||
} | ||
|
||
const MultiTemplate = ({ open, icon, title, content, buttons, duration, color, context, sx }) => { | ||
return ` | ||
<cbp-toast | ||
${open ? `open=${open}` : ''} | ||
color=${color} | ||
duration=${duration} | ||
${icon ? `icon=${icon}` : ''} | ||
${context && context != 'light-inverts' ? `context=${context}` : ''} | ||
${sx ? `sx=${JSON.stringify(sx)}` : ''} | ||
> | ||
<div slot="cbp-toast-icon"><cbp-icon size='2rem' name=${icon}></cbp-icon></div> | ||
<div slot="cbp-toast-title">${title}</div> | ||
${content} | ||
<div slot="cbp-toast-buttons">${buttons}</div> | ||
</cbp-toast> | ||
<cbp-toast | ||
${open ? `open=${open}` : ''} | ||
color=${color} | ||
duration=${duration} | ||
${icon ? `icon=${icon}` : ''} | ||
${context && context != 'light-inverts' ? `context=${context}` : ''} | ||
${sx ? `sx=${JSON.stringify(sx)}` : ''} | ||
> | ||
<div slot="cbp-toast-icon"><cbp-icon size='2rem' name=${icon}></cbp-icon></div> | ||
<div slot="cbp-toast-title">${title}</div> | ||
${content} | ||
<div slot="cbp-toast-buttons">${buttons}</div> | ||
</cbp-toast> | ||
<cbp-toast | ||
${open ? `open=${open}` : ''} | ||
color=${color} | ||
duration=${duration} | ||
${icon ? `icon=${icon}` : ''} | ||
${context && context != 'light-inverts' ? `context=${context}` : ''} | ||
${sx ? `sx=${JSON.stringify(sx)}` : ''} | ||
> | ||
<div slot="cbp-toast-icon"><cbp-icon size='2rem' name=${icon}></cbp-icon></div> | ||
<div slot="cbp-toast-title">${title}</div> | ||
${content} | ||
<div slot="cbp-toast-buttons">${buttons}</div> | ||
</cbp-toast> | ||
<cbp-toast | ||
${open ? `open=${open}` : ''} | ||
color=${color} | ||
duration=${duration} | ||
${icon ? `icon=${icon}` : ''} | ||
${context && context != 'light-inverts' ? `context=${context}` : ''} | ||
${sx ? `sx=${JSON.stringify(sx)}` : ''} | ||
> | ||
<div slot="cbp-toast-icon"><cbp-icon size='2rem' name=${icon}></cbp-icon></div> | ||
<div slot="cbp-toast-title">${title}</div> | ||
${content} | ||
<div slot="cbp-toast-buttons">${buttons}</div> | ||
</cbp-toast> | ||
`; | ||
}; | ||
|
||
export const MultipleToast = MultiTemplate.bind({}); | ||
|
||
MultipleToast.args = { | ||
open: true, | ||
icon: `user`, | ||
title: 'Test Toast Title', | ||
content: 'Notification Description - A rule you are following just fired.', | ||
buttons: '<cbp-button type="button" fill="ghost" color="secondary"> Dismiss </cbp-button> <cbp-button type="button" fill="ghost" color="secondary"> Default 2</cbp-button>' | ||
} |
69 changes: 69 additions & 0 deletions
69
packages/web-components/src/components/cbp-toast/cbp-toast.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { Component, Prop, Element, Host, Watch, h } from '@stencil/core'; | ||
import { setCSSProps } from '../../utils/utils'; | ||
|
||
@Component({ | ||
tag: 'cbp-toast', | ||
styleUrl: 'cbp-toast.scss' | ||
}) | ||
export class CbpToast { | ||
|
||
@Element() host: HTMLElement; | ||
|
||
/** specifies the color for the toast */ | ||
@Prop({ reflect: true }) color: 'info' | 'danger' | 'success' | 'warning' = 'info'; | ||
|
||
/** specifies time in seconds for the toast to be displayed */ | ||
@Prop() duration: 3 | 5 | 10; | ||
|
||
/** When set, specifies that the toast is open */ | ||
@Prop({ reflect: true }) open: boolean; | ||
|
||
/** Specifies the context of the component as it applies to the visual design and whether it inverts when light/dark mode is toggled. Default behavior is "light-inverts" and does not have to be specified. */ | ||
@Prop({ reflect: true }) context: "light-inverts" | "light-always" | "dark-inverts" | "dark-always"; | ||
|
||
/** Supports adding inline styles as an object */ | ||
@Prop() sx: any = {}; | ||
|
||
@Watch('open') | ||
watchOpenHandler(newValue: boolean){ | ||
console.log('watchOpenHandler check'); | ||
if(!newValue) { | ||
console.log('dismiss toast!'); | ||
} | ||
} | ||
|
||
componentWillLoad() { | ||
if (typeof this.sx == 'string') { | ||
this.sx = JSON.parse(this.sx) || {}; | ||
} | ||
setCSSProps(this.host, { | ||
...this.sx, | ||
}); | ||
} | ||
|
||
render() { | ||
|
||
if(this.open && this.duration){ | ||
setTimeout(() => { this.open = false }, this.duration * 1000) | ||
} | ||
|
||
return ( | ||
<Host> | ||
<div class='cbp-toast-sidebar'> | ||
<slot name='cbp-toast-icon'></slot> | ||
</div> | ||
<div class='cbp-toast-container'> | ||
<div class='cbp-toast-title'> | ||
<slot name='cbp-toast-title'></slot> | ||
</div> | ||
<div class='cbp-toast-content'> | ||
<slot></slot> | ||
</div> | ||
<div class='cbp-toast-button-bar'> | ||
<slot name='cbp-toast-buttons'></slot> | ||
</div> | ||
</div> | ||
</Host> | ||
); | ||
} | ||
} |