Skip to content

Commit

Permalink
feat: Add Stories for o-banner demos.
Browse files Browse the repository at this point in the history
Destroy Method:

Adds a `destroy` method to o-banner. This is used in the o-banner
Story to clean up the close button when re-rendered, which o-banner
dynamically creates on `init`. This is required to support the
`suppressCloseButton` Story control.

Hidden Controls:

It appears banner headings (headingLong/headingShort) should only
be used with the small/compact layout variants. Not with the default
banner style which is full bleed. However it is possible to use
Story controls to create that view.

`@storybook/addon-knobs` (deprecated) allowed dynamic knobs,
so we could hide the heading controls if the default layout
was selected. That's not possible with `@storybook/addon-controls`:
storybookjs/storybook#11984

I think that's probably a good thing. Support for
dynamic controls was worked on but not merge. It's
a poor experience when controls shift around.
storybookjs/storybook#13890

For now this commit hides the layout control on layout
demos, and hides the heading controls from the default
layout demo, to avoid showing the discouraged
heading + layout combination. However it is still
possible to select the base layout with heading on the
theme specific demos, so that the small/compact layout
can also be selected which is allowed.

This could be resolved by exporting 2 templates, one
for each kind of banner / usecase. This could make
components easier to reason with and maintain. For
now this commit sticks to one banner template as
coming up with a name without history / useage
guidelines is difficult, and we don't know that
users aren't already using headings with the base
layout - though we never intended it as far as I
can tell.

No Custom Theme Demo:

The custom theme demo has not been migrated to a Story
yet.
https://registry.origami.ft.com/components/[email protected]#demo-custom-theme

I'm not sure there is value, and maybe harm, in showing
made up customised styles alongside those with brand/design
approval. Plus it's not clear how to re-create the style
without understanding Sass and inspecting demo code.
We probably want to have stories for customised components
at a later date, with improved guidelines around them
and demo Sass/JS pane. See related issue:
#370

No Custom Call To Action Demo:

https://registry.origami.ft.com/components/[email protected]#demo-custom-call-to-action-layout

The description of the demo starts "Although not recommended for
design consistency..." Let's not recomend it with a demo.
  • Loading branch information
notlee committed Jan 11, 2022
1 parent 08e0b01 commit 730910b
Show file tree
Hide file tree
Showing 6 changed files with 3,487 additions and 3,920 deletions.
1 change: 1 addition & 0 deletions components/o-banner/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Banner from './src/js/banner.js';

function constructAll () {
Banner.init();

document.removeEventListener('o.DOMContentLoaded', constructAll);
}

Expand Down
12 changes: 11 additions & 1 deletion components/o-banner/src/js/banner.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,10 +270,20 @@ class Banner {
}, {});
}

/**
* Undo the init method
*/
destroy() {
if(this.closeButtonElement) {
this.closeButtonElement.remove();
delete this.closeButtonElement;
}
}

/**
* Initialise banner components.
*
* @param {(HTMLElement | string)} rootElement - The root element to intialise banners in, or a CSS selector for the root element
* @param {(HTMLElement | string)} [rootElement] - The root element to intialise banners in, or a CSS selector for the root element
* @param {object} [options={}] - An options object for configuring the banners
* @returns {Banner | Banner[]} - The newly instantiated Banner (or Banners, if rootElement was not a banner)
*/
Expand Down
105 changes: 105 additions & 0 deletions components/o-banner/src/tsx/banner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
export interface BannerSubmitButton {
action: string;
encoding: string;
method: string;
copy: string;
}

export interface BannerLink {
copy: string;
url: string;
}

export interface BannerProps {
suppressCloseButton?: boolean;
headingLong?: string;
headingShort?: string;
contentLong: string;
contentShort?: string;
closeButtonLabel?: string;
primaryAction?: BannerLink | BannerSubmitButton;
secondaryAction?: BannerLink
theme: 'product' | 'marketing' | '';
layout: 'small' | 'compact' | '';
}

function isBannerSubmitButton(action: BannerSubmitButton | BannerLink): action is BannerSubmitButton {
return (action as BannerSubmitButton).action !== undefined;
}

