Skip to content

Commit

Permalink
refactor(Builder): move components to single files, create hooks and …
Browse files Browse the repository at this point in the history
…create state files (#16279)

* refactor(Builder): move components to single files and create hooks

* refactor(Builder): add aria label
  • Loading branch information
assuncaocharles authored Jan 4, 2021
1 parent 2d86556 commit be53c0f
Show file tree
Hide file tree
Showing 11 changed files with 520 additions and 487 deletions.
401 changes: 10 additions & 391 deletions packages/fluentui/react-builder/src/components/Designer.tsx

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions packages/fluentui/react-builder/src/components/ErrorPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as React from 'react';
import { ErrorIcon } from '@fluentui/react-icons-northstar';
import { Text, Accordion } from '@fluentui/react-northstar';

export const ErrorPanel = ({ axeErrors }) => {
const panels = [
{
key: 'axe',
title: {
'aria-level': 4,
content: (
<Text>
<ErrorIcon style={{ marginRight: '0.5rem' }} /> {axeErrors.length} Accessibility{' '}
{axeErrors.length > 1 ? 'Errors' : 'Error'}
</Text>
),
},
content: (
<ul style={{ padding: '0rem 0.7rem', listStyleType: 'none' }}>
{axeErrors.map(error => (
<li>{error}</li>
))}
</ul>
),
},
];

return (
<div
style={{
background: '#e3404022',
}}
>
<Accordion panels={panels} />
</div>
);
};
2 changes: 1 addition & 1 deletion packages/fluentui/react-builder/src/components/Knobs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as React from 'react';
import { Menu, tabListBehavior } from '@fluentui/react-northstar';
import { ComponentInfo, ComponentProp } from '../componentInfo/types';
import { JSONTreeElement } from './types';
import { MultiTypeKnob } from '../config';
import { MultiTypeKnob } from './MultiTypeKnob';

// const designUnit = 4;
// const sizeRamp = [
Expand Down
99 changes: 99 additions & 0 deletions packages/fluentui/react-builder/src/components/MultiTypeKnob.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import * as React from 'react';
// import { isElement } from 'react-is';
// import * as _ from 'lodash';
// import * as FUI from '@fluentui/react-northstar';
// import * as FUIIcons from '@fluentui/react-icons-northstar';

/**
* Displays a knob with the ability to switch between data `types`.
*/
export const MultiTypeKnob: React.FunctionComponent<{
label: string;
types: ('boolean' | 'number' | 'string' | 'literal')[];
value: any;
onChange: (value: any) => void;
onRemoveProp: () => void;
options: string[];
required: boolean;
}> = ({ label, types, value, onChange, onRemoveProp, options, required }) => {
const defaultType = types[0];
const [type, setType] = React.useState(defaultType);

const knob = knobs[type];
const handleChangeType = React.useCallback(
e => setType(e.target.value), // @ts-ignore
[],
);

const propId = `prop-${label}`;

return (
<div style={{ paddingBottom: '4px', marginBottom: '4px', opacity: knob ? 1 : 0.4 }}>
<div>
{type !== 'boolean' && <label htmlFor={propId}>{label} </label>}
{types.length === 1 ? (
<code style={{ float: 'right' }}>{type}</code>
) : (
types.map(t => (
<button key={t} onClick={() => handleChangeType(t)}>
{t}
</button>
))
)}
</div>
{knob && knob({ options, value, onChange, id: propId })}
{type === 'boolean' && <label htmlFor={propId}> {label}</label>}
{!required && type === 'literal' && value && (
<button
style={{
background: 'none',
border: '1px solid black',
borderRadius: 4,
margin: 8,
}}
aria-label="Remove"
onClick={_ => {
onRemoveProp();
}}
>
X
</button>
)}
</div>
);
};

export const knobs = {
boolean: ({ value, onChange, id }) => (
<input id={id} type="checkbox" checked={!!value} onChange={e => onChange(!!e.target.checked)} />
),

number: ({ value, onChange, id }) => (
<input
id={id}
style={{ width: '100%' }}
type="number"
value={Number(value)}
onChange={e => onChange(Number(e.target.value))}
/>
),

string: ({ value, onChange, id }) => (
<input id={id} style={{ width: '100%' }} value={String(value)} onChange={e => onChange(e.target.value)} />
),

literal: ({ options, value, onChange, id }) => (
<select id={id} onChange={e => onChange(e.target.value)} value={value}>
{options?.map((
opt, // FIXME the optional is workaround for showing `Dialog` props when selected from component tree
) => (
<option key={opt} value={opt}>
{opt}
</option>
))}
</select>
),

ReactText: ({ value, onChange, id }) => knobs.string({ value, onChange, id }),
'React.ElementType': ({ value, onChange, id }) => knobs.string({ value, onChange, id }),
};
95 changes: 0 additions & 95 deletions packages/fluentui/react-builder/src/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -769,98 +769,3 @@ export const jsonTreeCloneElement = (tree: JSONTreeElement, element: any): JSONT
}
return result;
};

