Skip to content

Commit

Permalink
Button: Migrate react-button to new DX (microsoft#18607)
Browse files Browse the repository at this point in the history
#### Pull request checklist

- [x] Addresses an existing issue: Fixes part of microsoft#18579
- [x] Include a change request file using `$ yarn change`

#### Description of changes
- migrates `react-button` package using `yarn nx workspace-generator migrate-converged-pkg`
- moved `Playground.tsx` and `Playground.types.tsx` to `react-button` package as they were deleted by migration script.
- updates `useCompountButtonStyles` to explicitly declare `marginTop` value to fix babel-loader error.
- extracted `buttonBaseProps` from `Button` story to its own file.
- updated import paths for all Button stories.
  • Loading branch information
TristanWatanabe authored and PeterDraex committed Aug 6, 2021
1 parent b771790 commit 97c1a55
Show file tree
Hide file tree
Showing 23 changed files with 258 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Button: Migrate to new dx",
"packageName": "@fluentui/react-button",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Button: Migrate to new dx",
"packageName": "@fluentui/react-examples",
"email": "[email protected]",
"dependentChangeType": "patch"
}
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ module.exports = {
'<rootDir>/packages/react-utilities',
'<rootDir>/packages/react-theme',
'<rootDir>/packages/react-badge',
'<rootDir>/packages/react-button',
'<rootDir>/packages/keyboard-keys',
'<rootDir>/packages/react-slider',
'<rootDir>/packages/make-styles-webpack-loader',
Expand Down
2 changes: 1 addition & 1 deletion nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"@fluentui/react-accordion": { "tags": ["vNext"], "implicitDependencies": [] },
"@fluentui/react-avatar": { "tags": ["vNext", "platform:web"], "implicitDependencies": [] },
"@fluentui/react-badge": { "tags": ["vNext", "platform:web"], "implicitDependencies": [] },
"@fluentui/react-button": { "implicitDependencies": [] },
"@fluentui/react-button": { "tags": ["vNext", "platform:web"], "implicitDependencies": [] },
"@fluentui/react-card": { "implicitDependencies": [] },
"@fluentui/react-cards": { "implicitDependencies": [] },
"@fluentui/react-charting": { "implicitDependencies": [] },
Expand Down
11 changes: 11 additions & 0 deletions packages/react-button/.storybook/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const rootMain = require('../../../.storybook/main');

module.exports = /** @type {Pick<import('../../../.storybook/main').StorybookConfig,'addons'|'stories'|'webpackFinal'>} */ ({
stories: [...rootMain.stories, '../src/**/*.stories.mdx', '../src/**/*.stories.@(ts|tsx)'],
addons: [...rootMain.addons],
webpackFinal: (config, options) => {
const localConfig = { ...rootMain.webpackFinal(config, options) };

return localConfig;
},
});
3 changes: 3 additions & 0 deletions packages/react-button/.storybook/preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as rootPreview from '../../../.storybook/preview';

export const decorators = [...rootPreview.decorators];
9 changes: 9 additions & 0 deletions packages/react-button/.storybook/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true
},
"exclude": ["../**/*.test.ts", "../**/*.test.js", "../**/*.test.tsx", "../**/*.test.jsx"],
"include": ["../src/**/*", "*.js"]
}
5 changes: 5 additions & 0 deletions packages/react-button/config/api-extractor.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"extends": "./api-extractor.json",
"mainEntryPointFilePath": "<projectFolder>/dist/<unscopedPackageName>/src/index.d.ts"
}
26 changes: 19 additions & 7 deletions packages/react-button/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
const { createConfig } = require('@fluentui/scripts/jest/jest-resources');
const path = require('path');
// @ts-check

const config = createConfig({
setupFiles: [path.resolve(path.join(__dirname, 'config', 'tests.js'))],
/**
* @type {jest.InitialOptions}
*/
module.exports = {
displayName: 'react-button',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsConfig: '<rootDir>/tsconfig.json',
diagnostics: false,
},
},
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
coverageDirectory: './coverage',
setupFilesAfterEnv: ['./config/tests.js'],
snapshotSerializers: ['@fluentui/jest-serializer-make-styles'],
});

