Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Form component #1598

Merged
merged 67 commits into from
Apr 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
16769dc
Add form component
bytasv Jan 20, 2023
c2b2b03
Merge branch 'master' into form-component
bytasv Jan 23, 2023
37bfae6
Add form component
bytasv Jan 24, 2023
7289e9a
Replace with reac-hook-form
bytasv Jan 27, 2023
6bdab56
Merge branch 'master' into form-component
bytasv Feb 2, 2023
2f94fb0
Merge branch 'master' into form-component
bytasv Feb 8, 2023
855d8e9
Use a different form approach
bytasv Feb 8, 2023
6c7a51f
Fix form value setting
bytasv Feb 8, 2023
8981533
Merge branch 'master' into form-component
bytasv Feb 10, 2023
32a72f5
Use form context and form setter
bytasv Feb 14, 2023
1a37903
Pull closest form for prop editor
bytasv Feb 15, 2023
d0dd2d6
Fix controlled props editor
bytasv Feb 16, 2023
8605f5c
Hide form value control
bytasv Feb 16, 2023
2938d13
Merge branch 'master' into form-component
bytasv Feb 16, 2023
a95d9e6
Fix lint errors
bytasv Feb 17, 2023
46f693e
tsignore error
bytasv Feb 17, 2023
362b53d
Merge remote-tracking branch 'origin/master' into form-component
apedroferreira Feb 28, 2023
36497da
Basic functionality with react-hook-form
apedroferreira Mar 1, 2023
86271e7
Store form values in scope
apedroferreira Mar 1, 2023
7f37b80
Add functioning validation
apedroferreira Mar 1, 2023
b6ce0da
Show validation errors, in-progress form reset
apedroferreira Mar 2, 2023
9211a7c
Fix text, file and date components in forms
apedroferreira Mar 3, 2023
eab4144
Fix date component
apedroferreira Mar 3, 2023
a4ecfb8
Extra fix
apedroferreira Mar 3, 2023
7b6a1f5
Fix default values with resets in all form input components
apedroferreira Mar 13, 2023
8cdb9fe
Run validation when validation rules change
apedroferreira Mar 13, 2023
5484531
Add validation props to all inputs
apedroferreira Mar 15, 2023
6a7d987
Allow validation outside forms
apedroferreira Mar 16, 2023
5764345
Add prop categories and separate validation props
apedroferreira Mar 16, 2023
55b3e85
fix slow text input
apedroferreira Mar 16, 2023
774aeb7
Run validation when validation rules change (fixed)
apedroferreira Mar 16, 2023
62479b3
Validate on submit in all input types
apedroferreira Mar 16, 2023
6d84e9b
Refactor with HOC
apedroferreira Mar 16, 2023
6dd6fcb
Add visual customization options
apedroferreira Mar 16, 2023
67ea396
Fix types
apedroferreira Mar 16, 2023
9679333
Merge remote-tracking branch 'origin/master' into form-component
apedroferreira Mar 17, 2023
e23200b
Fix tests
apedroferreira Mar 17, 2023
93764f4
Restore spacing in tests
apedroferreira Mar 17, 2023
605d5f8
Refactor, fix bugs
apedroferreira Mar 20, 2023
1bbfcb1
Improve shared form logic
apedroferreira Mar 21, 2023
898d25e
Merge remote-tracking branch 'origin/master' into form-component
apedroferreira Mar 21, 2023
6c7224f
Adjust date picker component
apedroferreira Mar 21, 2023
21e19e9
Merge remote-tracking branch 'origin/master' into form-component
apedroferreira Mar 24, 2023
f41dcc5
Fix merge
apedroferreira Mar 24, 2023
4d50594
Fix types and imports
apedroferreira Mar 24, 2023
2ee186c
Merge remote-tracking branch 'origin/master' into form-component
apedroferreira Mar 24, 2023
6e142c3
try to fix tests
apedroferreira Mar 24, 2023
7af7b6c
Allow renaming form fields
apedroferreira Mar 24, 2023
e56448a
Disallow nesting form nodes
apedroferreira Mar 24, 2023
1b6e393
Merge remote-tracking branch 'origin/master' into form-component
apedroferreira Mar 24, 2023
8802c8b
Try to fix CI
apedroferreira Mar 24, 2023
ee1ba95
Why didn't Prettier run
apedroferreira Mar 24, 2023
3a87fa2
Merge branch 'master' into form-component
Janpot Mar 28, 2023
e4b4c84
Replace all page root waits with waitForOverlay
apedroferreira Mar 28, 2023
2257a7b
Merge remote-tracking branch 'origin/master' into form-component
apedroferreira Apr 6, 2023
7ff5266
Try to fix last merge
apedroferreira Apr 6, 2023
4d2a6d3
Fix tests
apedroferreira Apr 10, 2023
3f0559d
Fix tests more
apedroferreira Apr 10, 2023
0fbe5d6
Merge branch 'master' into form-component
apedroferreira Apr 10, 2023
ba56e7a
Pass props directly + fallback field display name
apedroferreira Apr 11, 2023
8d59461
Fix container hover border
apedroferreira Apr 14, 2023
efc062a
Merge remote-tracking branch 'origin/master' into form-component
apedroferreira Apr 14, 2023
699e6cd
Try test without waitFor
apedroferreira Apr 14, 2023
dd912eb
Test fix attempt
apedroferreira Apr 14, 2023
08f1cc3
Merge remote-tracking branch 'origin/master' into form-component
apedroferreira Apr 14, 2023
f06e5f4
Fix isInteractive prop
apedroferreira Apr 14, 2023
cdc220f
Fix interactive node issues
apedroferreira Apr 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/toolpad-app/src/appDom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,15 @@ export function getPageAncestor(dom: AppDom, node: AppDomNode): PageNode | null
return null;
}

