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

Feature: #41 Alert. #60

Merged
merged 22 commits into from Oct 20, 2021
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
cae4905
41: Basic unstyled alert.
damontgomery Jul 16, 2021
b65fad5
41: Default header based on type.
damontgomery Jul 16, 2021
24dab7b
41: Alert basic styling.
damontgomery Jul 16, 2021
b769038
41: Add placeholder for icon.
damontgomery Jul 16, 2021
a238e1d
41: Clean up stories.
damontgomery Jul 16, 2021
e9ae7f7
41: Remove unused decorator.
damontgomery Jul 16, 2021
99be962
41: Add story with link.
damontgomery Jul 16, 2021
693c6ca
41: Add accessibility roles based on interactivity.
damontgomery Jul 16, 2021
646a156
41: Add information about the header slot.
damontgomery Jul 16, 2021
2885dd9
41: Refactor to move properties near where they are relevant.
damontgomery Jul 16, 2021
57ce07a
41: Simplify the header capitalization by using CSS.
damontgomery Jul 16, 2021
5de4065
41: Fix storybook issue with booleans.
damontgomery Jul 16, 2021
95a531f
41: Fix booleans in storybook using simple Lit boolean syntax.
damontgomery Jul 19, 2021
f756efa
41: Move simple functions into template.
damontgomery Jul 19, 2021
78c7cb2
Merge branch 'next' into feature/41-alert
damontgomery Jul 19, 2021
0400552
41: Move properties to the top to match style expectations.
damontgomery Jul 20, 2021
c6b73de
41: Use null when returning empty templates.
damontgomery Jul 20, 2021
30cf5fa
Merge branch 'next' into feature/41-alert
damontgomery Jul 28, 2021
9b17e1f
docs: 41: Update how we show the booleans.
damontgomery Jul 28, 2021
704e602
test: 41: Simple slotting test for outline alert.
damontgomery Jul 28, 2021
0e32697
chore: 41: Add interface for alert element.
damontgomery Jul 28, 2021
532d22c
chore: 41: Reduce copy & paste in type definition for enumerable prop…
damontgomery Jul 28, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/components/base/outline-alert/outline-alert.css
@@ -0,0 +1,28 @@
/* The default is information. */
#body {
@apply block border-solid border-0 border-l-8 p-4 bg-ui-info text-ui-infoText;

border-color: var(--ui-infoText);
}

#header {
@apply font-bold text-lg capitalize;
}

:host([statusType='warning']) #body {
@apply bg-ui-warning text-ui-warningText;

border-color: var(--ui-warningText);
}

:host([statusType='error']) #body {
@apply bg-ui-error text-ui-errorText;

border-color: var(--ui-errorText);
}

:host([statusType='success']) #body {
@apply bg-ui-success text-ui-successText;

border-color: var(--ui-successText);
}
158 changes: 158 additions & 0 deletions src/components/base/outline-alert/outline-alert.stories.ts
@@ -0,0 +1,158 @@
import { html, TemplateResult } from 'lit';
import './outline-alert';
import { argTypeSlotContent } from '../../base/outline-element/utils/utils';
import { alertSizes, alertStatusTypes } from './outline-alert';
import { ifDefined } from 'lit/directives/if-defined';

export default {
title: 'Molecules/Alert',
component: 'outline-alert',
argTypes: {
headerSlot: {
name: 'slot="outline-alert--header"',
description: 'The header slot. For example a header title.',
table: {
category: 'Slots',
},
// We aren't setting a control type here so we can edit the value using the infered object.
},
// We aren't setting a control type here so we can edit the value using the infered object.
defaultSlot: {
...argTypeSlotContent.defaultSlot,
table: {
category: 'Slots',
},
},
statusType: {
description:
'The status type of the alert. Such as `information` or `warning`.',
options: alertStatusTypes,
control: { type: 'select' },
},
size: {
description: 'The size of the alert.',
options: alertSizes,
LeeMellon marked this conversation as resolved.
Show resolved Hide resolved
control: { type: 'select' },
},
shouldShowIcon: {
description: '(not yet implemented) Should we show the icon',
control: { type: 'boolean' },
},
isInteractive: {
description: 'Is there an interaction in the alert, such as a button.',
control: { type: 'boolean' },
},
},
parameters: {
docs: {
description: {
component: `
mabry1985 marked this conversation as resolved.
Show resolved Hide resolved
This component renders an alert.

## Variation

You can set the type of alert with \`statusType\`.

You can use a smaller alert with \`size\` of \`small\`.

You can remove the icon with \`shouldShowIcon\` set to \`false\`.

You can customize the header by adding a \`outline-alert--header\` slot.

## Accessibility

If the alert has an interaction, you should indicate this with \`isInteractive\` set to \`true\`.
`,
},
source: {
// This code sample will be used for every example unless overridden.
code: `
<outline-alert
statusType="{{ statusType }}"
size="{{ size }}"
shouldShowIcon="{{ shouldShowIcon }}"
isInteractive="{{ isInteractive }}"
>
<span slot="outline-alert--header">{{ headerSlot }}</span>
{{ defaultSlot }}
</outline-alert>
`,
},
},
},
};