module.exports = config;
};
9 changes: 5 additions & 4 deletions packages/react-button/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@
"code-style": "just-scripts code-style",
"just": "just-scripts",
"lint": "just-scripts lint",
"start": "just-scripts dev:storybook",
"start-test": "just-scripts jest-watch",
"test": "just-scripts test",
"update-snapshots": "just-scripts jest -u"
"start": "yarn storybook",
"test": "jest",
"docs": "api-extractor run --config=config/api-extractor.local.json --local",
"build:local": "tsc -p . --module esnext --emitDeclarationOnly && node ../../scripts/typescript/normalize-import --output dist/packages/react-button/src && yarn docs",
"storybook": "start-storybook"
},
"devDependencies": {
"@fluentui/a11y-testing": "^0.1.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as React from 'react';
import { Button, ButtonProps } from '@fluentui/react-button';
import { Playground } from '../Playground';
import { PlaygroundProps, PropDefinition } from '../Playground.types';
import { Button, ButtonProps } from './Button';
import { Playground } from './Playground.stories';
import { PlaygroundProps } from './Playground.types.stories';
import { buttonBaseProps } from './buttonBaseProps.stories';

// TODO: this is here while waiting for react-icons to merge
const SVGIcon = () => (
Expand Down Expand Up @@ -101,29 +102,6 @@ export const Disabled = () => (
</>
);

type ExampleProps = { iconOnly?: string };

export const buttonBaseProps: PropDefinition<ButtonProps & ExampleProps>[] = [
{ propName: 'block', propType: 'boolean' },
{ propName: 'circular', propType: 'boolean' },
{ propName: 'content', propType: 'string', defaultValue: 'This is a button', dependsOnProps: ['~iconOnly'] },
{ propName: 'disabled', propType: 'boolean', dependsOnProps: ['~disabledFocusable'] },
{ propName: 'disabledFocusable', propType: 'boolean', dependsOnProps: ['~disabled'] },
{ propName: 'icon', propType: 'boolean' },
{ propName: 'iconOnly', propType: 'boolean', dependsOnProps: ['icon'] },
{
propName: 'iconPosition',
propType: ['before', 'after'],
defaultValue: 'before',
dependsOnProps: ['icon', '~iconOnly'],
},
{ propName: 'outline', propType: 'boolean', dependsOnProps: ['~primary', '~subtle', '~transparent'] },
{ propName: 'primary', propType: 'boolean', dependsOnProps: ['~outline', '~subtle', '~transparent'] },
{ propName: 'size', propType: ['small', 'medium', 'large'], defaultValue: 'medium' },
{ propName: 'subtle', propType: 'boolean', dependsOnProps: ['~outline', '~primary', '~transparent'] },
{ propName: 'transparent', propType: 'boolean', dependsOnProps: ['~outline', '~primary', '~subtle'] },
];

const buttonProps: PlaygroundProps<ButtonProps>['sections'] = [
{ sectionName: 'Button props', propList: buttonBaseProps },
];
Expand All @@ -133,3 +111,8 @@ export const ButtonPlayground = () => (
<Button />
</Playground>
);

export default {
title: 'Components/Button',
component: Button,
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as React from 'react';
import { CompoundButton, CompoundButtonProps } from '@fluentui/react-button';
import { buttonBaseProps } from '../Button/Button.stories';
import { Playground } from '../Playground';
import { PlaygroundProps, PropDefinition } from '../Playground.types';
import { CompoundButton, CompoundButtonProps } from './CompoundButton';
import { Playground } from './Playground.stories';
import { PlaygroundProps, PropDefinition } from './Playground.types.stories';
import { buttonBaseProps } from './buttonBaseProps.stories';

type ExampleProps = { iconOnly?: string };

Expand All @@ -25,3 +25,8 @@ export const CompoundButtonPlayground = () => (
<CompoundButton />
</Playground>
);

export default {
title: 'Components/CompoundButton',
component: CompoundButton,
};
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import * as React from 'react';
import { MenuButton, MenuButtonProps } from '@fluentui/react-button';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import { Menu, MenuItem, MenuList, MenuProps, MenuTrigger } from '@fluentui/react-menu';
import { buttonBaseProps } from '../Button/Button.stories';
import { Playground } from '../Playground';
import { PlaygroundProps } from '../Playground.types';
import { MenuButton, MenuButtonProps } from './MenuButton';
import { Playground } from './Playground.stories';
import { PlaygroundProps } from './Playground.types.stories';
import { buttonBaseProps } from './buttonBaseProps.stories';

const ExampleMenu = (props: MenuButtonProps): JSX.Element => (
<Menu>
Expand All @@ -29,3 +31,8 @@ export const MenuButtonPlayground = () => {
</Playground>
);
};

export default {
title: 'Components/MenuButton',
component: MenuButton,
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import { Checkbox, Dropdown, IDropdownOption, Stack, Text, TextField } from '@fluentui/react';
import { PlaygroundProps } from './Playground.types';
import { Checkbox, Dropdown, IDropdownOption, TextField } from './tmp-components.stories';
import { PlaygroundProps } from './Playground.types.stories';
import { makeStyles } from '@fluentui/react-make-styles';

const tableStyle: React.CSSProperties = {
border: '1px solid black',
Expand All @@ -10,8 +11,39 @@ const cellStyle: React.CSSProperties = {
padding: '5px',
};

const useStyles = makeStyles({
stack: {
display: 'flex',
flexDirection: 'row',
flexWrap: 'nowrap',
width: 'auto',
height: 'auto',
boxSizing: 'border-box',
justifyContent: 'center',
},

checkbox: {
position: 'relative',
display: 'flex',
flexShrink: 0,
alignItems: 'center',
justifyContent: 'center',
height: '20px',
width: '20px',
border: `1px solid rgb(50, 49, 48)`,
borderRadius: '2px',
boxSizing: 'border-box',
transitionProperty: 'background, border, border-color',
transitionDuration: '200ms',
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.23, 1)',
overflow: 'hidden',
marginRight: '4px',
},
});

export const Playground = function <TType>(props: PlaygroundProps<TType>): JSX.Element {
const { children, sections } = props;
const styles = useStyles();

const [componentProps, setComponentProps] = React.useState<{ [key in string]: boolean | string } | null>(null);
const newProps: { [key in string]: boolean | string } = {};
Expand Down Expand Up @@ -54,7 +86,9 @@ export const Playground = function <TType>(props: PlaygroundProps<TType>): JSX.E
}

const onBooleanPropChange = (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean) => {
const newComponentProps: { [key in string]: boolean | string } = { ...componentProps };
const newComponentProps: { [key in string]: boolean | string } = {
...componentProps,
};
newComponentProps[propName] = checked || false;
setComponentProps(newComponentProps);
prop.setDefaultValue?.(checked || false);
Expand All @@ -71,8 +105,8 @@ export const Playground = function <TType>(props: PlaygroundProps<TType>): JSX.E
: (prop.defaultValue as boolean)
}
disabled={!isPropEnabled}
// eslint-disable-next-line react/jsx-no-bind
onChange={onBooleanPropChange}
styles={styles.checkbox}
/>
</td>
</tr>,
Expand All @@ -84,7 +118,9 @@ export const Playground = function <TType>(props: PlaygroundProps<TType>): JSX.E
ev?: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string,
) => {
const newComponentProps: { [key in string]: boolean | string } = { ...componentProps };
const newComponentProps: { [key in string]: boolean | string } = {
...componentProps,
};
newComponentProps[propName] = newValue || '';
setComponentProps(newComponentProps);
};
Expand All @@ -100,7 +136,6 @@ export const Playground = function <TType>(props: PlaygroundProps<TType>): JSX.E
: (prop.defaultValue as string)
}
disabled={!isPropEnabled}
// eslint-disable-next-line react/jsx-no-bind
onChange={onStringPropChange}
/>
</td>
Expand All @@ -111,11 +146,13 @@ export const Playground = function <TType>(props: PlaygroundProps<TType>): JSX.E
newProps[propName] = (componentProps && componentProps[propName]) || prop.defaultValue || propType[0];

const onOptionsPropChange = (
ev?: React.FormEvent<HTMLDivElement>,
option?: IDropdownOption<any>,
ev?: React.FormEvent<HTMLSelectElement>,
option?: IDropdownOption,
index?: number,
) => {
const newComponentProps: { [key in string]: boolean | string } = { ...componentProps };
const newComponentProps: { [key in string]: boolean | string } = {
...componentProps,
};
if (option) {
newComponentProps[propName] = (option.key as string) || '';
setComponentProps(newComponentProps);
Expand All @@ -134,7 +171,6 @@ export const Playground = function <TType>(props: PlaygroundProps<TType>): JSX.E
: (defaultSelectedKey as string)
}
options={propType.map(value => ({ key: value, text: value }))}
// eslint-disable-next-line react/jsx-no-bind
onChange={onOptionsPropChange}
/>
</td>
Expand All @@ -146,7 +182,7 @@ export const Playground = function <TType>(props: PlaygroundProps<TType>): JSX.E
<React.Fragment key={section.sectionName}>
<tr>
<td style={cellStyle} colSpan={2}>
<Text variant="medium">{section.sectionName}</Text>
<text>{section.sectionName}</text>
</td>
</tr>
{sectionList}
Expand All @@ -166,7 +202,7 @@ export const Playground = function <TType>(props: PlaygroundProps<TType>): JSX.E

return (
<>
<Stack horizontalAlign="center">{React.cloneElement(children, elementProps || {})}</Stack>
<div className={styles.stack}>{React.cloneElement(children, elementProps || {})}</div>
<table style={tableStyle} cellSpacing={0}>
<tbody>{playgroundSections}</tbody>
</table>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import * as React from 'react';

/* eslint-disable @typescript-eslint/naming-convention */

/** Type to get only the string keys of T. */
type StringKeyOf<T> = { [K in keyof T]: K extends string ? K : never }[keyof T];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as React from 'react';
import { ToggleButton, ToggleButtonProps } from '@fluentui/react-button';
import { useBoolean } from '@fluentui/react-utilities';
import { buttonBaseProps } from '../Button/Button.stories';
import { Playground } from '../Playground';
import { PlaygroundProps, PropDefinition } from '../Playground.types';
import { ToggleButton, ToggleButtonProps } from './ToggleButton';
import { Playground } from './Playground.stories';
import { PlaygroundProps, PropDefinition } from './Playground.types.stories';
import { buttonBaseProps } from './buttonBaseProps.stories';

export const ToggleButtonPlayground = () => {
const [checked, { setTrue: setTrueChecked, setFalse: setFalseChecked, toggle: toggleChecked }] = useBoolean(false);
Expand Down Expand Up @@ -43,3 +43,8 @@ export const ToggleButtonPlayground = () => {
</Playground>
);
};

export default {
title: 'Components/ToggleButton',
component: ToggleButton,
};
25 changes: 25 additions & 0 deletions packages/react-button/src/buttonBaseProps.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ButtonProps } from './Button';
import { PropDefinition } from './Playground.types.stories';

type ExampleProps = { iconOnly?: string };

export const buttonBaseProps: PropDefinition<ButtonProps & ExampleProps>[] = [
{ propName: 'block', propType: 'boolean' },
{ propName: 'circular', propType: 'boolean' },
{ propName: 'content', propType: 'string', defaultValue: 'This is a button', dependsOnProps: ['~iconOnly'] },
{ propName: 'disabled', propType: 'boolean', dependsOnProps: ['~disabledFocusable'] },
{ propName: 'disabledFocusable', propType: 'boolean', dependsOnProps: ['~disabled'] },
{ propName: 'icon', propType: 'boolean' },
{ propName: 'iconOnly', propType: 'boolean', dependsOnProps: ['icon'] },
{
propName: 'iconPosition',
propType: ['before', 'after'],
defaultValue: 'before',
dependsOnProps: ['icon', '~iconOnly'],
},
{ propName: 'outline', propType: 'boolean', dependsOnProps: ['~primary', '~subtle', '~transparent'] },
{ propName: 'primary', propType: 'boolean', dependsOnProps: ['~outline', '~subtle', '~transparent'] },
{ propName: 'size', propType: ['small', 'medium', 'large'], defaultValue: 'medium' },
{ propName: 'subtle', propType: 'boolean', dependsOnProps: ['~outline', '~primary', '~transparent'] },
{ propName: 'transparent', propType: 'boolean', dependsOnProps: ['~outline', '~primary', '~subtle'] },
];
Loading

0 comments on commit 97c1a55

Please sign in to comment.