Anvil2 Codemods
This package includes the source code for codemods created to make upgrading Anvil2 components and utilities easier.
Inspiration is from https://github.com/carlrip/codemod-react-ts.
Contributing
Contributions are encouraged! See the Contributing docs for instructions.
Using the codemods
To use this CLI tool to upgrade code from Anvil (@servicetitan/design-system
) to Anvil2 (@servicetitan/anvil2
), we recommend using npx
to always use the latest version.
npx @servicetitan/anvil2-codemods@latest [path] [transforms] [options]
Using the --help
option will give you the following description:
Arguments:
path Files or directory to transform. Can be a glob like src/**.test.js. Can be relative path from current working directory or absolute path.
transforms One or many of the choices separated by comma (no space). Available choices are all,button. See
https://github.com/servicetitan/hammer/tree/main/packages/anvil2-codemods/README.md#transforms for more information.
Options:
-f, --force Bypass Git safety checks and forcibly run codemods
--dry Dry run (no changes are made to files)
--print Print transformed files
--jscodeshift='<ARGS>' Space separated jscodeshift CLI options. For example, --jscodeshift='--silent --run-in-band'. See https://jscodeshift.com/run/cli/#options for more information.
-h, --help Display this help
See the JSCodeshift docs for more info no available options to pass to --jscodeshift
.
Transforms
These are all of the transforms currently available that can be passed as the second argument of the CLI:
all
banner
body-text
button
button-group
eyebrow
headline
link
tag
tag-group
If no transform is passed, a menu will appear to manually select transform(s) to run.
Formatting after transforms
Due to the nature of how codemods are run, formatting using Prettier or similar tools is not done during the transform. You will likely need to reformat the page after running a codemod to match the project standards.
This is not something we plan to incorporate into the codemods tool, since there are many different tools, plugins, and configuration options for code formatting and linting.
Docs for missing transforms
There are some transforms that can't be automated with a codemod, usually because the Anvil version doesn't have an exact match in Anvil2. In such cases, a comment will be added with some details and a links to this page or to the Anvil2 documentation.
The rest of this page has instructions for individual cases that could not be auto-transformed.
Known issues (no automated message)
Removed import from Anvil that is used as a TypeScript Generic Parameter
If there is an import from @servicetitan/design-system
that is only used on the page as a TypeScript generic parameter for a function (see example below), the import will be removed. This is related to a known issue in jscodeshift
.
import { AnvilSelectMultipleProps } from "@servicetitan/design-system";
const example = functionWithGenericParam<AnvilSelectMultipleProps>();
The solution is to manually add back the import that was removed. We will update the codemods once the issue is resolved on jscodeshift
.
Anvil (1) Text components might need to be ran multiple times
There is an issue that occurs when multiple Anvil (1) components are mapped to a single Anvil2 component, where only one component is transformed at a time.
For example, if both a <BodyText />
and <Headline />
component are used, only one will be transformed the first time you run the code, and the other will remain as-is. This should be resolved by running the transform again.
Generic messages
Component name changed and alias removed
Expand for details
Summary
Some components in Anvil map to new components in Anvil2 if they have a certain combination of props. For example, this Anvil Button
:
<Button href="https://something.com">Open link</Button>
would change to a ButtonLink
in Anvil2:
<ButtonLink href="https://something.com">Open link</ButtonLink>
Since there are other combinations of Button
that do not result in a ButtonLink
, there are some complexities involved with determining how an alias would work for a Button
import that is used both as a regular Anvil2 Button
and as a ButtonLink
in the same page.
Because of this, we ask that certain aliases are manually adjusted after the codemod is done. Here's a full example, again using ButtonLink
:
import { Button as AnvilButton } from "@servicetitan/design-system";
export const Component = () => (
<>
<AnvilButton>Regular Button</AnvilButton>
<AnvilButton href="#">Button Link</AnvilButton>
</>
);
import { Button as AnvilButton, ButtonLink } from "@servicetitan/anvil2";
export const Component = () => (
<>
<AnvilButton>Regular Button</AnvilButton>
{/* ... codemods comment ... */}
<ButtonLink href="#">Button Link</ButtonLink>
</>
);
Next steps
- Either manually update the alias for the the new component, or don't use an alias
Component.el
- No longer supported
Expand for details
See documentation
Summary
In Anvil2, the el
prop has been removed to promote consistency and better accessibility in our components.
Next steps
- Verify the behavior and any custom styles of this component is still correct
Banner
Banner.icon
- No longer supported
Expand for details
See Alert documentation
BodyText
BodyText.inline
, BodyText.italic
and BodyText.bold
- No longer supported
Expand for details
See Body Text documentation
Summary
In Anvil2, the inline
, italic
, and bold
props are no longer supported. Body text is now created using the Text
component with the variant="body"
prop.
Next steps
- Verify that the new
Text
component with variant="body"
doesn't break the UI.
- Manually remove
className="d-i"
, className="fs-italic"
, className="fw-bold"
from the Text
component.
- Make adjustments if needed.
Button
Button.appearance
- Invalid combinations
Expand for details
See Button appearance documentation
Summary
In Anvil, a combination of fill and color props could be used to create many variations of buttons. These props included:
color
fill
negative
outline
primary
text
In Anvil2, we've reduced the possible combinations and have a single appearance
prop, which allows:
"primary"
"secondary"
"danger"
"danger-secondary"
"ghost"
For any combination that doesn't easily translate from Anvil to Anvil2, manual intervention is required to set the new appearance
. For example, a blue
/outline
button from Anvil could be either a "primary"
or "secondary"
button in Anvil2, depending on context.
Next steps
- Verify the correct
appearance
with your designer
- Remove any of the original Anvil props
- Add the correct
appearance
prop
Button.full
and Button.width
- No longer supported
Expand for details
See Button documentation
Summary
In Anvil2, the Button.width
and Button.full
props are no longer supported. Custom styles must be added using one of the methods mentioned in the comment.
Next steps
- Use CSS or Flex/Grid properties to achieve the desired button width
Button.icon
- Verify SVG component
Expand for details
See Button documentation
Summary
In Anvil2, the component passed to the Button.icon
prop must be of Svg
type:
type Svg = FC<SVGProps<SVGSVGElement>>;
*.svg
files imported in projects that use svgo
(default in ST MFEs and monolith) are converted to this type.
There may be no further action required, but if a TypeScript error crops up, the icon
may need to be refactored to use the correct type, rather than the React.ReactElement
type in Anvil.
Next steps
- If a TypeScript error occurs, or the icon is not rendered, replace the current component with an SVG component.
Button.inactive
- No longer supported
Expand for details
See Button documentation
Summary
In Anvil2, the inactive
prop has been removed to promote consistency and better accessibility in our components. It is automatically replaced with disabled
, which similarly prevents interactions, but also updates the UI and adds accessibility properties related to disabled elements.
Next steps
- Verify that the button should be disabled
Button.xsmall
and Button.size="xsmall"
- No longer supported
Expand for details
See Button size documentation
Summary
In Anvil2, xsmall
buttons are no longer supported, either through the xsmall
or size="xsmall"
props. The button is automatically converted to a small
button, but this may require some manual style adjustments if it breaks the UI.
Next steps
- Verify that the new button size doesn't break the UI.
- Make adjustments if needed.
Button Group
ButtonGroup.attached
- Not supported in Flex
Expand for details
See Flex documentation
Summary
Since the ButtonGroup
no longer exists in Anvil2, and the Flex
component does not have an attached
prop, this behavior must be done custom. A designer should verify that this UI is still the expectation versus having a gap between the buttons.
Next steps
- Work with designer to decide if the design should update
- If not, remove gap="2" and add custom styles to Button children to achieve desired UI.
ButtonGroup.equalWidth
- Not supported in Flex
Expand for details
See Flex documentation
Summary
Since the ButtonGroup
no longer exists in Anvil2, and the Flex
component does not have an equalWidth
prop, this behavior must be done custom.
The codemod will automatically add flexGrow="1"
to all Button
components that are direct children of the new Flex
component. For more complex cases, such as using a map
function, the prop should be added manually.
This message will also appear if there are any JSX Element children that are not specifically named Button
. If using an alias or passing through props to an Anvil2 Button
, adding flexGrow="1"
should work, otherwise you may need to add custom styles using a CSS class with flex-grow: 1
.
Note: You will also need to add custom styling for the fullWidth
prop, which is used with equalWidth
. More on that below.
Next steps
- Add CSS styles via the
flexGrow="1"
prop on all children, or with custom CSS
ButtonGroup.fullWidth
- Not supported in Flex
Expand for details
See Flex documentation
Summary
Since the ButtonGroup
no longer exists in Anvil2, and the Flex
component does not have a fullWidth
prop, this behavior must be done custom. This can be done a few ways, and depends on the parent element of the new Flex
component.
Some options:
- Add
width: 100%
to styles
- Use
grow="1"
on Flex
if parent is also a flex container and has full-width
- Use grid props, such as
gridColumn
or gridArea
, if the parent is a grid container
Next steps
- Add CSS styles to create a full-width button group using a
Flex
Eyebrow
Eyebrow.inline
, Eyebrow.italic
and Eyebrow.bold
- No longer supported
Expand for details
See Eyebrow documentation
Summary
In Anvil2, the inline
, italic
, and bold
props are no longer supported. Eyebrows are now created using the Text
component with the variant="eyebrow"
prop.
Next steps
- Verify that the new
Text
component with variant="eyebrow"
doesn't break the UI.
- Manually remove
className="d-i"
, className="fs-italic"
, className="fw-bold"
from the Text
component.
- Make adjustments if needed.
Headline
Headline.inline
, Headline.italic
, Headline.subdued
and Headline.bold
- No longer supported
Expand for details
See Headline documentation
Summary
In Anvil2, the inline
, italic
, subdued
, and bold
props are no longer supported. Headlines are now created using the Text
component with the variant="headline"
prop.
Next steps
- Verify that the new
Text
component with variant="headline"
doesn't break the UI.
- Manually remove
className="d-i"
, className="fs-italic"
, className="fw-bold"
from the Text
component.
- Make adjustments if needed.
Headline.el
- Now required
Expand for details
See Headline documentation
Summary
In Anvil2, the el
prop is required on Text
when variant="headline"
. A valid el
is important for semantics and accessibility. We apply an imperfect system to set this value for you:
- If your A1
Headline
had an el
prop, we use that if it is h1
, h2
, h3
, h4
, h5
, or h6
.
- If your A1
Headline
had a size
prop, we attempt to infer a correct value for el
(small
= h4
, medium
=h3
, large
=h2
, xlarge
=h1
).
- If the values of
el
or size
are invalid or unknown, we default to h3
.
Next steps
- Verify that the new
Text
component with variant="headline"
has an appropriate heading level. Refer to MDN for more info on heading levels.
- Make adjustments if needed.
Link
Link
as a button (no href
) - No longer supported
Expand for details
See Link documentation
Summary
In Anvil2, the href
attribute should always be used with a Link
. It should not be used as a link-styled button element. The next best option would be to use a Button
with appearance="ghost"
.
Next steps
- Replace the
Link
with a Button
if it is meant to behave like a button.
- Or, add the
href
attribute.
Link.disabled
- No longer supported
Expand for details
See Link variations documentation
Summary
In Anvil2, links can no longer be disabled.
Next steps
- Verify if any other changes should be made with your designer
- Add custom styles using
className
, if necessary
Link.negative
- No longer supported
Expand for details
See Link variations documentation
Summary
In Anvil2, links can no longer be negative in order to conform to accessibility best practices. Using only color to represent meaning is problematic for colorblind users, and red text generally has worse contrast ratios with certain background colors.
Next steps
- Verify if any other changes should be made with your designer
- Add custom styles using
className
, if necessary
Tag
Tag.subtle
- No longer supported
Expand for details
See Chip documentation
Summary
In Anvil2, the subtle
prop is no longer supported.
Next steps
- Verify that the new
Chip
component doesn't break the UI.
- Make adjustments if needed.
Tag.color
- No longer supported
Expand for details
See Chip documentation
Summary
In Anvil2, the color
prop only supports color values such as HEX, RGB, HSL, and HSV.
Next steps
- Verify that the new
Chip
component doesn't break the UI.
- Make adjustments if needed.