const Template = ({
headerSlot,
defaultSlot,
statusType,
size,
shouldShowIcon,
isInteractive,
}): TemplateResult => {
return html`
<outline-alert
statusType="${ifDefined(statusType)}"
size="${ifDefined(size)}"
?shouldShowIcon="${shouldShowIcon}"
?isInteractive="${isInteractive}"
>
${ifDefined(headerSlot)} ${ifDefined(defaultSlot)}
</outline-alert>
`;
};

export const Information = Template.bind({});

Choose a reason for hiding this comment

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

I don't think we need a SB example for every possible iteration/variant.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's beneficial to write stories for every visual representation of a component like we do with the button component, especially as we implement VRT. It would be good to detect if something you did broke a layout of a component in a specific state.

Choose a reason for hiding this comment

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

I'm down to have this discussion in a larger setting, but for me SB serves t purposes. 1: To show what the component can do. 2: To show how to use the component.
I think 1 is taken care of by the controls we take the time to set up. So the number of variants in a story should be dictated by the different ways the component can be used. EX: the alertdialog option here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@LeeMellon , I think that's an interesting point.

This is the first time I've used storybook, so the patterns are new to me. I can see the point of view that a smaller number of stories may be easier for an implementer since it focuses their attention.

If it's helpful, this is what I've used Storybook for so far.

I write the storybook page first with examples of the variations that I understand need to be represented. In the alert, this means one of each status type. There is some level of feeling things out here. On a current project, we have an icon that can show 1800 icons and I don't have 1800 stories. So, I think 4 statuses makes sense to me as stories, but 1800 doesn't. I made another component to show all the colors / icons and those have their own component pages. In development, I was settings CSS classes per status type, but there were no CSS changes for those icons. Meaning, it was possible for me to break a single status type, but not a single icon.

If there is JS interaction, I may write the Jest tests at this point too.

Then, I start implementing the component itself. At this time, I'm watching those stories to see the components change and gradually get the features that I expect. Most of the time I'm looking at the docs pane, or for interaction / screen reader the iframe example.

If I forgot an example, I'll add more stories to capture those use cases.

... And I don't really use the controls much.

I agree with @mabry1985, that for visual regression testing, some of the additional variations may be helpful since the tools will default to one screenshot of each.

Might be a good team discussion or one for Github discussions.

Information.args = {
defaultSlot: html`Here is an informational message.`,
statusType: 'information',
};

export const Warning = Template.bind({});
Warning.args = {
defaultSlot: html`Here is a warning message.`,
statusType: 'warning',
};

export const Error = Template.bind({});
Error.args = {
defaultSlot: html`Here is an error message.`,
statusType: 'error',
};

export const Success = Template.bind({});
Success.args = {
defaultSlot: html`Here is a success message.`,
statusType: 'success',
};

export const Small = Template.bind({});
Small.args = {
defaultSlot: html`Here is a small alert message.`,
size: 'small',
};

export const Header = Template.bind({});
Header.args = {
headerSlot: html`
<span slot="outline-alert--header">
Here is an alert with a custom header.
</span>
`,
defaultSlot: html` Here is a message. `,
};

export const NoIcon = Template.bind({});
NoIcon.args = {
defaultSlot: html`Here is an alert with no icon.`,
shouldShowIcon: false,
};

