Skip to content

Commit

Permalink
feat: support slot binding
Browse files Browse the repository at this point in the history
  • Loading branch information
Hein Jeong committed Jul 7, 2022
1 parent f54c226 commit c67fa0d
Show file tree
Hide file tree
Showing 16 changed files with 178 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3890,6 +3890,42 @@ export default function TextWithDataBinding(
"
`;

exports[`amplify render tests component with binding should render slot binding 1`] = `
"/* eslint-disable */
import React from \\"react\\";
import {
EscapeHatchProps,
getOverrideProps,
} from \\"@aws-amplify/ui-react/internal\\";
import { Flex, FlexProps, View } from \\"@aws-amplify/ui-react\\";

export type ComponentWithSlotBindingProps = React.PropsWithChildren<
Partial<FlexProps> & {
mySlot?: React.ReactNode;
} & {
overrides?: EscapeHatchProps | undefined | null;
}
>;
export default function ComponentWithSlotBinding(
props: ComponentWithSlotBindingProps
): React.ReactElement {
const { mySlot, overrides, ...rest } = props;
return (
/* @ts-ignore: TS2322 */
<Flex
{...rest}
{...getOverrideProps(overrides, \\"ComponentWithSlotBinding\\")}
>
<View
children={mySlot}
{...getOverrideProps(overrides, \\"Rectangle\\")}
></View>
</Flex>
);
}
"
`;

exports[`amplify render tests component with data binding should add model imports 1`] = `
"/* eslint-disable */
import React from \\"react\\";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
getSyntaxKindToken,
buildChildElement,
buildConditionalExpression,
hasChildrenProp,
} from '../react-component-render-helper';

import { assertASTMatchesSnapshot } from './__utils__';
Expand Down Expand Up @@ -295,4 +296,18 @@ describe('react-component-render-helper', () => {
).toThrow('Parsed value 18 and type boolean mismatch');
});
});

