@hig/banner
Advanced tools
Comparing version 0.2.0-alpha.8d7714a2 to 0.2.0-alpha.8e499225
{ | ||
"name": "@hig/banner", | ||
"version": "0.2.0-alpha.8d7714a2", | ||
"version": "0.2.0-alpha.8e499225", | ||
"description": "HIG Banner", | ||
@@ -11,4 +11,6 @@ "author": "Autodesk Inc.", | ||
"devDependencies": { | ||
"@hig/scripts": "0.2.0-alpha.8d7714a2", | ||
"@hig/styles": "0.1.0-alpha.8d7714a2" | ||
"@hig/eslint-config": "0.2.0-alpha.8e499225", | ||
"@hig/scripts": "0.2.0-alpha.8e499225", | ||
"@hig/styles": "0.1.0-alpha.8e499225", | ||
"hig-react": "0.29.0-alpha.8e499225" | ||
}, | ||
@@ -20,7 +22,19 @@ "peerDependencies": { | ||
"scripts": { | ||
"build": "hig-scripts-build" | ||
"build": "hig-scripts-build", | ||
"lint": "eslint ./src/**/*" | ||
}, | ||
"dependencies": { | ||
"hig-react": "0.29.0-alpha.8d7714a2" | ||
} | ||
"@hig/icon": "0.1.0-alpha.8e499225", | ||
"@hig/icon-button": "0.1.0-alpha.8e499225", | ||
"@hig/icons": "0.1.0-alpha.8e499225", | ||
"@hig/typography": "0.1.0-alpha.8e499225", | ||
"react-lifecycles-compat": "^2.0.0" | ||
}, | ||
"eslintConfig": { | ||
"extends": "@hig" | ||
}, | ||
"eslintIgnore": [ | ||
"*.scss", | ||
"*.json" | ||
] | ||
} |
@@ -1,88 +0,21 @@ | ||
import React from "react"; | ||
import { storiesOf } from "@storybook/react"; | ||
import { action } from "@storybook/addon-actions"; | ||
import { text, select } from "@storybook/addon-knobs/react"; | ||
import { withInfo } from "@storybook/addon-info"; | ||
import { Button } from "hig-react"; | ||
import Banner from "../Banner"; | ||
import { selectLanguage } from "./getKnobs"; | ||
import infoOptions from "./infoOptions"; | ||
import renderBannerStory from "./renderBannerStory"; | ||
import stories from "./stories"; | ||
function getBannerKnobs(props) { | ||
const { | ||
type, | ||
placement, | ||
label, | ||
message, | ||
dismissButtonTitle, | ||
onDismiss, | ||
...otherProps | ||
} = props; | ||
const storybook = storiesOf("Banner", module); | ||
return { | ||
type: select("Type", Banner.AVAILABLE_TYPES, type), | ||
placement: select("Placement", Banner.AVAILABLE_PLACEMENTS, placement), | ||
label: text("Label", label), | ||
message: text("Message", message), | ||
dismissButtonTitle: text("Dismiss title", dismissButtonTitle), | ||
onDismiss: action("Banner dismissed", onDismiss), | ||
labelId: "unique-id", | ||
isVisible: true, | ||
...otherProps | ||
}; | ||
} | ||
stories.forEach(({ description, getProps }) => { | ||
storybook.add( | ||
description, | ||
withInfo(infoOptions)(() => { | ||
const language = selectLanguage(); | ||
const props = getProps({ language }); | ||
function BannerDemo({ children, ...props }) { | ||
return ( | ||
<div style={{ marginBottom: "15px" }}> | ||
<Banner {...getBannerKnobs(props)}>{children}</Banner> | ||
</div> | ||
return renderBannerStory(props); | ||
}) | ||
); | ||
} | ||
function BannerStory({ props }) { | ||
return <BannerDemo {...props} />; | ||
} | ||
const bannerStories = storiesOf("Banner", module); | ||
const stories = [ | ||
{ | ||
description: "default", | ||
props: {} | ||
}, | ||
{ | ||
description: "verbose, with interactions", | ||
props: { | ||
type: Banner.types.WARNING, | ||
label: "PROCESS COMPLETE", | ||
// eslint-disable-next-line max-len | ||
message: | ||
"Changes have been made to you document. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam.", | ||
/** @todo Cleanup/refactor */ | ||
children: ({ isWrappingActions }) => ( | ||
<Banner.Interactions isWrappingActions={isWrappingActions}> | ||
<Banner.Action> | ||
<Button | ||
type="secondary" | ||
size="small" | ||
width={isWrappingActions ? "grow" : "shrink"} | ||
title={text("Resolve text", "Accept Changes")} | ||
/> | ||
</Banner.Action> | ||
<Banner.Action> | ||
<Button | ||
type="secondary" | ||
size="small" | ||
width={isWrappingActions ? "grow" : "shrink"} | ||
title={text("Reject text", "Reject Changes")} | ||
/> | ||
</Banner.Action> | ||
</Banner.Interactions> | ||
) | ||
} | ||
} | ||
]; | ||
stories.forEach(({ description, props }) => { | ||
bannerStories.add(description, () => <BannerStory props={props} />); | ||
}); |
@@ -6,2 +6,3 @@ import React, { Component } from "react"; | ||
import { types, AVAILABLE_TYPES } from "./types"; | ||
import animatorPropsByPlacement from "./animatorPropsByPlacement"; | ||
import BannerAction from "./BannerAction"; | ||
@@ -18,6 +19,6 @@ import BannerAnimator from "./BannerAnimator"; | ||
* @property {string} [label] | ||
* @property {string} [message] | ||
* @property {string} [labelledBy] | ||
* @property {any} [actions] | ||
* @property {string} [dismissButtonTitle] | ||
* @property {Function} [onDismiss] | ||
* @property {string} [labelId] | ||
* @property {boolean} [isVisible] | ||
@@ -45,4 +46,6 @@ * @property {any} [children] | ||
label: PropTypes.string, | ||
/** The displayed message */ | ||
message: PropTypes.string, | ||
/** The ID used for ARIA labeling */ | ||
labelledBy: PropTypes.string, | ||
/** Banner actions; Any JSX, or a render prop function */ | ||
actions: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), | ||
/** Accessibility text for the dismiss button */ | ||
@@ -52,8 +55,6 @@ dismissButtonTitle: PropTypes.string, | ||
onDismiss: PropTypes.func, | ||
/** The ID used for ARIA labeling */ | ||
labelId: PropTypes.string, | ||
/** Animation; Determines the visibility of the banner */ | ||
isVisible: PropTypes.bool, | ||
/** Banner actions; Any JSX, or a render prop function */ | ||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]) | ||
/** The displayed message */ | ||
children: PropTypes.string | ||
}; | ||
@@ -74,9 +75,26 @@ | ||
render() { | ||
const { isVisible, children: actions } = this.props; | ||
/** | ||
* @param {import("./BannerAnimator").ContainerBag} containerBag | ||
*/ | ||
renderContainer = ({ handleReady }) => { | ||
const { actions } = this.props; | ||
const { renderPresenter } = this; | ||
return ( | ||
<BannerAnimator isVisible={isVisible}> | ||
<BannerContainer actions={actions}>{renderPresenter}</BannerContainer> | ||
<BannerContainer actions={actions} onReady={handleReady}> | ||
{renderPresenter} | ||
</BannerContainer> | ||
); | ||
}; | ||
render() { | ||
const { isVisible, placement } = this.props; | ||
const { renderContainer } = this; | ||
return ( | ||
<BannerAnimator | ||
isVisible={isVisible} | ||
{...animatorPropsByPlacement[placement]} | ||
> | ||
{renderContainer} | ||
</BannerAnimator> | ||
@@ -83,0 +101,0 @@ ); |
import { mount } from "enzyme"; | ||
import React from "react"; | ||
import renderer from "react-test-renderer"; | ||
@@ -7,47 +8,52 @@ import Banner from "./Banner"; | ||
describe("banner/Banner", () => { | ||
describe("Action", () => { | ||
it("exposes the `Action` component", () => { | ||
expect(Banner).toHaveProperty("Action", expect.any(Function)); | ||
}); | ||
}); | ||
const exposedComponents = ["Action", "Interactions", "Presenter"]; | ||
const componentMatcher = expect.any(Function); | ||
describe("Interactions", () => { | ||
it("exposes the `Interactions` component", () => { | ||
expect(Banner).toHaveProperty("Interactions", expect.any(Function)); | ||
exposedComponents.forEach(componentName => { | ||
describe(componentName, () => { | ||
it(`exposes the \`${componentName}\` component`, () => { | ||
expect(Banner).toHaveProperty(componentName, componentMatcher); | ||
}); | ||
}); | ||
}); | ||
describe("Presenter", () => { | ||
it("exposes the `Presenter` component", () => { | ||
expect(Banner).toHaveProperty("Presenter", expect.any(Function)); | ||
}); | ||
}); | ||
describe("sub-component rendering", () => { | ||
const renderedComponents = [ | ||
"BannerAnimator", | ||
"BannerContainer", | ||
"BannerPresenter" | ||
]; | ||
describe("rendering", () => { | ||
beforeAll(() => { | ||
window.requestAnimationFrame = jest.fn(); | ||
}); | ||
let wrapper; | ||
afterAll(() => { | ||
delete window.requestAnimationFrame; | ||
beforeEach(() => { | ||
wrapper = mount(<Banner />); | ||
}); | ||
it("renders the `BannerAnimator`", () => { | ||
const wrapper = mount(<Banner />); | ||
expect(wrapper.find("BannerAnimator")).toBePresent(); | ||
renderedComponents.forEach(componentName => { | ||
describe(componentName, () => { | ||
it(`renders a \`${componentName}\` component`, () => { | ||
expect(wrapper.find(componentName)).toBePresent(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it("renders the `BannerContainer`", () => { | ||
const wrapper = mount(<Banner />); | ||
describe("snapshot tests", () => { | ||
const cases = [ | ||
{ | ||
description: "renders with no props", | ||
props: {} | ||
} | ||
]; | ||
expect(wrapper.find("BannerContainer")).toBePresent(); | ||
}); | ||
cases.forEach(({ description, props: { children, ...otherProps } }) => { | ||
it(description, () => { | ||
const wrapper = <Banner {...otherProps}>{children}</Banner>; | ||
const tree = renderer.create(wrapper).toJSON(); | ||
it("renders the `BannerPresenter`", () => { | ||
const wrapper = mount(<Banner />); | ||
expect(wrapper.find("BannerPresenter")).toBePresent(); | ||
expect(tree).toMatchSnapshot(); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -16,2 +16,3 @@ import { Component } from "react"; | ||
* @property {any} [actions] | ||
* @property {Function} [onReady] | ||
* @property {function(PresenterBag): any} [children] | ||
@@ -38,2 +39,4 @@ */ | ||
actions: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), | ||
/** Called after the component has been mounted, and dynamically resized */ | ||
onReady: PropTypes.func, | ||
/** A render prop function to render a `BannerPresenter` */ | ||
@@ -51,3 +54,3 @@ children: PropTypes.func.isRequired | ||
this.bindResize(); | ||
this.updateWrapping(); | ||
this.updateWrapping(this.props.onReady); | ||
} | ||
@@ -141,3 +144,6 @@ | ||
updateContentWrapping = () => { | ||
/** | ||
* @param {Function} callback | ||
*/ | ||
updateContentWrapping = callback => { | ||
const update = { isWrappingContent: this.shouldWrapContent() }; | ||
@@ -147,12 +153,17 @@ | ||
delete this.wrappingFrame; | ||
if (callback) callback(); | ||
}); | ||
}; | ||
updateActionWrapping = () => { | ||
/** | ||
* @param {Function} callback | ||
*/ | ||
updateActionWrapping = callback => { | ||
const update = { isWrappingActions: this.shouldWrapActions() }; | ||
this.setState(update, () => { | ||
this.wrappingFrame = window.requestAnimationFrame( | ||
this.updateContentWrapping | ||
); | ||
this.wrappingFrame = window.requestAnimationFrame(() => { | ||
this.updateContentWrapping(callback); | ||
}); | ||
}); | ||
@@ -163,9 +174,10 @@ }; | ||
* Asynchronously updates the wrapping behavior of the presenter | ||
* @param {Function} callback | ||
*/ | ||
updateWrapping() { | ||
updateWrapping(callback) { | ||
if (this.wrappingFrame !== undefined) return; | ||
this.wrappingFrame = window.requestAnimationFrame( | ||
this.updateActionWrapping | ||
); | ||
this.wrappingFrame = window.requestAnimationFrame(() => { | ||
this.updateActionWrapping(callback); | ||
}); | ||
} | ||
@@ -199,3 +211,3 @@ | ||
refInteractionsWrapper, | ||
children: this.renderActions() | ||
actions: this.renderActions() | ||
}; | ||
@@ -202,0 +214,0 @@ } |
@@ -28,3 +28,3 @@ import { shallow } from "enzyme"; | ||
expect(presenterBag).toMatchObject({ | ||
children: "foobar", | ||
actions: "foobar", | ||
isWrappingContent: false, | ||
@@ -31,0 +31,0 @@ refContent: expect.any(Function), |
@@ -5,3 +5,2 @@ import React from "react"; | ||
import { types, AVAILABLE_TYPES } from "../types"; | ||
import { placements, AVAILABLE_PLACEMENTS } from "../placements"; | ||
@@ -13,3 +12,2 @@ import { | ||
InteractionsWrapper, | ||
Label, | ||
Message, | ||
@@ -23,8 +21,7 @@ Notification, | ||
* @property {string} [type] | ||
* @property {string} [placement] | ||
* @property {string} [label] | ||
* @property {string} [message] | ||
* @property {string} [labelledBy] | ||
* @property {any} [actions] | ||
* @property {string} [dismissButtonTitle] | ||
* @property {Function} [onDismiss] | ||
* @property {string} [labelId] | ||
* @property {boolean} [isWrappingContent] | ||
@@ -45,8 +42,7 @@ * @property {function(HTMLDivElement): any} [refContent] | ||
type, | ||
placement, | ||
label, | ||
message, | ||
labelledBy, | ||
actions, | ||
dismissButtonTitle, | ||
onDismiss, | ||
labelId, | ||
isWrappingContent, | ||
@@ -56,8 +52,6 @@ refContent, | ||
refInteractionsWrapper, | ||
children | ||
children: message | ||
} = props; | ||
const hasLabel = !!label; | ||
const hasActions = React.Children.count(children) > 0; | ||
const wrapperLabelledBy = hasLabel ? labelId : undefined; | ||
const hasActions = React.Children.count(actions) > 0; | ||
@@ -67,6 +61,6 @@ return ( | ||
type={type} | ||
placement={placement} | ||
hasActions={hasActions} | ||
isWrappingContent={isWrappingContent} | ||
labelledBy={wrapperLabelledBy} | ||
label={label} | ||
labelledBy={labelledBy} | ||
> | ||
@@ -76,3 +70,2 @@ <Icon type={type} /> | ||
<Notification innerRef={refNotification}> | ||
{hasLabel ? <Label id={labelId}>{label}</Label> : null} | ||
<Message>{message}</Message> | ||
@@ -82,3 +75,3 @@ </Notification> | ||
<InteractionsWrapper innerRef={refInteractionsWrapper}> | ||
{children} | ||
{actions} | ||
</InteractionsWrapper> | ||
@@ -95,6 +88,5 @@ ) : null} | ||
type: types.PRIMARY, | ||
placement: placements.STANDARD, | ||
message: "Message", | ||
dismissButtonTitle: "Dismiss", | ||
isWrappingContent: false | ||
isWrappingContent: false, | ||
children: "Message" | ||
}; | ||
@@ -105,8 +97,8 @@ | ||
type: PropTypes.oneOf(AVAILABLE_TYPES), | ||
/** Determines the intended placement of banner */ | ||
placement: PropTypes.oneOf(AVAILABLE_PLACEMENTS), | ||
/** The label of the message displayed */ | ||
label: PropTypes.string, | ||
/** The displayed message */ | ||
message: PropTypes.string, | ||
/** The ID used for ARIA labeling */ | ||
labelledBy: PropTypes.string, | ||
/** Banner actions */ | ||
actions: PropTypes.node, | ||
/** Accessibility text for the dismiss button */ | ||
@@ -116,4 +108,2 @@ dismissButtonTitle: PropTypes.string, | ||
onDismiss: PropTypes.func, | ||
/** The ID used for ARIA labeling */ | ||
labelId: PropTypes.string, | ||
/** Determines whether the banner content wraps */ | ||
@@ -127,4 +117,4 @@ isWrappingContent: PropTypes.bool, | ||
refInteractionsWrapper: PropTypes.func, | ||
/** Banner actions */ | ||
/** The displayed message */ | ||
children: PropTypes.node | ||
}; |
import React from "react"; | ||
import renderer from "react-test-renderer"; | ||
import { placements } from "../placements"; | ||
import { types } from "../types"; | ||
import BannerPresenter from "./BannerPresenter"; | ||
describe.only("banner/BannerPresenter/BannerPresenter", () => { | ||
describe("banner/BannerPresenter/BannerPresenter", () => { | ||
[ | ||
@@ -19,23 +18,22 @@ { | ||
label: "HELLO", | ||
message: "World" | ||
children: "World" | ||
} | ||
}, | ||
{ | ||
description: "renders with a string as children", | ||
description: "renders with a string as actions", | ||
props: { | ||
placement: placements.TOP, | ||
children: "foobar" | ||
actions: "foobar" | ||
} | ||
}, | ||
{ | ||
description: "renders with a node as children", | ||
description: "renders with a node as actions", | ||
props: { | ||
dismissButtonTitle: "boom", | ||
children: <span>foo</span> | ||
actions: <span>foo</span> | ||
} | ||
}, | ||
{ | ||
description: "renders with a fragment as children", | ||
description: "renders with a fragment as actions", | ||
props: { | ||
children: [<span key="0">bar</span>, <div key="1">baz</div>] | ||
actions: [<span key="0">bar</span>, <div key="1">baz</div>] | ||
} | ||
@@ -42,0 +40,0 @@ } |
@@ -6,6 +6,7 @@ /* eslint-disable react/prop-types */ | ||
import { Icon as BasicIcon, IconButton, Text } from "hig-react"; | ||
import BasicIcon, { names as iconNames, sizes as iconSizes } from "@hig/icon"; | ||
import IconButton, { types as iconButtonTypes } from "@hig/icon-button"; | ||
import { Text } from "@hig/typography"; | ||
import "./banner-presenter.scss"; | ||
import { placements } from "../placements"; | ||
import { types } from "../types"; | ||
@@ -16,5 +17,2 @@ | ||
const { types: iconButtonTypes } = IconButton; | ||
const { names: iconNames, sizes: iconSizes } = BasicIcon; | ||
/** @type {Object.<string, string>} */ | ||
@@ -28,3 +26,3 @@ const classNames = Object.freeze({ | ||
interactionsWrapper: "hig__banner__interactions-wrapper", | ||
notification: "hig__banner__notification", | ||
notification: "hig__banner__message", | ||
wrapper: "hig__banner", | ||
@@ -42,8 +40,2 @@ wrapperBottom: "hig__banner--bottom", | ||
/** @type {Object.<string, string>} */ | ||
const wrapperModifiersByPlacement = { | ||
[placements.BOTTOM]: classNames.wrapperBottom, | ||
[placements.TOP]: classNames.wrapperTop | ||
}; | ||
/** @type {Object.<string, string>} */ | ||
const wrapperModifiersByType = { | ||
@@ -73,3 +65,2 @@ [types.PRIMARY]: classNames.wrapperPrimary, | ||
* @property {string} type | ||
* @property {string} placement | ||
* @property {boolean} hasActions | ||
@@ -88,4 +79,4 @@ * @property {string | undefined} [labelledBy] | ||
type, | ||
placement, | ||
hasActions, | ||
label, | ||
labelledBy, | ||
@@ -99,3 +90,2 @@ isWrappingContent, | ||
wrapperModifiersByType[type], | ||
wrapperModifiersByPlacement[placement], | ||
hasActions ? classNames.wrapperInteractive : undefined, | ||
@@ -106,3 +96,9 @@ isWrappingContent ? classNames.wrapperWrapContent : undefined | ||
return ( | ||
<div role="alert" aria-labelledby={labelledBy} className={classes}> | ||
<div | ||
role="alert" | ||
aria-label={label} | ||
aria-labelledby={labelledBy} | ||
aria-live={type === types.URGENT ? "assertive" : "polite"} | ||
className={classes} | ||
> | ||
{children} | ||
@@ -179,16 +175,2 @@ </div> | ||
/** | ||
* @typedef {Object} LabelProps | ||
* @property {string} [id] | ||
* @property {any} [children] | ||
*/ | ||
/** | ||
* @param {LabelProps} props | ||
* @returns {JSX.Element} | ||
*/ | ||
export function Label({ id, children }) { | ||
return <Text color={TEXT_COLOR} id={id}>{`${children}: `}</Text>; | ||
} | ||
/** | ||
* @param {StyledProps} props | ||
@@ -202,6 +184,2 @@ * @returns {JSX.Element} | ||
if (typeof children === "function") { | ||
return children(); | ||
} | ||
return children; | ||
@@ -208,0 +186,0 @@ } |
import "./external.scss"; | ||
export { default } from "./Banner"; | ||
@@ -3,0 +4,0 @@ export { default as BannerAction } from "./BannerAction"; |
/** @type {Object.<string, string>} */ | ||
export const placements = Object.freeze({ | ||
STANDARD: "standard", | ||
TOP: "top", | ||
BOTTOM: "bottom" | ||
ABOVE: "above", | ||
ABOVE_OVERLAY: "above-overlay", | ||
BETWEEN: "between", | ||
BELOW_OVERLAY: "below-overlay" | ||
}); | ||
@@ -7,0 +8,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No README
QualityPackage does not have a README. This may indicate a failed publish or a low quality package.
Found 1 instance in 1 package
176789
56
4695
1
50
7
4
45
1
+ Added@hig/icon@0.1.0-alpha.8e499225(transitive)
+ Added@hig/icon-button@0.1.0-alpha.8e499225(transitive)
+ Added@hig/icons@0.1.0-alpha.8e499225(transitive)
+ Added@hig/typography@0.1.0-alpha.8e499225(transitive)
+ Addedreact-lifecycles-compat@2.0.2(transitive)
- Removedhig-react@0.29.0-alpha.8d7714a2
- Removed@babel/runtime@7.26.0(transitive)
- Removeddom-helpers@3.4.0(transitive)
- Removedhig-interface@0.2.0-alpha.8d7714a2(transitive)
- Removedhig-react@0.29.0-alpha.8d7714a2(transitive)
- Removedhig-vanilla@0.2.0-alpha.8d7714a2(transitive)
- Removedreact@18.3.1(transitive)
- Removedreact-dom@15.7.018.3.1(transitive)
- Removedreact-flip-move@3.0.5(transitive)
- Removedreact-lifecycles-compat@3.0.4(transitive)
- Removedreact-transition-group@2.9.0(transitive)
- Removedregenerator-runtime@0.14.1(transitive)
- Removedscheduler@0.23.2(transitive)