/**
* Displays a knob with the ability to switch between data `types`.
*/
export const MultiTypeKnob: React.FunctionComponent<{
label: string;
types: ('boolean' | 'number' | 'string' | 'literal')[];
value: any;
onChange: (value: any) => void;
onRemoveProp: () => void;
options: string[];
required: boolean;
}> = ({ label, types, value, onChange, onRemoveProp, options, required }) => {
const defaultType = types[0];
const [type, setType] = React.useState(defaultType);

const knob = knobs[type];
const handleChangeType = React.useCallback(
e => setType(e.target.value), // @ts-ignore
[],
);

// console.log('MultiTypeKnob', { label, value, type, types });

const propId = `prop-${label}`;

return (
<div style={{ paddingBottom: '4px', marginBottom: '4px', opacity: knob ? 1 : 0.4 }}>
<div>
{type !== 'boolean' && <label htmlFor={propId}>{label} </label>}
{types.length === 1 ? (
<code style={{ float: 'right' }}>{type}</code>
) : (
types.map(t => (
<button key={t} onClick={() => handleChangeType(t)}>
{t}
</button>
))
)}
</div>
{knob && knob({ options, value, onChange, id: propId })}
{type === 'boolean' && <label htmlFor={propId}> {label}</label>}
{!required && type === 'literal' && value && (
<button
style={{
background: 'none',
border: '1px solid black',
borderRadius: 4,
margin: 8,
}}
onClick={_ => {
onRemoveProp();
}}
>
X
</button>
)}
</div>
);
};

export const knobs = {
boolean: ({ value, onChange, id }) => (
<input id={id} type="checkbox" checked={!!value} onChange={e => onChange(!!e.target.checked)} />
),

number: ({ value, onChange, id }) => (
<input
id={id}
style={{ width: '100%' }}
type="number"
value={Number(value)}
onChange={e => onChange(Number(e.target.value))}
/>
),

string: ({ value, onChange, id }) => (
<input id={id} style={{ width: '100%' }} value={String(value)} onChange={e => onChange(e.target.value)} />
),

literal: ({ options, value, onChange, id }) => (
<select id={id} onChange={e => onChange(e.target.value)} value={value}>
{options?.map((
opt, // FIXME the optional is workaround for showing `Dialog` props when selected from component tree
) => (
<option key={opt} value={opt}>
{opt}
</option>
))}
</select>
),

ReactText: ({ value, onChange, id }) => knobs.string({ value, onChange, id }),
'React.ElementType': ({ value, onChange, id }) => knobs.string({ value, onChange, id }),
};
2 changes: 2 additions & 0 deletions packages/fluentui/react-builder/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './useAxeOnElement';
export * from './useMode';
41 changes: 41 additions & 0 deletions packages/fluentui/react-builder/src/hooks/useAxeOnElement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as React from 'react';
import * as axeCore from 'axe-core';

export function useAxeOnElement(): [any[], (selectedElementUuid: any) => void] {
const [axeErrors, setAxeErrors] = React.useState([]);
const runAxeOnElement = React.useCallback(selectedElementUuid => {
const iframe = document.getElementsByTagName('iframe')[0];
const selectedComponentAxeErrors = [];
axeCore.run(
iframe,
{
rules: {
// excluding rules which are related to the whole page not to components
'page-has-heading-one': { enabled: false },
region: { enabled: false },
'landmark-one-main': { enabled: false },
},
},
(err, result) => {
if (err) {
console.error('Axe failed', err);
} else {
result.violations.forEach(violation => {
violation.nodes.forEach(node => {
if (node.html.includes(`data-builder-id="${selectedElementUuid}"`)) {
selectedComponentAxeErrors.push(
node.failureSummary
.replace('Fix all of the following:', '-')
.replace('Fix any of the following:', '-'),
);
}
});
});
}
setAxeErrors(selectedComponentAxeErrors);
},
);
}, []);

return [axeErrors, runAxeOnElement];
}
13 changes: 13 additions & 0 deletions packages/fluentui/react-builder/src/hooks/useMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as React from 'react';
import { DesignerMode } from '../components/types';

export function useMode(): [
{ mode: DesignerMode; isExpanding: boolean; isSelecting: boolean },
(mode: DesignerMode) => void,
] {
const [mode, setMode] = React.useState<DesignerMode>('build');
const isExpanding = mode === 'build';
const isSelecting = mode === 'build' || mode === 'design';

return [{ mode, isExpanding, isSelecting }, setMode];
}
2 changes: 2 additions & 0 deletions packages/fluentui/react-builder/src/state/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './state';
export * from './utils';
Loading

0 comments on commit be53c0f

Please sign in to comment.