/**
* Returns all nodes with a given component type
*/
export function getComponentTypeNodes(dom: AppDom, componentId: string): readonly AppDomNode[] {
return Object.values(dom.nodes).filter(
(node) => isElement(node) && node.attributes.component.value === componentId,
);
}

/**
* Returns the set of names for which the given node must have a different name
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/toolpad-app/src/runtime/ToolpadApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ function RenderedNodeContent({ node, childNodeGroups, Component }: RenderedNodeC
return (
<NodeRuntimeWrapper
nodeId={nodeId}
nodeName={node.name}
componentConfig={Component[TOOLPAD_COMPONENT]}
NodeError={NodeError}
>
Expand Down Expand Up @@ -1085,7 +1086,6 @@ function RenderedPage({ nodeId }: RenderedNodeProps) {
childNodeGroups={{ children }}
Component={PageRootComponent}
/>

{queries.map((node) => (
<FetchNode key={node.id} page={page} node={node} />
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default function NodeNameEditor({ node, sx }: NodeNameEditorProps) {
<TextField
sx={sx}
fullWidth
label="name"
label="Node name"
error={!isNameValid}
helperText={nodeNameError}
value={nameInput}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ const FUTURE_COMPONENTS = new Map<string, FutureComponentSpec>([
['Drawer', { url: 'https://github.com/mui/mui-toolpad/issues/1540', displayName: 'Drawer' }],
['Html', { url: 'https://github.com/mui/mui-toolpad/issues/1311', displayName: 'Html' }],
['Icon', { url: 'https://github.com/mui/mui-toolpad/issues/83', displayName: 'Icon' }],
['Form', { url: 'https://github.com/mui/mui-toolpad/issues/749', displayName: 'Form' }],
['Card', { url: 'https://github.com/mui/mui-toolpad/issues/748', displayName: 'Card' }],
['Slider', { url: 'https://github.com/mui/mui-toolpad/issues/746', displayName: 'Slider' }],
['Switch', { url: 'https://github.com/mui/mui-toolpad/issues/745', displayName: 'Switch' }],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Stack, styled, Typography, Divider } from '@mui/material';
import * as React from 'react';
import * as _ from 'lodash-es';
import {
ArgTypeDefinition,
ArgTypeDefinitions,
Expand Down Expand Up @@ -97,6 +98,11 @@ function ComponentPropsEditor<P extends object>({
);
}, [bindings, node.id]);

const argTypesByCategory = _.groupBy(
Object.entries(componentConfig.argTypes || {}) as ExactEntriesOf<ArgTypeDefinitions<P>>,
([, propTypeDef]) => propTypeDef?.category || 'properties',
);

return (
<React.Fragment>
{hasLayoutControls ? (
Expand All @@ -121,24 +127,26 @@ function ComponentPropsEditor<P extends object>({
<Divider sx={{ mt: 1 }} />
</React.Fragment>
) : null}
<Typography variant="overline" className={classes.sectionHeading}>
Properties:
</Typography>
{(
Object.entries(componentConfig.argTypes || {}) as ExactEntriesOf<ArgTypeDefinitions<P>>
).map(([propName, propTypeDef]) =>
propTypeDef && shouldRenderControl(propTypeDef, propName, props, componentConfig) ? (
<div key={propName} className={classes.control}>
<NodeAttributeEditor
node={node}
namespace="props"
props={props}
name={propName}
argType={propTypeDef}
/>
</div>
) : null,
)}
{Object.entries(argTypesByCategory).map(([category, argTypeEntries]) => (
<React.Fragment key={category}>
<Typography variant="overline" className={classes.sectionHeading}>
{category}:
</Typography>
{argTypeEntries.map(([propName, propTypeDef]) =>
propTypeDef && shouldRenderControl(propTypeDef, propName, props, componentConfig) ? (
<div key={propName} className={classes.control}>
<NodeAttributeEditor
node={node}
namespace="props"
props={props}
name={propName}
argType={propTypeDef}
/>
</div>
) : null,
)}
</React.Fragment>
))}
</React.Fragment>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export default function EditorCanvasHost({
const [editorOverlayRoot, setEditorOverlayRoot] = React.useState<HTMLElement | null>(null);

const handleKeyDown = useEvent((event: KeyboardEvent) => {
const isZ = event.key.toLowerCase() === 'z';
const isZ = !!event.key && event.key.toLowerCase() === 'z';

const undoShortcut = isZ && (event.metaKey || event.ctrlKey);
const redoShortcut = undoShortcut && event.shiftKey;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const NodeHudWrapper = styled('div', {
outline: `1px dotted ${isOutlineVisible ? theme.palette.primary[500] : 'transparent'}`,
zIndex: 80,
'&:hover': {
outline: `2px dashed ${isHoverable ? 'transparent' : theme.palette.primary[500]}`,
outline: `2px dashed ${isHoverable ? theme.palette.primary[500] : 'transparent'}`,
},
[`.${nodeHudClasses.selected}`]: {
position: 'absolute',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
isPageColumn,
PAGE_ROW_COMPONENT_ID,
PAGE_COLUMN_COMPONENT_ID,
isFormComponent,
FORM_COMPONENT_ID,
} from '../../../../toolpadComponents';
import { PinholeOverlay } from '../../../../PinholeOverlay';
import {
Expand Down Expand Up @@ -436,9 +438,12 @@ export default function RenderOverlay({ bridge }: RenderOverlayProps) {
return new Set();
}
return new Set(
[...appDom.getPageAncestors(dom, selectedNode), selectedNode].map(
(interactiveNode) => interactiveNode.id,
),
[
...appDom
.getPageAncestors(dom, selectedNode)
.filter((ancestor) => appDom.isElement(ancestor) && isPageLayoutComponent(ancestor)),
selectedNode,
].map((ancestor) => ancestor.id),
);
}, [dom, selectedNode]);

Expand Down Expand Up @@ -519,20 +524,33 @@ export default function RenderOverlay({ bridge }: RenderOverlayProps) {

const isEmptyPage = pageNodes.length <= 1;

/**
* Return all nodes that are available for insertion.
* i.e. Exclude all descendants of the current selection since inserting in one of
* them would create a cyclic structure.
*/
const availableDropTargets = React.useMemo((): appDom.AppDomNode[] => {
if (!draggedNode) {
return [];
}

/**
* Return all nodes that are available for insertion.
* i.e. Exclude all descendants of the current selection since inserting in one of
* them would create a cyclic structure.
*/
const excludedNodes =
selectedNode && !newNode
? new Set<appDom.AppDomNode>([selectedNode, ...appDom.getDescendants(dom, selectedNode)])
: new Set();
let excludedNodes = new Set();

if (selectedNode && !newNode) {
excludedNodes = new Set<appDom.AppDomNode>([
selectedNode,
...appDom.getDescendants(dom, selectedNode),
]);
}

if (isFormComponent(draggedNode)) {
const formNodes = appDom.getComponentTypeNodes(dom, FORM_COMPONENT_ID);
const formNodeDescendants = formNodes
.map((formNode) => appDom.getDescendants(dom, formNode))
.flat();

formNodeDescendants.forEach(excludedNodes.add, excludedNodes);
}

return pageNodes.filter((n) => !excludedNodes.has(n));
}, [dom, draggedNode, newNode, pageNodes, selectedNode]);
Expand Down Expand Up @@ -1581,7 +1599,6 @@ export default function RenderOverlay({ bridge }: RenderOverlayProps) {
const isPageColumnChild = parent ? appDom.isElement(parent) && isPageColumn(parent) : false;

const isSelected = selectedNode && !newNode ? selectedNode.id === node.id : false;
const isInteractive = interactiveNodes.has(node.id) && !draggedNode && !draggedEdge;

const isHorizontallyResizable = isSelected && (isPageRowChild || isPageColumnChild);
const isVerticallyResizable =
Expand All @@ -1590,6 +1607,8 @@ export default function RenderOverlay({ bridge }: RenderOverlayProps) {
const isResizing = Boolean(draggedEdge);
const isResizingNode = isResizing && node.id === draggedNodeId;

const isInteractive = interactiveNodes.has(node.id) && !isResizing && !isDraggingOver;

if (!nodeRect) {
return null;
}
Expand Down Expand Up @@ -1618,7 +1637,7 @@ export default function RenderOverlay({ bridge }: RenderOverlayProps) {
onDelete={handleNodeDelete(node.id)}
isResizing={isResizingNode}
resizePreviewElementRef={resizePreviewElementRef}
isHoverable={isResizing && !isDraggingOver}
isHoverable={!isResizing && !isDraggingOver}
isOutlineVisible={isDraggingOver}
/>
) : null}
Expand Down
6 changes: 6 additions & 0 deletions packages/toolpad-app/src/toolpadComponents/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type InstantiatedComponents = Record<string, InstantiatedComponent | unde
export const PAGE_ROW_COMPONENT_ID = 'PageRow';
export const PAGE_COLUMN_COMPONENT_ID = 'PageColumn';
export const STACK_COMPONENT_ID = 'Stack';
export const FORM_COMPONENT_ID = 'Form';

export const INTERNAL_COMPONENTS = new Map<string, ToolpadComponentDefinition>([
[PAGE_ROW_COMPONENT_ID, { displayName: 'Row', builtIn: 'PageRow', system: true }],
Expand All @@ -40,6 +41,7 @@ export const INTERNAL_COMPONENTS = new Map<string, ToolpadComponentDefinition>([
['Paper', { displayName: 'Paper', builtIn: 'Paper' }],
['Tabs', { displayName: 'Tabs', builtIn: 'Tabs' }],
['Container', { displayName: 'Container', builtIn: 'Container' }],
[FORM_COMPONENT_ID, { displayName: 'Form', builtIn: 'Form' }],
]);

function createCodeComponent(domNode: appDom.CodeComponentNode): ToolpadComponentDefinition {
Expand Down Expand Up @@ -84,3 +86,7 @@ export function isPageColumn(elementNode: appDom.ElementNode): boolean {
export function isPageLayoutComponent(elementNode: appDom.ElementNode): boolean {
return isPageRow(elementNode) || isPageColumn(elementNode);
}

export function isFormComponent(elementNode: appDom.ElementNode): boolean {
return getElementNodeComponentId(elementNode) === FORM_COMPONENT_ID;
}
4 changes: 4 additions & 0 deletions packages/toolpad-components/src/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ export default createComponent(Button, {
helperText: 'Whether the button is disabled.',
typeDef: { type: 'boolean' },
},
type: {
helperText: 'Button HTML type',
typeDef: { type: 'string', enum: ['button', 'submit', 'reset'], default: 'button' },
},
sx: {
helperText: SX_PROP_HELPER_TEXT,
typeDef: { type: 'object' },
Expand Down
Loading