export const InteractiveAlert = Template.bind({});
InteractiveAlert.args = {
defaultSlot: html`
Here is an alert with an interaction.
<outline-link linkhref="#">Click here</outline-link>
`,
isInteractive: true,
};
67 changes: 67 additions & 0 deletions src/components/base/outline-alert/outline-alert.ts
@@ -0,0 +1,67 @@
import { html, TemplateResult, CSSResultGroup } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import componentStyles from './outline-alert.css.lit';
import { OutlineElement } from '../outline-element/outline-element';

export type AlertSize = 'small' | 'large';
export const alertSizes: AlertSize[] = ['small', 'large'];

export type AlertStatusType = 'information' | 'warning' | 'error' | 'success';
export const alertStatusTypes: AlertStatusType[] = [
'information',
'warning',
'error',
'success',
];

/**
* The Outline Alert component
*
* @element outline-alert
* @slot default - The alert contents
* @slot outline-alert--header - The header in the alert
*/
@customElement('outline-alert')
export class OutlineAlert extends OutlineElement {
static styles: CSSResultGroup = [componentStyles];

@property({ type: String })
statusType: AlertStatusType = 'information';

/**
* This is important context for screen readers.
*/
@property({ type: Boolean })
isInteractive = false;

@property({ type: Boolean })

Choose a reason for hiding this comment

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

Even if they're simple/obvious let's keep the standard of docs for every/state/method.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I agree, I've been doing this and reflecting that comment in the story controls. I'd like to look into creating a method for using those JSDocs to populate the storybook control info for us. Stencil does something similar.

I think for Storybook, especially, it's important to be explicit with the comments because there are QA, design, clients, etc. who are going to be seeing these pages. Its more user friendly and accessible is my argument.

A Storybook style guide is something I'd love to get nailed down soon, I'm just trying to figure out the best way to write them still too. Would be a good discussion for guild.

shouldShowIcon = true;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This probably needs to be shouldHideIcon because of the way attributes work in the DOM. The presence of an attribute means true, so there is no way to set a false value if the default is true. :(

So, all boolean attributes probably need to default to false.

Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting 🤯


@property({ type: String })
size: AlertSize = 'large';

render(): TemplateResult {
// The `body` wrapper is used to avoid styles (like border) that are preventing us from styling `:host`.
return html`
<div id="body" role="${this.isInteractive ? 'alertdialog' : 'alert'}">

Choose a reason for hiding this comment

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

I think this is going to require some more 'plumbing'.
Looks like you're going to need for 'alertdialog':

I see the SB example, but we can't always be sure that the consumer will pass the requirements.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the links! Maybe we can remove this feature for now and add a todo.

Choose a reason for hiding this comment

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

I think you could change isInteractive to something like alertDialogue: string and if it's there pass that to an aria-label attrib, and then set the role to "alertdialog". Just a thought to keep the functionality. Also we may just need to have a focusable close button here either way.

${this.shouldShowIcon === true
mabry1985 marked this conversation as resolved.
Show resolved Hide resolved
? html`
<div id="icon">
<!--@todo include icon when we have that ready.-->
</div>
`
: null}
${this.size === 'large'
? html`
<div id="header">
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this use the <outline-header>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the question. I'm not sure.

In the storybook examples I left it as a slot, so we could pass in an <outline-header> if desired.

The trade-off there seems to be a little more work on the consumer of the component for the flexibility to define the header level / visual level.

I suppose we could always look to see if the slotted component was an <outline-header>, text, or something else and then only wrap text in the <outline-header>. What levels would we use then as defaults?

Anyone have ideas?

Copy link
Contributor

Choose a reason for hiding this comment

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

Good point. I saw the this.statusType and was thinking that was the text that was passed, but realize that is just a placeholder and we should be using content in the slot so that might make more sense. One reason I guess would be to keep all headings the same style.

Copy link
Contributor

@mabry1985 mabry1985 Jul 30, 2021

Choose a reason for hiding this comment

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

I agree with Chris on this one, I think this should be an outline-heading with the statusType rendered inside of it. I don't think there is much benefit to allowing a consumer to place their own element here. If someone wanted to do that they could extend this element and overwrite the largeTemplate() method I'd mentioned above.

Choose a reason for hiding this comment

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

After a while on a project with the enhanced editor I think the slot is a better idea.

<slot name="outline-alert--header">${this.statusType}</slot>
</div>
`
: null}
<div id="message">
<slot></slot>
</div>
</div>
`;
}
}