From bb60ab835c8d1ffda07a4a8d7a56d653c7c7ff64 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Thu, 25 Apr 2019 13:44:49 +0300 Subject: [PATCH 1/9] remove HOCs, remove `active` on `ComponentButton`, remove unused props --- .../ComponentControls/ComponentButton.tsx | 7 ++-- .../ComponentControls/ComponentControls.tsx | 33 ++++--------------- .../ComponentControlsCodeSandbox.tsx | 22 ++++--------- .../ComponentControlsCopyLink.tsx | 5 ++- .../ComponentExample/ComponentExample.tsx | 5 +-- .../ComponentProp/ComponentPropEnum.tsx | 8 +++-- .../ComponentProp/ComponentPropEnumToggle.tsx | 6 ++-- .../ComponentProp/ComponentPropEnumValue.tsx | 6 ++-- .../ComponentPropFunctionSignature.tsx | 5 +-- .../ComponentProp/ComponentPropHeader.tsx | 17 ---------- .../ComponentPropsComponents.tsx | 8 +++-- .../ComponentTable/ComponentTableHeader.tsx | 6 ++-- docs/src/hoc/index.ts | 2 -- docs/src/hoc/neverUpdate.tsx | 14 -------- docs/src/hoc/updateForKeys.tsx | 16 --------- 15 files changed, 42 insertions(+), 118 deletions(-) delete mode 100644 docs/src/components/ComponentDoc/ComponentProp/ComponentPropHeader.tsx delete mode 100644 docs/src/hoc/index.ts delete mode 100644 docs/src/hoc/neverUpdate.tsx delete mode 100644 docs/src/hoc/updateForKeys.tsx diff --git a/docs/src/components/ComponentDoc/ComponentControls/ComponentButton.tsx b/docs/src/components/ComponentDoc/ComponentControls/ComponentButton.tsx index 545d4d84ce..86ff463991 100644 --- a/docs/src/components/ComponentDoc/ComponentControls/ComponentButton.tsx +++ b/docs/src/components/ComponentDoc/ComponentControls/ComponentButton.tsx @@ -4,12 +4,11 @@ import * as React from 'react' interface LabelledButtonProps { iconName: string label: string - active: boolean onClick?: (event: React.SyntheticEvent) => void } -const LabelledButton = createComponent({ - displayName: 'LabelledButton', +const ComponentButton = createComponent({ + displayName: 'ComponentButton', render: ({ stardust, ...props }) => { const { iconName, label, onClick } = props return ( @@ -20,4 +19,4 @@ const LabelledButton = createComponent({ ) }, }) -export default LabelledButton +export default ComponentButton diff --git a/docs/src/components/ComponentDoc/ComponentControls/ComponentControls.tsx b/docs/src/components/ComponentDoc/ComponentControls/ComponentControls.tsx index c0b4488ed0..7bea76cb13 100644 --- a/docs/src/components/ComponentDoc/ComponentControls/ComponentControls.tsx +++ b/docs/src/components/ComponentDoc/ComponentControls/ComponentControls.tsx @@ -9,7 +9,6 @@ import * as _ from 'lodash' import * as React from 'react' import { NavLink } from 'react-router-dom' -import { updateForKeys } from 'docs/src/hoc' import ComponentButton from './ComponentButton' import { ComponentSourceManagerLanguage } from 'docs/src/components/ComponentDoc/ComponentSourceManager' import ComponentControlsCodeSandbox from './ComponentControlsCodeSandbox/ComponentControlsCodeSandbox' @@ -25,10 +24,7 @@ type ComponentControlsProps = { onShowRtl: (e: React.SyntheticEvent) => void onShowTransparent: (e: React.SyntheticEvent) => void onShowVariables: (e: React.SyntheticEvent) => void - showCode: boolean showRtl: boolean - showTransparent: boolean - showVariables: boolean } const controlsTheme: ThemeInput = { @@ -58,10 +54,7 @@ const ComponentControls: React.FC = props => { exampleCode, exampleLanguage, examplePath, - showCode, showRtl, - showTransparent, - showVariables, onCopyLink, onShowCode, onShowRtl, @@ -83,7 +76,7 @@ const ComponentControls: React.FC = props => { items={[ { key: 'show-code', - content: , + content: , onClick: onShowCode, accessibility: toolbarButtonBehavior, }, @@ -100,31 +93,25 @@ const ComponentControls: React.FC = props => { }, { key: 'show-variables', - content: ( - - ), + content: , onClick: onShowVariables, accessibility: toolbarButtonBehavior, }, { key: 'show-transparent', - content: ( - - ), + content: , onClick: onShowTransparent, accessibility: toolbarButtonBehavior, }, { key: 'show-rtl', - content: , + content: , onClick: onShowRtl, accessibility: toolbarButtonBehavior, }, { key: 'maximize', - content: ( - - ), + content: , as: NavLink, to: `/maximize/${_.kebabCase( examplePath @@ -144,7 +131,6 @@ const ComponentControls: React.FC = props => { )} @@ -160,11 +146,4 @@ const ComponentControls: React.FC = props => { ) } -export default updateForKeys([ - 'exampleCode', - 'examplePath', - 'showRtl', - 'showCode', - 'showTransparent', - 'showVariables', -])(ComponentControls, ComponentButton) +export default React.memo(ComponentControls) diff --git a/docs/src/components/ComponentDoc/ComponentControls/ComponentControlsCodeSandbox/ComponentControlsCodeSandbox.tsx b/docs/src/components/ComponentDoc/ComponentControls/ComponentControlsCodeSandbox/ComponentControlsCodeSandbox.tsx index 1a93e34afe..170e71d47d 100644 --- a/docs/src/components/ComponentDoc/ComponentControls/ComponentControlsCodeSandbox/ComponentControlsCodeSandbox.tsx +++ b/docs/src/components/ComponentDoc/ComponentControls/ComponentControlsCodeSandbox/ComponentControlsCodeSandbox.tsx @@ -2,16 +2,14 @@ import * as React from 'react' import CodeSandboxer from 'react-codesandboxer' import { ComponentSourceManagerLanguage } from 'docs/src/components/ComponentDoc/ComponentSourceManager' -import { updateForKeys } from 'docs/src/hoc' import { appTemplateJs, appTemplateTs } from './indexTemplates' -import LabelledButton from '../ComponentButton' +import ComponentButton from '../ComponentButton' import createPackageJson from './createPackageJson' type ComponentControlsCodeSandboxProps = { exampleCode: string exampleLanguage: ComponentSourceManagerLanguage exampleName: string - active: boolean } type ComponentControlsCodeSandboxState = { @@ -20,7 +18,7 @@ type ComponentControlsCodeSandboxState = { sandboxUrl: string } -class ComponentControlsShowCode extends React.Component< +class ComponentControlsCodeSandbox extends React.PureComponent< ComponentControlsCodeSandboxProps, ComponentControlsCodeSandboxState > { @@ -55,7 +53,7 @@ class ComponentControlsShowCode extends React.Component< } render() { - const { active, exampleLanguage, exampleCode, exampleName } = this.props + const { exampleLanguage, exampleCode, exampleName } = this.props const { examplePath, sandboxUrl } = this.state const main = exampleLanguage === 'ts' ? 'index.tsx' : 'index.js' @@ -64,12 +62,7 @@ class ComponentControlsShowCode extends React.Component< if (sandboxUrl) { return ( - + ) } @@ -86,13 +79,12 @@ class ComponentControlsShowCode extends React.Component< skipRedirect template={template} > - {({ isLoading, isDeploying, active }) => { + {({ isLoading, isDeploying }) => { const loading = isLoading || isDeploying return ( - ) }} @@ -101,4 +93,4 @@ class ComponentControlsShowCode extends React.Component< } } -export default updateForKeys(['exampleCode', 'active'])(ComponentControlsShowCode) +export default ComponentControlsCodeSandbox diff --git a/docs/src/components/ComponentDoc/ComponentControls/ComponentControlsCopyLink.tsx b/docs/src/components/ComponentDoc/ComponentControls/ComponentControlsCopyLink.tsx index 1ab6dee630..62ead5aa3f 100644 --- a/docs/src/components/ComponentDoc/ComponentControls/ComponentControlsCopyLink.tsx +++ b/docs/src/components/ComponentDoc/ComponentControls/ComponentControlsCopyLink.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import LabelledButton from './ComponentButton' +import ComponentButton from './ComponentButton' import * as _ from 'lodash' export default class ComponentControlsCopyLink extends React.Component { @@ -24,10 +24,9 @@ export default class ComponentControlsCopyLink extends React.Component const { active } = this.state return ( - ) diff --git a/docs/src/components/ComponentDoc/ComponentExample/ComponentExample.tsx b/docs/src/components/ComponentDoc/ComponentExample/ComponentExample.tsx index 47fa23c4f0..07ef9fbe41 100644 --- a/docs/src/components/ComponentDoc/ComponentExample/ComponentExample.tsx +++ b/docs/src/components/ComponentDoc/ComponentExample/ComponentExample.tsx @@ -552,7 +552,7 @@ class ComponentExample extends React.Component @@ -576,10 +576,7 @@ class ComponentExample extends React.Component diff --git a/docs/src/components/ComponentDoc/ComponentProp/ComponentPropEnum.tsx b/docs/src/components/ComponentDoc/ComponentProp/ComponentPropEnum.tsx index 576d5810d8..1a1d501009 100644 --- a/docs/src/components/ComponentDoc/ComponentProp/ComponentPropEnum.tsx +++ b/docs/src/components/ComponentDoc/ComponentProp/ComponentPropEnum.tsx @@ -2,7 +2,6 @@ import * as _ from 'lodash' import * as PropTypes from 'prop-types' import * as React from 'react' -import { updateForKeys } from 'docs/src/hoc' import ComponentPropExtra from './ComponentPropExtra' import ComponentPropToggle from './ComponentPropEnumToggle' import ComponentPropValue from './ComponentPropEnumValue' @@ -39,4 +38,9 @@ ComponentPropEnum.propTypes = { values: PropTypes.array, } -export default updateForKeys(['showAll', 'type', 'values'])(ComponentPropEnum) +const arePropsEqual = (prevProps, nextProps) => + prevProps.showAll === nextProps.showAll && + prevProps.type === nextProps.type && + prevProps.values === nextProps.values + +export default React.memo(ComponentPropEnum, arePropsEqual) diff --git a/docs/src/components/ComponentDoc/ComponentProp/ComponentPropEnumToggle.tsx b/docs/src/components/ComponentDoc/ComponentProp/ComponentPropEnumToggle.tsx index f183cc4f12..a5541f03ce 100644 --- a/docs/src/components/ComponentDoc/ComponentProp/ComponentPropEnumToggle.tsx +++ b/docs/src/components/ComponentDoc/ComponentProp/ComponentPropEnumToggle.tsx @@ -1,8 +1,6 @@ import * as PropTypes from 'prop-types' import * as React from 'react' -import { updateForKeys } from 'docs/src/hoc' - const toggleStyle = { cursor: 'pointer', } @@ -19,4 +17,6 @@ ComponentPropEnumToggle.propTypes = { total: PropTypes.number, } -export default updateForKeys(['showAll'])(ComponentPropEnumToggle) +const areEqualProps = (prevProps, nextProps) => prevProps.showAll === nextProps.showAll + +export default React.memo(ComponentPropEnumToggle, areEqualProps) diff --git a/docs/src/components/ComponentDoc/ComponentProp/ComponentPropEnumValue.tsx b/docs/src/components/ComponentDoc/ComponentProp/ComponentPropEnumValue.tsx index c970543886..ffd1b8ba50 100644 --- a/docs/src/components/ComponentDoc/ComponentProp/ComponentPropEnumValue.tsx +++ b/docs/src/components/ComponentDoc/ComponentProp/ComponentPropEnumValue.tsx @@ -1,8 +1,6 @@ import * as PropTypes from 'prop-types' import * as React from 'react' -import { neverUpdate } from 'docs/src/hoc' - const spanStyle = { display: 'inline-block', paddingRight: '0.2em', @@ -18,4 +16,6 @@ ComponentPropEnumValue.propTypes = { children: PropTypes.node, } -export default neverUpdate(ComponentPropEnumValue) +const arePropsEqual = () => true + +export default React.memo(ComponentPropEnumValue, arePropsEqual) diff --git a/docs/src/components/ComponentDoc/ComponentProp/ComponentPropFunctionSignature.tsx b/docs/src/components/ComponentDoc/ComponentProp/ComponentPropFunctionSignature.tsx index e62f4ac1ad..be78cb8810 100644 --- a/docs/src/components/ComponentDoc/ComponentProp/ComponentPropFunctionSignature.tsx +++ b/docs/src/components/ComponentDoc/ComponentProp/ComponentPropFunctionSignature.tsx @@ -2,7 +2,6 @@ import * as _ from 'lodash' import * as PropTypes from 'prop-types' import * as React from 'react' -import { neverUpdate } from 'docs/src/hoc' import ComponentPropExtra, { ComponentPropExtraProps } from './ComponentPropExtra' interface ComponentPropFunctionProps extends ComponentPropExtraProps { @@ -75,4 +74,6 @@ ComponentPropFunctionSignature.propTypes = { tags: PropTypes.array, } -export default neverUpdate(ComponentPropFunctionSignature) +const arePropsEqual = () => true + +export default React.memo(ComponentPropFunctionSignature, arePropsEqual) diff --git a/docs/src/components/ComponentDoc/ComponentProp/ComponentPropHeader.tsx b/docs/src/components/ComponentDoc/ComponentProp/ComponentPropHeader.tsx deleted file mode 100644 index f546f9dc51..0000000000 --- a/docs/src/components/ComponentDoc/ComponentProp/ComponentPropHeader.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from 'react' - -import { neverUpdate } from 'docs/src/hoc' - -// TODO: Use Flex or some Table component -const ComponentPropHeader = () => ( - - - Name - Default - Type - Description - - -) - -export default neverUpdate(ComponentPropHeader) diff --git a/docs/src/components/ComponentDoc/ComponentProps/ComponentPropsComponents.tsx b/docs/src/components/ComponentDoc/ComponentProps/ComponentPropsComponents.tsx index f403cd5489..f7f2044aa7 100644 --- a/docs/src/components/ComponentDoc/ComponentProps/ComponentPropsComponents.tsx +++ b/docs/src/components/ComponentDoc/ComponentProps/ComponentPropsComponents.tsx @@ -3,8 +3,6 @@ import * as PropTypes from 'prop-types' import * as React from 'react' import { Menu, tabListBehavior } from '@stardust-ui/react' -import { updateForKeys } from 'docs/src/hoc' - const ComponentPropsComponents: any = ({ activeDisplayName, displayNames, @@ -42,4 +40,8 @@ ComponentPropsComponents.propTypes = { parentDisplayName: PropTypes.string.isRequired, } -export default updateForKeys(['activeDisplayName', 'parentDisplayName'])(ComponentPropsComponents) +const areEqualProps = (prevProps, nextProps) => + prevProps.activeDisplayName === nextProps.activeDisplayName && + prevProps.parentDisplayName === nextProps.parentDisplayName + +export default React.memo(ComponentPropsComponents, areEqualProps) diff --git a/docs/src/components/ComponentDoc/ComponentTable/ComponentTableHeader.tsx b/docs/src/components/ComponentDoc/ComponentTable/ComponentTableHeader.tsx index 0a7888c0eb..5b1006f0a6 100644 --- a/docs/src/components/ComponentDoc/ComponentTable/ComponentTableHeader.tsx +++ b/docs/src/components/ComponentDoc/ComponentTable/ComponentTableHeader.tsx @@ -1,7 +1,5 @@ import * as React from 'react' -import { neverUpdate } from 'docs/src/hoc' - // TODO: use Flex or a Table component, when it will be available const ComponentTableHeader = () => ( @@ -14,4 +12,6 @@ const ComponentTableHeader = () => ( ) -export default neverUpdate(ComponentTableHeader) +const arePropsEqual = () => true + +export default React.memo(ComponentTableHeader, arePropsEqual) diff --git a/docs/src/hoc/index.ts b/docs/src/hoc/index.ts deleted file mode 100644 index 08094c3e59..0000000000 --- a/docs/src/hoc/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as neverUpdate } from './neverUpdate' -export { default as updateForKeys } from './updateForKeys' diff --git a/docs/src/hoc/neverUpdate.tsx b/docs/src/hoc/neverUpdate.tsx deleted file mode 100644 index 15c2ab22d7..0000000000 --- a/docs/src/hoc/neverUpdate.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import * as React from 'react' - -const neverUpdate = (ChildComponent): any => - class extends React.Component { - shouldComponentUpdate() { - return false - } - - render() { - return - } - } - -export default neverUpdate diff --git a/docs/src/hoc/updateForKeys.tsx b/docs/src/hoc/updateForKeys.tsx deleted file mode 100644 index d49df8d67b..0000000000 --- a/docs/src/hoc/updateForKeys.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import * as _ from 'lodash' -import * as React from 'react' -import * as shallowEqual from 'shallowequal' - -const updateForKeys = (propKeys): any => (ChildComponent): any => - class extends React.Component { - shouldComponentUpdate(nextProps) { - return !shallowEqual(_.pick(this.props, propKeys), _.pick(nextProps, propKeys)) - } - - render() { - return - } - } - -export default updateForKeys From eb458e0f29fe7513bbf482ce0e8436910967da68 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Thu, 25 Apr 2019 15:26:52 +0300 Subject: [PATCH 2/9] rename to ComponentPropsTable, add useComponentProps hook --- .../ComponentProps/ComponentProps.tsx | 6 +-- .../ComponentPropsRow.tsx} | 2 +- .../ComponentPropsTable.tsx | 43 +++++++++++++++++++ .../ComponentPropsTable/index.tsx | 1 + .../ComponentTable/ComponentTable.tsx | 31 ------------- .../ComponentTable/ComponentTableHeader.tsx | 17 -------- .../ComponentDoc/ComponentTable/index.tsx | 1 - .../ComponentDoc/useComponentProps.ts | 15 +++++++ 8 files changed, 63 insertions(+), 53 deletions(-) rename docs/src/components/ComponentDoc/{ComponentTable/ComponentTableRow.tsx => ComponentPropsTable/ComponentPropsRow.tsx} (96%) create mode 100644 docs/src/components/ComponentDoc/ComponentPropsTable/ComponentPropsTable.tsx create mode 100644 docs/src/components/ComponentDoc/ComponentPropsTable/index.tsx delete mode 100644 docs/src/components/ComponentDoc/ComponentTable/ComponentTable.tsx delete mode 100644 docs/src/components/ComponentDoc/ComponentTable/ComponentTableHeader.tsx delete mode 100644 docs/src/components/ComponentDoc/ComponentTable/index.tsx create mode 100644 docs/src/components/ComponentDoc/useComponentProps.ts diff --git a/docs/src/components/ComponentDoc/ComponentProps/ComponentProps.tsx b/docs/src/components/ComponentDoc/ComponentProps/ComponentProps.tsx index efa154fe9c..35c80d5b3f 100644 --- a/docs/src/components/ComponentDoc/ComponentProps/ComponentProps.tsx +++ b/docs/src/components/ComponentDoc/ComponentProps/ComponentProps.tsx @@ -3,7 +3,7 @@ import * as PropTypes from 'prop-types' import * as React from 'react' import { getComponentGroup } from 'docs/src/utils' -import ComponentTable from '../ComponentTable' +import ComponentTableProps from '../ComponentPropsTable' import ComponentPropsComponents from './ComponentPropsComponents' import ComponentPropsDescription from './ComponentPropsDescription' import { ICSSInJSStyle, Input, Text, Flex } from '@stardust-ui/react' @@ -48,7 +48,7 @@ export default class ComponentProps extends React.Component { const { displayName } = this.props const { activeDisplayName, componentGroup } = this.state const displayNames = _.keys(componentGroup) - const { docblock, props } = (componentGroup[activeDisplayName] || {}) as any + const { docblock } = (componentGroup[activeDisplayName] || {}) as any const description = _.get(docblock, 'description', []) return ( @@ -81,7 +81,7 @@ export default class ComponentProps extends React.Component { <> - + )} diff --git a/docs/src/components/ComponentDoc/ComponentTable/ComponentTableRow.tsx b/docs/src/components/ComponentDoc/ComponentPropsTable/ComponentPropsRow.tsx similarity index 96% rename from docs/src/components/ComponentDoc/ComponentTable/ComponentTableRow.tsx rename to docs/src/components/ComponentDoc/ComponentPropsTable/ComponentPropsRow.tsx index 0d1c1bf96a..321e9cc63d 100644 --- a/docs/src/components/ComponentDoc/ComponentTable/ComponentTableRow.tsx +++ b/docs/src/components/ComponentDoc/ComponentPropsTable/ComponentPropsRow.tsx @@ -6,7 +6,7 @@ import ComponentPropDefaultValue from '../ComponentProp/ComponentPropDefaultValu import ComponentPropDescription from '../ComponentProp/ComponentPropDescription' import ComponentPropName from '../ComponentProp/ComponentPropName' -export default class ComponentTableRow extends React.Component { +export default class ComponentPropsRow extends React.Component { static propTypes = { defaultValue: PropTypes.string, description: PropTypes.arrayOf(PropTypes.string), diff --git a/docs/src/components/ComponentDoc/ComponentPropsTable/ComponentPropsTable.tsx b/docs/src/components/ComponentDoc/ComponentPropsTable/ComponentPropsTable.tsx new file mode 100644 index 0000000000..4831c3ffcf --- /dev/null +++ b/docs/src/components/ComponentDoc/ComponentPropsTable/ComponentPropsTable.tsx @@ -0,0 +1,43 @@ +import * as _ from 'lodash' +import * as React from 'react' + +import ComponentPropsRow from './ComponentPropsRow' +import useComponentProps from 'docs/src/components/ComponentDoc/useComponentProps' + +const tableStyles: React.CSSProperties = { + textAlign: 'left', + borderCollapse: 'collapse', +} + +type ComponentPropsTable = { + componentName: string +} + +/** + * Displays a table of a Component's PropTypes. + * TODO: use Flex or a Table component, when it will be available + */ +const ComponentPropsTable: React.FunctionComponent = props => { + const componentProps = useComponentProps(props.componentName) + + return ( + + + + + + + + + + + + {_.map(componentProps, propDef => ( + + ))} + +
NameDefaultTypeDescription
+ ) +} + +export default ComponentPropsTable diff --git a/docs/src/components/ComponentDoc/ComponentPropsTable/index.tsx b/docs/src/components/ComponentDoc/ComponentPropsTable/index.tsx new file mode 100644 index 0000000000..eea7734082 --- /dev/null +++ b/docs/src/components/ComponentDoc/ComponentPropsTable/index.tsx @@ -0,0 +1 @@ +export { default } from './ComponentPropsTable' diff --git a/docs/src/components/ComponentDoc/ComponentTable/ComponentTable.tsx b/docs/src/components/ComponentDoc/ComponentTable/ComponentTable.tsx deleted file mode 100644 index ee05269e9b..0000000000 --- a/docs/src/components/ComponentDoc/ComponentTable/ComponentTable.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import * as _ from 'lodash' -import * as PropTypes from 'prop-types' -import * as React from 'react' - -import ComponentTableHeader from './ComponentTableHeader' -import ComponentTableRow from './ComponentTableRow' - -const tableStyles: React.CSSProperties = { - textAlign: 'left', - borderCollapse: 'collapse', -} -/** - * Displays a table of a Component's PropTypes. - * TODO: use Flex or a Table component, when it will be available - */ -const ComponentTable: any = ({ props }) => ( - - - - {_.map(props, propDef => ( - - ))} - -
-) - -ComponentTable.propTypes = { - props: PropTypes.arrayOf(PropTypes.object), -} - -export default ComponentTable diff --git a/docs/src/components/ComponentDoc/ComponentTable/ComponentTableHeader.tsx b/docs/src/components/ComponentDoc/ComponentTable/ComponentTableHeader.tsx deleted file mode 100644 index 5b1006f0a6..0000000000 --- a/docs/src/components/ComponentDoc/ComponentTable/ComponentTableHeader.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from 'react' - -// TODO: use Flex or a Table component, when it will be available -const ComponentTableHeader = () => ( - - - Name - Default - Type - Description - - -) - -const arePropsEqual = () => true - -export default React.memo(ComponentTableHeader, arePropsEqual) diff --git a/docs/src/components/ComponentDoc/ComponentTable/index.tsx b/docs/src/components/ComponentDoc/ComponentTable/index.tsx deleted file mode 100644 index 9f6975b102..0000000000 --- a/docs/src/components/ComponentDoc/ComponentTable/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ComponentTable' diff --git a/docs/src/components/ComponentDoc/useComponentProps.ts b/docs/src/components/ComponentDoc/useComponentProps.ts new file mode 100644 index 0000000000..16fe443998 --- /dev/null +++ b/docs/src/components/ComponentDoc/useComponentProps.ts @@ -0,0 +1,15 @@ +import componentInfoContext from 'docs/src/utils/componentInfoContext' + +const useComponentProps = (componentName: string) => { + const info = componentInfoContext.byDisplayName[componentName] + + if (!info) { + throw new Error( + `We don't have definitions for "${componentName}", please ensure that are generated by "gulp-docgen" plugin`, + ) + } + + return info.props +} + +export default useComponentProps From 69a701e0557f9a0a5cbd55085f3b24a013c477e3 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Thu, 25 Apr 2019 15:49:32 +0300 Subject: [PATCH 3/9] add FTZ components to docgen, to propsTable --- build/gulp/tasks/docs.ts | 7 ++- docs/src/views/FocusTrapZone.tsx | 73 +------------------------------- 2 files changed, 7 insertions(+), 73 deletions(-) diff --git a/build/gulp/tasks/docs.ts b/build/gulp/tasks/docs.ts index 31e7b4381a..63ee62cee8 100644 --- a/build/gulp/tasks/docs.ts +++ b/build/gulp/tasks/docs.ts @@ -68,11 +68,14 @@ task( ), ) -// ----------------------------------------s +// ---------------------------------------- // Build // ---------------------------------------- -const componentsSrc = [`${paths.posix.packageSrc('react')}/components/*/[A-Z]*.tsx`] +const componentsSrc = [ + `${paths.posix.packageSrc('react')}/components/*/[A-Z]*.tsx`, + `${paths.posix.packageSrc('react')}/lib/accessibility/FocusZone/[A-Z]!(*.types).tsx`, +] const behaviorSrc = [`${paths.posix.packageSrc('react')}/lib/accessibility/Behaviors/*/[a-z]*.ts`] const examplesIndexSrc = `${paths.posix.docsSrc()}/examples/*/*/*/index.tsx` const examplesSrc = `${paths.posix.docsSrc()}/examples/*/*/*/!(*index|.knobs).tsx` diff --git a/docs/src/views/FocusTrapZone.tsx b/docs/src/views/FocusTrapZone.tsx index e88fb3941e..b4b2b32242 100644 --- a/docs/src/views/FocusTrapZone.tsx +++ b/docs/src/views/FocusTrapZone.tsx @@ -5,6 +5,7 @@ import DocPage from '../components/DocPage' import { link, code } from '../utils/helpers' import CodeSnippet from '../components/CodeSnippet' +import ComponentPropsTable from 'docs/src/components/ComponentDoc/ComponentPropsTable' export default () => ( @@ -50,77 +51,7 @@ export default () => ( )} ):

-
    -
  • - as - element type the root element will use. Default is "div". -

    Type: {code('React.ReactType')}

    -
  • -
  • - className - additional class name to provide to the root element, in addition to the - ms-FocusZone class. -

    Type: {code('string')}

    -
  • -
  • - elementToFocusOnDismiss - sets the HTMLElement to focus on when exiting the - {code('FocusTrapZone')}. -

    - Default: The {code('target')} which triggered the {code('FocusTrapZone')}. -

    -

    Type: {code('React.ReactType')}

    -
  • -
  • - ariaLabelledBy - sets the "aria-labelledby" attribute. -

    Type: {code('string')}

    -
  • -
  • - isClickableOutsideFocusTrap - if true, allows clicks outside the{' '} - {code('FocusTrapZone')}.

    Default: {code('true')}

    -

    Type: {code('boolean')}

    -
  • -
  • - focusTriggerOnOutsideClick - indicates if the previously focused element outside - {code('FocusTrapZone')} should be focused on outside click. Note: trigger will be focused - when exiting FTZ using keyboard. If {code('isClickableOutsideFocusTrap')} ==={' '} - {code('false')},{code('focusTriggerOnOutsideClick')} will not be taken into account. -

    Default: {code('false')}

    -

    Type: {code('boolean')}

    -
  • -
  • - ignoreExternalFocusing - indicates if this Trap Zone will ignore keeping track of - HTMLElement that activated the Zone. -

    Default: {code('false')}

    -

    Type: {code('boolean')}

    -
  • -
  • - forceFocusInsideTrap - indicates whether focus trap zone should force focus inside - the zone when outside 'focus' event occurs. -

    Default: {code('false')}

    -

    Type: {code('boolean')}

    -
  • -
  • - disableFirstFocus - do not put focus onto first element when render focus trap zone. -

    Default: {code('false')}

    -

    Type: {code('boolean')}

    -
  • -
  • - focusPreviouslyFocusedInnerElement - specifies the algorithm used to determine which - descendant element to focus when focus() is called. -
    If false, the first focusable descendant, filtered by the firstFocusableSelector - property if present, is chosen. -
    If true, the element that was focused when the Trap Zone last had a focused - descendant is chosen. -
    If it has never had a focused descendant before, behavior falls back to the first - focused descendant. -

    Default: {code('false')}

    -

    Type: {code('boolean')}

    -
  • -
  • - firstFocusableSelector - indicates the selector for first focusable item. By default, - the first tabbable element will get focus. Only applies if - {code('focusPreviouslyFocusedInnerElement')} === {code('false')}. -

    Type: {code('string | (() => string)')}

    -
  • -
+
Override {code('FocusTrapZone')} settings

To be able to add/override {code('FocusTrapZone')} props already set for a component, it is From 1b2d39679bb436dac0e5532c494b8f7f86049295 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Thu, 2 May 2019 10:22:51 +0200 Subject: [PATCH 4/9] fix links, update docgen to filter interfaces --- build/gulp/plugins/gulp-react-docgen.ts | 4 +- build/gulp/plugins/util/getComponentInfo.ts | 25 ++- build/gulp/tasks/docs.ts | 2 +- docs/src/utils/helpers.tsx | 14 +- docs/src/views/AccessibilityBehaviors.tsx | 10 +- docs/src/views/AutoFocusZone.tsx | 13 +- docs/src/views/FocusZone.tsx | 210 +------------------- 7 files changed, 42 insertions(+), 236 deletions(-) diff --git a/build/gulp/plugins/gulp-react-docgen.ts b/build/gulp/plugins/gulp-react-docgen.ts index 548107f33f..abe429579b 100644 --- a/build/gulp/plugins/gulp-react-docgen.ts +++ b/build/gulp/plugins/gulp-react-docgen.ts @@ -7,7 +7,7 @@ import { getComponentInfo } from './util' const pluginName = 'gulp-react-docgen' -export default () => +export default (ignoredInterfaces: string[] = []) => through2.obj(function bufferContents(file, enc, cb) { if (file.isNull()) { cb(null, file) @@ -21,7 +21,7 @@ export default () => try { const infoFilename = file.basename.replace(/\.tsx$/, '.info.json') - const contents = getComponentInfo(file.path) + const contents = getComponentInfo(file.path, ignoredInterfaces) const infoFile = new Vinyl({ path: `./${infoFilename}`, diff --git a/build/gulp/plugins/util/getComponentInfo.ts b/build/gulp/plugins/util/getComponentInfo.ts index 5584abba02..07097c721c 100644 --- a/build/gulp/plugins/util/getComponentInfo.ts +++ b/build/gulp/plugins/util/getComponentInfo.ts @@ -12,7 +12,7 @@ interface BehaviorInfo { category: string } -const getComponentInfo = (filepath: string) => { +const getComponentInfo = (filepath: string, ignoredParentInterfaces: string[]) => { const absPath = path.resolve(process.cwd(), filepath) const dir = path.dirname(absPath) @@ -99,14 +99,21 @@ const getComponentInfo = (filepath: string) => { const { description, tags } = parseDocblock(propDef.description) const { name, value } = parseType(propName, propDef) - info.props[propName] = { - ...propDef, - description, - tags, - value, - defaultValue: parseDefaultValue(propDef), - name: propName, - type: name, + const parentInterface = _.get(propDef, 'parent.name') + const ignoredInDefinitions = _.includes(ignoredParentInterfaces, parentInterface) + + if (!ignoredInDefinitions) { + info.props[propName] = { + ...propDef, + description, + tags, + value, + defaultValue: parseDefaultValue(propDef), + name: propName, + type: name, + } + } else { + delete info.props[propName] } }) diff --git a/build/gulp/tasks/docs.ts b/build/gulp/tasks/docs.ts index 63ee62cee8..491889e85f 100644 --- a/build/gulp/tasks/docs.ts +++ b/build/gulp/tasks/docs.ts @@ -91,7 +91,7 @@ const markdownSrc = [ task('build:docs:component-info', () => src(componentsSrc, { since: lastRun('build:docs:component-info') }) .pipe( - cache(gulpReactDocgen(), { + cache(gulpReactDocgen(['DOMAttributes', 'HTMLAttributes']), { name: 'componentInfo', }), ) diff --git a/docs/src/utils/helpers.tsx b/docs/src/utils/helpers.tsx index be42bdf57d..7615aa79c8 100644 --- a/docs/src/utils/helpers.tsx +++ b/docs/src/utils/helpers.tsx @@ -1,10 +1,14 @@ import * as React from 'react' +import { Link } from 'react-router-dom' import { Icon } from '@stardust-ui/react' export const code = value => {value} -export const link = (content, href, isExternal = false) => ( - - {content} {isExternal ? : ''} - -) +export const link = (content, href, isExternal = false) => + isExternal ? ( + + {content} {isExternal ? : ''} + + ) : ( + {content} + ) diff --git a/docs/src/views/AccessibilityBehaviors.tsx b/docs/src/views/AccessibilityBehaviors.tsx index a6bcbbfeef..be59f6494b 100644 --- a/docs/src/views/AccessibilityBehaviors.tsx +++ b/docs/src/views/AccessibilityBehaviors.tsx @@ -236,17 +236,17 @@ export default () => ( {link( 'GitHub', 'https://github.com/stardust-ui/react/tree/master/packages/react/src/lib/accessibility/Behaviors', + true, )} .

The default and other available behaviors for all the components can be found in the{' '} - {link('documentation', 'https://stardust-ui.github.io/react/')}, together with notes on other - accessibility considerations for using the component. The examples show the recommended way of - using the components in different variations. It is possible to edit example's code, see the - rendered HTML, change themes and validate the rendering in RTL scenario, or with different - behaviors. + {link('documentation', '/')}, together with notes on other accessibility considerations for + using the component. The examples show the recommended way of using the components in + different variations. It is possible to edit example's code, see the rendered HTML, change + themes and validate the rendering in RTL scenario, or with different behaviors.

diff --git a/docs/src/views/AutoFocusZone.tsx b/docs/src/views/AutoFocusZone.tsx index 0527ba2794..102f04e117 100644 --- a/docs/src/views/AutoFocusZone.tsx +++ b/docs/src/views/AutoFocusZone.tsx @@ -4,6 +4,7 @@ import { Link } from 'react-router-dom' import DocPage from '../components/DocPage' import { code, link } from '../utils/helpers' import CodeSnippet from '../components/CodeSnippet' +import ComponentPropsTable from 'docs/src/components/ComponentDoc/ComponentPropsTable' export default () => ( @@ -43,17 +44,7 @@ export default () => ( )} ):

-
    -
  • - as - element type the root element will use. Default is "div". -

    Type: {code('React.ReactType')}

    -
  • -
  • - firstFocusableSelector - indicates the selector for first focusable item. By default, - the first tabbable element will get focus. -

    Type: {code('string | (() => string)')}

    -
  • -
+
Override {code('AutoFocusZone')} settings

To be able to add/override {code('AutoFocusZone')} props already set for a component, it is diff --git a/docs/src/views/FocusZone.tsx b/docs/src/views/FocusZone.tsx index d57be93898..1ab709b496 100644 --- a/docs/src/views/FocusZone.tsx +++ b/docs/src/views/FocusZone.tsx @@ -2,6 +2,7 @@ import * as React from 'react' import { Link } from 'react-router-dom' import { Header } from '@stardust-ui/react' +import ComponentPropsTable from 'docs/src/components/ComponentDoc/ComponentPropsTable' import CodeSnippet from '../components/CodeSnippet' import DocPage from '../components/DocPage' import { code, link } from '../utils/helpers' @@ -40,7 +41,11 @@ export default () => (

  • Focus the next or previous element after pressing a navigation key
  • The last focused element within the zone is identified by using{' '} - {link('Roving tabindex', 'https://www.w3.org/TR/wai-aria-practices/#kbd_roving_tabindex')} + {link( + 'Roving tabindex', + 'https://www.w3.org/TR/wai-aria-practices/#kbd_roving_tabindex', + true, + )}
  • @@ -128,208 +133,7 @@ export default () => ( )} ):

    -
      -
    • - as - element type the root element will use. Default is "div". Only applies to - {code('FocusZone')}'s container in {code('Wrap')} mode. -

      Type: {code('React.ReactType')}

      -
    • -
    • - className - additional class name to provide to the root element, in addition to the - ms-FocusZone class. -

      Type: {code('string')}

      -
    • -
    • - direction - defines which arrows to react to. -

      Type: {code('FocusZoneDirection')}, enum with next options:

      -
        -
      • - horizontal - navigation between items can be made using left/right arrow keys. -

        See an example of {link('horizontal menu', 'components/menu#types-menu')}.

        -
      • -
      • - vertical - navigation between items can be made using up/down arrow keys. -

        - See an example of{' '} - {link('vertical menu', 'components/menu#variations-vertical-pointing')}. -

        -
      • -

        - Vertical and horizontal menu share the same accessibility behavior and direction is - defined by the Menu's prop "vertical": -

        - ({ - //... - focusZone: { - mode: FocusZoneMode.Embed, - props: { - direction: props.vertical ? - FocusZoneDirection.vertical : - FocusZoneDirection.horizontal, - //... - }, - }, - }) - `} - /> -
      • - bidirectional - navigation between items can be made using all arrow keys.{' '} - It is set by default. -

        See an example of {link('tab list', 'components/menu#usages-tab-list')}.

        -
      • -
      -
    • -
    • - defaultTabbableElement - function which uses root element as parameter to return the - initial active element. For example, when there is a chat with a bottom-up approach, it is - expected that the last chat message is tabbable (active), not the first default one. -

      Type: {code('(root: HTMLElement) => HTMLElement')}

      - ({ - //... - focusZone: { - //... - props: { - defaultTabbableElement: (root: HTMLElement) => { - return root.querySelector('[data-last-visible="true"]'); - }, - }, - }, - }) - `} - /> -
    • -
    • - shouldFocusOnMount - if a default tabbable element should be force focused on - FocusZone mount. -

      Default: {code('false')}

      -

      Type: {code('boolean')}

      -
    • -
    • - shouldFocusInnerElementWhenReceivedFocus - if true and {code('FocusZone')}'s root - element (container) receives focus, the focus will land either on the defaultTabbableElement - (if set) or on the first tabbable element of this {code('FocusZone')}. Usually a case for - nested focus zones, when nested focus zone's container is a focusable element. -

      Default: {code('false')}

      -

      Type: {code('boolean')}

      -
    • -
    • - shouldResetActiveElementWhenTabFromZone - if true and {code('TAB')} key is not - handled by - {code('FocusZone')}, resets current active element to null value. For example, when roving - index is not desirable and focus should always reset to the default tabbable element. -

      Default: {code('false')}

      -

      Type: {code('boolean')}

      -
    • -
    • - disabled - if set, the {code('FocusZone')} will not be tabbable and keyboard - navigation will be disabled. This does not affect disabled attribute of any child. -

      Default: {code('false')}

      -

      Type: {code('boolean')}

      -
    • -
    • - isRtl - if true, {code('FocusZone')} behavior will change to match RTL environments - (left/right arrows switched). -

      Default: {code('false')}

      -

      Type: {code('boolean')}

      -
    • -
    • - isCircularNavigation - if true, will cycle to the beginning of the targets once the - user attempts to navigate past the last target while at the end, and to the end when the - user attempts to naviagate before the first target. -

      Default: {code('false')}

      -

      Type: {code('boolean')}

      -

      For example, {link('horizontal menu', 'components/menu#types-menu')}.

      -
    • -
    • - shouldEnterInnerZone - callback function that will be executed, will be executed on - keypresses to determine if the user intends to navigate into the inner (nested) zone. - Returning true will ask the first inner zone to set focus. For example, when chat container - is focus zone and chat messages are inner focus zones. Navigation between messages possible - with up/down arrow keys, but when pressing Enter, focus should go to focusable elements - inside message, for example, a link. -

      Default: {code('false')}

      -

      Type: {code('(ev: React.KeyboardEvent) => boolean')}

      - --->FocusZone - ---> inner FocusZone - ---> inner FocusZone - ---> inner FocusZone - - `} - /> - ({ - //... - focusZone: { - //... - props: { - shouldEnterInnerZone: event => keyboardKey.getCode(event) === keyboardKey.Enter, - }, - }, - }) - `} - /> -
    • -
    • - onActiveElementChanged - callback for when one of immediate children elements gets - activated by getting focused or by having one of its respective children elements focused. -

      Type: {code('(element?: HTMLElement, ev?: React.FocusEvent) => void')}

      -
    • -
    • - shouldReceiveFocus - callback method for determining if focus should indeed be set on - the given element. -

      Type: {code('(childElement?: HTMLElement) => boolean')}

      -
    • -
    • - handleTabKey - allows {code('TAB')} key to be handled, thus alows tabbing through a - focusable list of items in the focus zone. A side effect is that users will not be able to{' '} - {code('TAB')} out of the focus zone and have to hit escape or some other key to exit focus - zone. -

      Type: {code('FocusZoneTabbableElements')}, enum with next options:

      -
        -
      • - none - tabbing is not allowed -
      • -
      • - all - all tabbing action is allowed -
      • -
      • - inputOnly - tabbing is allowed only on input elements -
      • -
      -
    • -
    • - shouldInputLoseFocusOnArrowKey - a callback method to determine if the input element - should lose focus on arrow keys. For example, when arrow keys are pressed to navigate when - an input element is empty or when cursor is at the beginning/end of a string. -

      Type: {code('(inputElement: HTMLInputElement) => boolean')}

      -
    • -
    • - stopFocusPropagation - if true, focus event propagation will be stopped. -

      Default: {code('false')}

      -

      Type: {code('boolean')}

      -
    • -
    • - onFocus - callback called when "focus" event triggered in {code('FocusZone')}. -

      Type: {code('(event: React.FocusEvent) => void')}

      -
    • -
    • - preventDefaultWhenHandled - if true, {code('FocusZone')} prevents default behavior - when handled a key event. -

      Default: {code('false')}

      -

      Type: {code('boolean')}

      -
    • -
    +
    Override {code('FocusZone')} settings

    To be able to add/override Focus Zone settings already set for a component, it is needed to From 861b0e17efb1bea267087fb1425ce3a81c4fea09 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Thu, 2 May 2019 10:30:09 +0200 Subject: [PATCH 5/9] fix ignore --- build/gulp/plugins/gulp-component-menu.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/gulp/plugins/gulp-component-menu.ts b/build/gulp/plugins/gulp-component-menu.ts index d9becf8af7..86b5e5d07a 100644 --- a/build/gulp/plugins/gulp-component-menu.ts +++ b/build/gulp/plugins/gulp-component-menu.ts @@ -39,7 +39,7 @@ export default () => { const jsonInfo = fs.readFileSync(infoFilePath) componentInfo = JSON.parse(jsonInfo.toString()) } else { - componentInfo = getComponentInfo(file.path) + componentInfo = getComponentInfo(file.path, []) } if (componentInfo.isParent) { From b70a1d5f43dabd9e79183058f84a0af950f5e145 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Thu, 2 May 2019 15:45:10 +0200 Subject: [PATCH 6/9] revert link() changes --- docs/src/utils/helpers.tsx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/docs/src/utils/helpers.tsx b/docs/src/utils/helpers.tsx index 7615aa79c8..be42bdf57d 100644 --- a/docs/src/utils/helpers.tsx +++ b/docs/src/utils/helpers.tsx @@ -1,14 +1,10 @@ import * as React from 'react' -import { Link } from 'react-router-dom' import { Icon } from '@stardust-ui/react' export const code = value => {value} -export const link = (content, href, isExternal = false) => - isExternal ? ( - - {content} {isExternal ? : ''} - - ) : ( - {content} - ) +export const link = (content, href, isExternal = false) => ( + + {content} {isExternal ? : ''} + +) From 72ec8c8c0590f3de05fc847be400e9e9308ccc14 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Thu, 2 May 2019 15:46:42 +0200 Subject: [PATCH 7/9] invert condition --- build/gulp/plugins/util/getComponentInfo.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/gulp/plugins/util/getComponentInfo.ts b/build/gulp/plugins/util/getComponentInfo.ts index 07097c721c..65d0bd84a8 100644 --- a/build/gulp/plugins/util/getComponentInfo.ts +++ b/build/gulp/plugins/util/getComponentInfo.ts @@ -100,9 +100,9 @@ const getComponentInfo = (filepath: string, ignoredParentInterfaces: string[]) = const { name, value } = parseType(propName, propDef) const parentInterface = _.get(propDef, 'parent.name') - const ignoredInDefinitions = _.includes(ignoredParentInterfaces, parentInterface) + const visibleInDefinition = !_.includes(ignoredParentInterfaces, parentInterface) - if (!ignoredInDefinitions) { + if (visibleInDefinition) { info.props[propName] = { ...propDef, description, From c43a6f7db7fae7c161c519d1c3b655f1a8f8d0d0 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Thu, 2 May 2019 15:47:46 +0200 Subject: [PATCH 8/9] update error contents --- docs/src/components/ComponentDoc/useComponentProps.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/components/ComponentDoc/useComponentProps.ts b/docs/src/components/ComponentDoc/useComponentProps.ts index 16fe443998..759b2ee980 100644 --- a/docs/src/components/ComponentDoc/useComponentProps.ts +++ b/docs/src/components/ComponentDoc/useComponentProps.ts @@ -5,7 +5,7 @@ const useComponentProps = (componentName: string) => { if (!info) { throw new Error( - `We don't have definitions for "${componentName}", please ensure that are generated by "gulp-docgen" plugin`, + `We don't have definitions for "${componentName}". Please ensure those are generated by "gulp-docgen" plugin`, ) } From 1fc3f7b16566ff902af86b12527d636ab3f11005 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Thu, 2 May 2019 15:51:07 +0200 Subject: [PATCH 9/9] restore link stuff --- docs/src/views/AccessibilityBehaviors.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/src/views/AccessibilityBehaviors.tsx b/docs/src/views/AccessibilityBehaviors.tsx index be59f6494b..34e1f9ba9b 100644 --- a/docs/src/views/AccessibilityBehaviors.tsx +++ b/docs/src/views/AccessibilityBehaviors.tsx @@ -243,10 +243,11 @@ export default () => (

    The default and other available behaviors for all the components can be found in the{' '} - {link('documentation', '/')}, together with notes on other accessibility considerations for - using the component. The examples show the recommended way of using the components in - different variations. It is possible to edit example's code, see the rendered HTML, change - themes and validate the rendering in RTL scenario, or with different behaviors. + {link('documentation', 'https://stardust-ui.github.io/react/')}, together with notes on other + accessibility considerations for using the component. The examples show the recommended way of + using the components in different variations. It is possible to edit example's code, see the + rendered HTML, change themes and validate the rendering in RTL scenario, or with different + behaviors.