describe('hasChildrenProp', () => {
test('returns true if children property exists', () => {
expect(
hasChildrenProp({ width: { value: '10px' }, children: { bindingProperties: { property: 'mySlot' } } }),
).toBe(true);
});

test('returns false if children property does not exist', () => {
expect(
hasChildrenProp({ width: { value: '10px' }, height: { bindingProperties: { property: 'myHeight' } } }),
).toBe(false);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ describe('amplify render tests', () => {
const generatedCode = generateWithAmplifyRenderer('textWithDataBinding');
expect(generatedCode.componentText).toMatchSnapshot();
});

it('should render slot binding', () => {
const generatedCode = generateWithAmplifyRenderer('slotBinding');
expect(generatedCode.componentText).toMatchSnapshot();
});
});

describe('component with variants', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
ActionStudioComponentEvent,
MutationActionSetStateParameter,
ComponentMetadata,
StudioComponentProperties,
} from '@aws-amplify/codegen-ui';

import {
Expand Down Expand Up @@ -631,3 +632,7 @@ export function getSetStateName(stateReference: StateStudioComponentProperty): s
const stateName = getStateName(stateReference);
return ['set', stateName.charAt(0).toUpperCase() + stateName.slice(1)].join('');
}

export function hasChildrenProp(componentProperties: StudioComponentProperties): boolean {
return !!('children' in componentProperties && componentProperties.children);
}
3 changes: 2 additions & 1 deletion packages/codegen-ui-react/lib/react-component-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
buildOpeningElementProperties,
getStateName,
getSetStateName,
hasChildrenProp,
} from './react-component-render-helper';
import {
buildOpeningElementControlEvents,
Expand Down Expand Up @@ -65,7 +66,7 @@ export class ReactComponentRenderer<TPropIn> extends ComponentRendererBase<

const element = factory.createJsxElement(
this.renderOpeningElement(),
renderChildren ? renderChildren(children) : [],
renderChildren && !hasChildrenProp(this.component.properties) ? renderChildren(children) : [],
factory.createJsxClosingElement(factory.createIdentifier(this.component.componentType)),
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
StudioComponentEventPropertyBinding,
InternalError,
InvalidInputError,
StudioComponentSlotBinding,
} from '@aws-amplify/codegen-ui';
import ts, {
createPrinter,
Expand Down Expand Up @@ -201,7 +202,8 @@ export function bindingPropertyUsesHook(
binding:
| StudioComponentDataPropertyBinding
| StudioComponentSimplePropertyBinding
| StudioComponentEventPropertyBinding,
| StudioComponentEventPropertyBinding
| StudioComponentSlotBinding,
): boolean {
return isDataPropertyBinding(binding) && 'predicate' in binding.bindingProperties;
}
Expand Down
19 changes: 18 additions & 1 deletion packages/codegen-ui-react/lib/react-studio-template-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
ComponentMetadata,
computeComponentMetadata,
validateComponentSchema,
isSlotBinding,
} from '@aws-amplify/codegen-ui';
import { EOL } from 'os';
import ts, {
Expand Down Expand Up @@ -450,6 +451,17 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer
),
);
propSignatures.push(propSignature);
} else if (isSlotBinding(binding)) {
const propSignature = factory.createPropertySignature(
undefined,
propName,
factory.createToken(SyntaxKind.QuestionToken),
factory.createTypeReferenceNode(
factory.createQualifiedName(factory.createIdentifier('React'), factory.createIdentifier('ReactNode')),
undefined,
),
);
propSignatures.push(propSignature);
}
});
}
Expand Down Expand Up @@ -510,7 +522,12 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer
if (isStudioComponentWithBinding(component)) {
Object.entries(component.bindingProperties).forEach((entry) => {
const [propName, binding] = entry;
if (isSimplePropertyBinding(binding) || isDataPropertyBinding(binding) || isEventPropertyBinding(binding)) {
if (
isSimplePropertyBinding(binding) ||
isDataPropertyBinding(binding) ||
isEventPropertyBinding(binding) ||
isSlotBinding(binding)
) {
const usesHook = bindingPropertyUsesHook(binding);
const shouldAssignToDifferentName = usesHook || keywords.has(propName);
const propVariableName = shouldAssignToDifferentName ? `${propName}Prop` : propName;
Expand Down
38 changes: 38 additions & 0 deletions packages/codegen-ui/example-schemas/slotBinding.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"id": "1234-5678-9010-99",
"componentType": "Flex",
"name": "ComponentWithSlotBinding",
"bindingProperties": {
"mySlot": {
"type": "Amplify.Slot"
}
},
"children": [
{
"children": [
{
"children": [],
"componentType": "Text",
"name": "ChildText",
"properties": {
"label": {
"value": "Nested child text"
}
}
}
],
"componentType": "View",
"name": "Rectangle",
"properties": {
"children": {
"bindingProperties": {
"property": "mySlot"
}
}
}
}
],
"properties": {},
"schemaVersion": "1.0"
}

5 changes: 5 additions & 0 deletions packages/codegen-ui/lib/renderer-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
StudioComponentSimplePropertyBinding,
StudioComponentPropertyBinding,
StudioComponentProperty,
StudioComponentSlotBinding,
} from './types';

export function isStudioComponentWithBinding(
Expand Down Expand Up @@ -76,3 +77,7 @@ export function isEventPropertyBinding(
): prop is StudioComponentEventPropertyBinding {
return 'type' in prop && prop.type === 'Event';
}

export function isSlotBinding(prop: StudioComponentPropertyBinding): prop is StudioComponentSlotBinding {
return 'type' in prop && prop.type === 'Amplify.Slot';
}
9 changes: 0 additions & 9 deletions packages/codegen-ui/lib/types/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export enum StudioComponentPropertyType {
Number = 'Number',
Boolean = 'Boolean',
Date = 'Date',
Slot = 'Amplify.Slot',
}

export type StudioComponentSimplePropertyBinding = {
Expand Down Expand Up @@ -92,14 +91,6 @@ export type StudioComponentSlotBinding = {
* This declares that the binding is a Slot type
*/
type: 'Amplify.Slot';
bindingProperties: StudioComponentSlotBindingProperty;
};

/**
* This represents the exposed top-level prop to be mapped as the children property
*/
export type StudioComponentSlotBindingProperty = {
slotName: string;
};

/**
Expand Down
5 changes: 0 additions & 5 deletions packages/codegen-ui/lib/types/properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export type CommonPropertyValues = {

export type StudioComponentProperty =
| FixedStudioComponentProperty
| SlotStudioComponentProperty
| BoundStudioComponentProperty
| CollectionStudioComponentProperty
| ConcatenatedStudioComponentProperty
Expand Down Expand Up @@ -147,7 +146,3 @@ export type StateStudioComponentProperty = {
componentName: string;
property: string;
} & CommonPropertyValues;

export type SlotStudioComponentProperty = {
slotName: string;
} & CommonPropertyValues;
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const EXPECTED_SUCCESSFUL_CASES = new Set([
'ComponentWithSimplePropertyBinding',
'ComponentWithAuthBinding',
'CompWithMultipleBindingsWithPred',
'ComponentWithSlotBinding',
'BoundDefaultValue',
'SimplePropertyBindingDefaultValue',
'SimpleAndBouldDefaultValue',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,15 @@ describe('Generated Components', () => {
});
});
});

describe('Slot Binding', () => {
it('Renders component passed into the slot, overriding nested components', () => {
cy.get('#slotBinding').within(() => {
cy.contains('Customer component');
cy.contains('Nested child text').should('not.exist');
});
});
});
});

describe('Collections', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
CollectionDefaultValue,
MyTheme,
ComponentWithSimplePropertyBinding,
ComponentWithSlotBinding,
ComponentWithDataBindingWithoutPredicate,
ComponentWithDataBindingWithPredicate,
ComponentWithMultipleDataBindingsWithPredicate,
Expand Down Expand Up @@ -279,6 +280,7 @@ export default function ComponentTests() {
priceUSD: 2200,
}}
/>
<ComponentWithSlotBinding id="slotBinding" mySlot={<div>Customer component</div>} />
</div>
<div id="collections">
<h2>Collections</h2>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"id": "1234-5678-9010-99",
"componentType": "Flex",
"name": "ComponentWithSlotBinding",
"bindingProperties": {
"mySlot": {
"type": "Amplify.Slot"
}
},
"children": [
{
"children": [
{
"children": [],
"componentType": "Text",
"name": "ChildText",
"properties": {
"label": {
"value": "Nested child text"
}
}
}
],
"componentType": "View",
"name": "Rectangle",
"properties": {
"children": {
"bindingProperties": {
"property": "mySlot"
}
}
}
}
],
"properties": {},
"schemaVersion": "1.0"
}

Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ export { default as ComponentWithDataBindingWithoutPredicate } from './component
export { default as ComponentWithSimplePropertyBinding } from './componentWithSimplePropertyBinding.json';
export { default as ComponentWithAuthBinding } from './componentWithAuthBinding.json';
export { default as CompWithMultipleBindingsWithPred } from './componentWithMultipleDataBindingsWithPredicate.json';
export { default as ComponentWithSlotBinding } from './componentWithSlotBinding.json';

0 comments on commit c67fa0d

Please sign in to comment.