export function Banner({
suppressCloseButton = false,
headingLong = '',
headingShort = '',
contentLong = 'Hello!',
contentShort = '',
closeButtonLabel = 'Close',
primaryAction = {
copy: 'OK',
url: '#',
},
secondaryAction = undefined,
theme = '',
layout = '',
}: BannerProps) {
const classNames = ['o-banner'];
const dataAttributes = {};

if(layout) {
classNames.push(`o-banner--${layout}`);
}

if(theme) {
classNames.push(`o-banner--${theme}`);
}

if(suppressCloseButton) {
dataAttributes['data-o-banner-suppress-close-button'] = true;
}

return (
<div className={classNames.join(' ')} {...dataAttributes} data-o-component="o-banner">
<div className="o-banner__outer">
<div className="o-banner__inner" data-o-banner-inner>
{contentLong && (
<div className="o-banner__content o-banner__content--long">
{headingLong && (
<header className="o-banner__heading">
<h2>{headingLong}</h2>
</header>
)}
<p>{contentLong}</p>
</div>
)}
{contentShort && (
<div className="o-banner__content o-banner__content--short">
{headingShort && (
<header className="o-banner__heading">
<h2>{headingShort}</h2>
</header>
)}
<p>{contentShort}</p>
</div>
)}
<div className="o-banner__actions">
{primaryAction && !isBannerSubmitButton(primaryAction) && (
<div className="o-banner__action">
<a href="{primaryAction.url}" className="o-banner__button">{primaryAction.copy}</a>
</div>
)}
{primaryAction && isBannerSubmitButton(primaryAction) && (
<form className="o-banner__action" method="{primaryAction.method}" encType="{primaryAction.encoding}" action="{primaryAction.action}">
<input className="o-banner__button" type="submit" value="{primaryAction.copy}" />
</form>
)}
{secondaryAction && (
<div className="o-banner__action o-banner__action--secondary">
<a href="{secondaryAction.url}" className="o-banner__link">{secondaryAction.copy}</a>
</div>
)}
</div>
</div>
</div>
</div>
);
}
7 changes: 7 additions & 0 deletions components/o-banner/stories/banner.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@import "@financial-times/o-fonts/main";
@import "@financial-times/o-normalise/main";
@include oFonts();
@include oNormalise();

@import "@financial-times/o-banner/main";
@include oBanner();
143 changes: 143 additions & 0 deletions components/o-banner/stories/banner.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import {withDesign} from 'storybook-addon-designs';
import {Banner} from '../src/tsx/banner';
import {useEffect} from 'react';
import javascript from '../main';
import './banner.scss';
import withHtml from 'origami-storybook-addon-html';

export default {
title: 'Banner',
component: Banner,
decorators: [withDesign, withHtml],
args: {
},
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/MyHQ1qdwYyek5IBdhEEaND/FT-UI-Library?node-id=0%3A1489',
},
guidelines: {
notion: '37cdc7ac2cac4d60a4c9f451c47a4647',
},
html: {},
},
};

const Story = args => {
useEffect(() => {
let banners = javascript.init();
return function cleanup() {
banners = Array.isArray(banners) ? banners : [banners];
banners.forEach(banner => banner.destroy());
}
}, [args.suppressCloseButton]);
return <Banner {...args} />;
};

export const Default = Story.bind({});
Default.args = {
suppressCloseButton: false,
contentLong: 'Try the new compact homepage. A list view of today\'s homepage with fewer images.',
contentShort: 'Try it now',
closeButtonLabel: 'Close',
primaryAction: {
copy: 'Try it now',
url: '#',
},
secondaryAction: {
copy: 'Give feedback',
url: '#'
},
theme: '',
layout: ''
};
// Exclude layout and heading controls to discourage use of the default layout with a heading.
// https://github.com/Financial-Times/origami/pull/523
Default.parameters = {controls: { exclude: ['headingLong', 'headingShort', 'layout'] }};


export const Small = Story.bind({});
Small.args = {
suppressCloseButton: false,
headingLong: 'FT Compact',
headingShort: 'FT Compact',
contentLong: 'Try the new compact homepage. A list view of today\'s homepage with fewer images.',
contentShort: 'Try it now',
closeButtonLabel: 'Close',
primaryAction: {
copy: 'Try it now',
url: '#',
},
secondaryAction: {
copy: 'Give feedback',
url: '#'
},
theme: '',
layout: 'small'
};
// Exclude layout to discourage use of the default layout with a heading.
// https://github.com/Financial-Times/origami/pull/523
Small.parameters = {controls: { exclude: ['layout'] }};

export const Compact = Story.bind({});
Compact.args = {
suppressCloseButton: false,
headingLong: 'FT Compact',
headingShort: 'FT Compact',
contentLong: 'Try the new compact homepage. A list view of today\'s homepage with fewer images.',
contentShort: 'Try it now',
closeButtonLabel: 'Close',
primaryAction: {
copy: 'Try it now',
url: '#',
},
secondaryAction: {
copy: 'Give feedback',
url: '#'
},
theme: '',
layout: 'compact'
};
// Exclude layout to discourage use of the default layout with a heading.
// https://github.com/Financial-Times/origami/pull/523
Compact.parameters = {controls: { exclude: ['layout'] }};

export const Marketing = Story.bind({});
Marketing.args = {
suppressCloseButton: false,
headingLong: 'FT Compact',
headingShort: 'FT Compact',
contentLong: 'Try the new compact homepage. A list view of today\'s homepage with fewer images.',
contentShort: 'Try it now',
closeButtonLabel: 'Close',
primaryAction: {
copy: 'Try it now',
url: '#',
},
secondaryAction: {
copy: 'Give feedback',
url: '#'
},
theme: 'marketing',
layout: 'small'
};

export const Product = Story.bind({});
Product.args = {
suppressCloseButton: false,
headingLong: 'FT Compact',
headingShort: 'FT Compact',
contentLong: 'Try the new compact homepage. A list view of today\'s homepage with fewer images.',
contentShort: 'Try it now',
closeButtonLabel: 'Close',
primaryAction: {
copy: 'Try it now',
url: '#',
},
secondaryAction: {
copy: 'Give feedback',
url: '#'
},
theme: 'product',
layout: 'small'
};
Loading

0 comments on commit 730910b

Please sign in to comment.