import ts from 'typescript' import { createJsxElementWithChildren, createProp, createStringProp, getPropValueText, } from '../utils' /** * A function that transforms button v1 prop values into a Button/next JSX component * @returns * - `ts.JsxAttribute` if the prop should be transformed * - `null` if the prop should be removed * - `undefined` if the prop should be kept as is */ export const transformButtonProp = ( propName: string, propValue: ts.JsxAttributeValue | undefined, ): ts.JsxAttribute | null | undefined => { const sanitizedPropName = propName.replace(/'/g, '') const sanitizedPropValue = propValue?.kind === ts.SyntaxKind.StringLiteral ? propValue : ts.factory.createJsxExpression(undefined, propValue) switch (sanitizedPropName) { case 'onClick': return createProp('onPress', sanitizedPropValue) case 'reversed': return createProp('isReversed', sanitizedPropValue) case 'classNameOverride': return createProp('className', sanitizedPropValue) case 'data-automation-id': return createProp('data-testid', sanitizedPropValue) case 'fullWidth': return createProp('isFullWidth', sanitizedPropValue) case 'working': return createProp('isPending', sanitizedPropValue) case 'workingLabel': return createProp('pendingLabel', sanitizedPropValue) case 'workingLabelHidden': return createProp('hasHiddenPendingLabel', sanitizedPropValue) case 'onMouseDown': return createProp('onPressStart', sanitizedPropValue) case 'disableTabFocusAndIUnderstandTheAccessibilityImplications': return null case 'newTabAndIUnderstandTheAccessibilityImplications': return null case 'disabled': return createProp('isDisabled', sanitizedPropValue) case 'secondaryDismiss': return null case 'icon': { return createProp('icon', sanitizedPropValue) } case 'size': { const oldValue = propValue && (getPropValueText(propValue) as 'small' | 'regular') const buttonSizeMap = { small: 'medium', regular: 'large', } return oldValue ? createStringProp('size', buttonSizeMap[oldValue]) : undefined } case 'primary': return null case 'secondary': return null case 'destructive': return null default: return undefined } } type TransformButtonProp = { import: string component: ts.JsxSelfClosingElement | ts.JsxElement } /** A utility to transform V1 Button props into a Button or LinkButton with the import JSX element */ export const transformV1ButtonPropsToButtonOrLinkButton = ( buttonProps: ts.ObjectLiteralExpression, /** optional override for the variant type if needed, ie: primary or secondary actions*/ variantOverride?: string, /** whether to add an arrow icon to the button */ addArrowIcon?: boolean, ): TransformButtonProp => { let childrenValue: ts.JsxAttributeValue | undefined let hasSizeProp = false let hasLinkAttr = false let hasIconProp = false const newProps = buttonProps.properties.reduce((acc, currentProp) => { const propName = currentProp?.name?.getText() const propValue = ts.isPropertyAssignment(currentProp) ? currentProp.initializer : undefined if (propName) { if (propName === 'label') { if (ts.isShorthandPropertyAssignment(currentProp)) { childrenValue = ts.factory.createJsxExpression( undefined, ts.factory.createIdentifier(propName), ) return acc } if (propValue && propValue?.kind !== ts.SyntaxKind.StringLiteral) { childrenValue = ts.factory.createJsxExpression(undefined, propValue) return acc } childrenValue = propValue as ts.JsxAttributeValue return acc } if (propName === 'newTabAndIUnderstandTheAccessibilityImplications') { return [ ...acc, createStringProp('target', '_blank'), createStringProp('rel', 'noopener noreferrer'), ] } if (propName === 'size') { hasSizeProp = true } if (propName === 'icon') { hasIconProp = true } if (propName === 'href') { hasLinkAttr = true if (propValue && propValue?.kind !== ts.SyntaxKind.StringLiteral) { return [...acc, createProp('href', ts.factory.createJsxExpression(undefined, propValue))] } return [...acc, createProp('href', propValue as ts.StringLiteral)] } if (propName === 'component') { hasLinkAttr = true return [ ...acc, createProp( 'component', ts.factory.createJsxExpression(undefined, propValue) as ts.JsxExpression, ), ] } const newProp = transformButtonProp(propName, propValue as ts.JsxAttributeValue) if (newProp === null) return acc if (newProp === undefined) { const sanitizedPropName = propName.replace(/'|"/g, '') const sanitizedPropValue: ts.JsxAttributeValue = propValue?.kind === ts.SyntaxKind.StringLiteral ? (propValue as ts.StringLiteral) : (ts.factory.createJsxExpression(undefined, propValue) as ts.JsxExpression) const otherAttr = ts.factory.createJsxAttribute( ts.factory.createIdentifier(sanitizedPropName), sanitizedPropValue, ) acc.push(otherAttr) return acc } if (newProp) { return [...acc, newProp] } } return acc }, []) // Always add variant from variantOverride, or default to 'secondary' newProps.push(createStringProp('variant', variantOverride ?? 'secondary')) if (!hasSizeProp) { newProps.push(createStringProp('size', 'large')) } // Only add arrow icon if addArrowIcon is true AND there's no existing icon prop if (addArrowIcon && !hasIconProp) { const iconProp = ts.factory.createJsxAttribute( ts.factory.createIdentifier('icon'), ts.factory.createJsxExpression( undefined, ts.factory.createJsxSelfClosingElement( ts.factory.createIdentifier('Icon'), undefined, ts.factory.createJsxAttributes([ ts.factory.createJsxAttribute( ts.factory.createIdentifier('name'), ts.factory.createStringLiteral('arrow_forward'), ), ts.factory.createJsxAttribute( ts.factory.createIdentifier('shouldMirrorInRTL'), undefined, ), ts.factory.createJsxAttribute( ts.factory.createIdentifier('isPresentational'), undefined, ), ]), ), ), ) const iconPositionProp = ts.factory.createJsxAttribute( ts.factory.createIdentifier('iconPosition'), ts.factory.createStringLiteral('end'), ) newProps.push(iconProp, iconPositionProp) } return { import: hasLinkAttr ? 'LinkButton' : 'Button', component: createJsxElementWithChildren( hasLinkAttr ? 'LinkButton' : 'Button', newProps, childrenValue, ), } }