Skip to content

Commit

Permalink
feat(action-bar)!: Refactor ActionBar component (#1396)
Browse files Browse the repository at this point in the history
- Convert to compound component.
- Remove `ChildrenContainer` and replace it styles to `ActionBarContainer`.
- Update documentation
- Add jest component test and visual tests for storybook.

Resolves: #1382

[category:Components]

### BREAKING CHANGES
Change `fixed` prop from component to `position` to set container position (`fixed` position has been set as default).
  • Loading branch information
RayRedGoose committed Jan 11, 2022
1 parent 0e355f8 commit b7fc2fe
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 173 deletions.
86 changes: 86 additions & 0 deletions modules/codemod/lib/v7/compoundActionBar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {API, FileInfo, Options, JSXElement, ImportDeclaration, ASTPath} from 'jscodeshift';
import {getImportRenameMap} from './utils/getImportRenameMap';

const mainPackage = '@workday/canvas-kit-react';
const actionBarPackage = '@workday/canvas-kit-react/action-bar';

export default function transformer(file: FileInfo, api: API, options: Options) {
const j = api.jscodeshift;

const root = j(file.source);

let hasActionBarImports = false;

// This toggles the failsafe that prevents us from accidentally transforming something unintentionally.
root.find(j.ImportDeclaration, (nodePath: ImportDeclaration) => {
const {value} = nodePath.source;
// If there's an import from the action-bar package, set the import boolean check to true
if (value === actionBarPackage) {
hasActionBarImports = true;
return;
}
// If there's an import from the main package, check to see if ActionBar is among the named imports
// e.g. import ActionBar from '@workday/canvas-kit-react';
if (value === mainPackage) {
(nodePath.specifiers || []).forEach(specifier => {
if (specifier.type === 'ImportSpecifier') {
const specifierName = specifier.imported.name;
if (specifierName === 'ActionBar') {
hasActionBarImports = true;
}
}
});
}
});

// Failsafe to skip transforms unless a ActionBar import is detected
if (!hasActionBarImports) {
return root.toSource();
}

// Replace default import with named or renamed import
root.find(j.ImportDefaultSpecifier).forEach(nodePath => {
const parent = nodePath.parent as ASTPath<ImportDeclaration>;
const importSource = String(parent.node.source.value) as typeof actionBarPackage;
const localName = nodePath.value.local?.name;
if (actionBarPackage === importSource && localName) {
nodePath.replace(j.importSpecifier(j.identifier('ActionBar'), j.identifier(localName)));
}
});

const {containsCanvasImports, importMap, styledMap} = getImportRenameMap(
j,
root,
'@workday/canvas-kit-react/action-bar'
);

if (!containsCanvasImports) {
return file.source;
}

// Remove fixed prop from ActionBar component
root
.find(
j.JSXElement,
(value: JSXElement) =>
value.openingElement.name.type === 'JSXIdentifier' &&
(value.openingElement.name.name === importMap.ActionBar ||
value.openingElement.name.name === styledMap.ActionBar)
)
.forEach(nodePath => {
const attributes = nodePath.value.openingElement.attributes;

if (attributes) {
// remove these attributes from ActionBar
nodePath.value.openingElement.attributes = attributes.filter(item =>
item.type === 'JSXAttribute' &&
item.name.type === 'JSXIdentifier' &&
['fixed'].includes(item.name.name)
? false
: true
);
}
});

return root.toSource();
}
92 changes: 92 additions & 0 deletions modules/codemod/lib/v7/spec/compoundActionBar.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import {expectTransformFactory} from './expectTransformFactory';
import transform from '../compoundActionBar';
import {stripIndent} from 'common-tags';

const expectTransform = expectTransformFactory(transform);

describe('ActionBar', () => {
it('should ignore non-canvas-kit imports', () => {
const input = `import ActionBar from '@workday/some-other-library'`;
const expected = `import ActionBar from '@workday/some-other-library'`;

expectTransform(input, expected);
});

it('should replace default import', () => {
const input = `import ActionBar from '@workday/canvas-kit-react/action-bar';`;
const expected = `import { ActionBar } from '@workday/canvas-kit-react/action-bar';`;

expectTransform(input, expected);
});

it('should replace default import with a renamed export', () => {
const input = `import ButtonBar from '@workday/canvas-kit-react/action-bar';`;
const expected = `import { ActionBar as ButtonBar } from '@workday/canvas-kit-react/action-bar';`;

expectTransform(input, expected);
});

it('should ignore ActionBar from other packages', () => {
const input = stripIndent`
import {ActionBar} from '@workday/some-other-library'
<ActionBar fixed="true" />
`;

const expected = stripIndent`
import {ActionBar} from '@workday/some-other-library'
<ActionBar fixed="true" />
`;

expectTransform(input, expected);
});

it('should remove fixed prop from ActionBar component', () => {
const input = stripIndent`
import {ActionBar} from '@workday/canvas-kit-react/action-bar';
<ActionBar fixed="true" />
`;

const expected = stripIndent`
import {ActionBar} from '@workday/canvas-kit-react/action-bar';
<ActionBar />
`;

expectTransform(input, expected);
});

it('should remove fixed prop from ActionBar component imported from main package', () => {
const input = stripIndent`
import {ActionBar} from '@workday/canvas-kit-react';
<ActionBar fixed="true" />
`;

const expected = stripIndent`
import {ActionBar} from '@workday/canvas-kit-react';
<ActionBar />
`;

expectTransform(input, expected);
});

it('should replace default import and remove fixed prop', () => {
const input = stripIndent`
import ActionBar from '@workday/canvas-kit-react/action-bar';
<ActionBar fixed="true" />
`;

const expected = stripIndent`
import { ActionBar } from '@workday/canvas-kit-react/action-bar';
<ActionBar />
`;

expectTransform(input, expected);
});
});
14 changes: 14 additions & 0 deletions modules/docs/mdx/7.0-MIGRATION-GUIDE.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Below are the breaking changes made in Canvas Kit v7. Please
any questions about the update.

- [Codemod](#codemod)
- [ActionBar Component Updates](#actionbar-component-updates)

## Codemod

Expand All @@ -35,3 +36,16 @@ rollback more easily if necessary.**
[Let us know](https://github.com/Workday/canvas-kit/issues/new?labels=bug&template=bug.md) if you
encounter any issues or use cases that we've missed. The `@workday/canvas-kit-codemod` package will
help us maintain additional codemod transforms to make future migrations easier.

## ActionBar Component Updates

We've refactored our ActionBar components to simplify logic.

PR: https://github.com/Workday/canvas-kit/pull/1396

**Changes:**

- Component has been converted to compound component.
- `fixed` prop has been removed from component.
- Now ActionBar uses `position` prop to set container position instead `fixed` prop.
- Fixed position has been set as default if there is not passed `position` prop.
57 changes: 4 additions & 53 deletions modules/react/action-bar/README.md
Original file line number Diff line number Diff line change
@@ -1,55 +1,6 @@
# Canvas Kit Action Bar
# Canvas Kit React Action Bar

Full width toolbar fixed to bottom of screen.
`ActionBar` contains primary and secondary actions related to a page or task.

Although not required, [buttons](../../button/react) are often used in in action bars. The primary
action button should be left aligned followed by secondary buttons. The primary button is on the
right only in task orchestration and on mobile devices.

## Installation

```sh
yarn add @workday/canvas-kit-react
```

## Usage

```tsx
import * as React from 'react';
import {PrimaryButton, SecondaryButton} from '@workday/canvas-kit-react/button';
import {ActionBar} from '@workday/canvas-kit-react/action-bar';

<ActionBar>
<PrimaryButton variant="primary">Button</PrimaryButton>
<SecondaryButton>Button</SecondaryButton>
<SecondaryButton>Button</SecondaryButton>
</ActionBar>;
```

## Static Properties

> None
## Component Props

### Required

> None
### Optional

#### `fixed: boolean`

> Fixes the toolbar to the bottom of the window (uses `position: fixed`)
## Responsive Behavior

At 575px, responsive styles will take effect:

- Applies a flex box to the buttons
- Makes single-button groups full width
- All buttons will become the same width (`flex: 1`).
- Button order will become reversed, making left-aligned primary buttons right-aligned.

> When on a mobile form factor, the button placement should flip to have the primary button on the
> far right.
For more detailed information on this component, please refer to the
[storybook documentation](https://workday.github.io/canvas-kit/?path=/docs/components-buttons-action-bar-react--basic)
4 changes: 0 additions & 4 deletions modules/react/action-bar/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
import ActionBar from './lib/ActionBar';

export default ActionBar;
export {ActionBar};
export * from './lib/ActionBar';
97 changes: 29 additions & 68 deletions modules/react/action-bar/lib/ActionBar.tsx
Original file line number Diff line number Diff line change
@@ -1,76 +1,37 @@
import * as React from 'react';
import {styled} from '@workday/canvas-kit-react/common';
import {colors, commonColors, space, CSSProperties} from '@workday/canvas-kit-react/tokens';
import {createComponent, styled, StyledType} from '@workday/canvas-kit-react/common';
import {commonColors, colors, space} from '@workday/canvas-kit-react/tokens';
import {HStack, HStackProps, StackSpacing} from '@workday/canvas-kit-labs-react';

export interface ActionBarProps extends React.HTMLAttributes<HTMLDivElement> {
/**
* If true, fix the ActionBar to the bottom of the screen.
* @default false
*/
fixed?: boolean;
interface ActionBarProps extends Omit<HStackProps, 'spacing'> {
spacing?: StackSpacing;
}

function getFixedStyles(fixed = false): CSSProperties {
return fixed
? {
position: 'fixed',
left: 0,
bottom: 0,
right: 0,
}
: {};
}

const ActionBarContainer = styled('div')<ActionBarProps>(
{
borderTop: `solid 1px ${colors.soap400}`,
background: commonColors.background,
const ResponsiveHStack = styled(HStack)<HStackProps & StyledType>(({theme}) => ({
[theme.canvas.breakpoints.down('s')]: {
padding: space.s,
boxShadow: '0 -2px 4px rgba(0, 0, 0, 0.08)',
},
({fixed, theme}) => {
return {
...getFixedStyles(fixed),
[theme.canvas.breakpoints.down('s')]: {
padding: space.xxs,
},
};
}
);

const ChildrenContainer = styled('div')(
{
display: 'inline-block',
padding: `0 ${space.m}`,
'*:not(:first-of-type)': {
marginLeft: space.s,
'> *:not(div)': {
flex: 1,
},
},
({theme}) => ({
[theme.canvas.breakpoints.down('s')]: {
display: 'flex',
padding: space.xxs,
justifyContent: 'center',
flexDirection: 'row-reverse',
'> *': {
flex: 1,
'&:not(:first-of-type)': {
marginRight: space.s,
marginLeft: 0,
},
},
},
})
);

export default class ActionBar extends React.Component<ActionBarProps> {
public render() {
const {fixed, children, ...elemProps} = this.props;
}));

return (
<ActionBarContainer {...elemProps} fixed={fixed}>
<ChildrenContainer>{children}</ChildrenContainer>
</ActionBarContainer>
);
}
}
export const ActionBar = createComponent('div')({
displayName: 'ActionBar',
Component: (elemProps: ActionBarProps, ref, Element) => (
<ResponsiveHStack
ref={ref}
as={Element}
spacing="s"
depth={1}
background={commonColors.background}
borderTop={`solid 1px ${colors.soap400}`}
padding={`${space.s} ${space.xl} `}
position="fixed"
bottom={0}
left={0}
right={0}
{...elemProps}
/>
),
});
Loading

0 comments on commit b7fc2fe

Please sign in to comment.