UNPKG

@workday/canvas-kit-docs

Version:

Documentation components of Canvas Kit components

1,475 lines (1,114 loc) 53.2 kB
# Canvas Kit 5.0 Upgrade Guide Below are the breaking changes made in Canvas Kit v5. Please [reach out](https://github.com/Workday/canvas-kit/issues/new?labels=bug&template=bug.md) if you have any questions about the update. - [Codemod](#codemod) - [General Changes](#general-changes) - [Slash Imports](#slash-imports) - [Canvas Kit Preview](#canvas-kit-preview) - [Type Deprecations and Hierarchy Updates](#type-deprecations-and-hierarchy-updates) - [Canvas Kit CSS Maintenance Mode](#canvas-kit-css-maintenance-mode) - [Prop Interfaces](#prop-interfaces) - [Component Changes](#component-changes) - [Component Promotions](#component-promotions) - [Core](#core) - [Tokens](#tokens) - [Button](#button) - [Card](#card) - [Inputs](#inputs) - [Tabs](#tabs) - [Popper](#popper) - [Popups](#popups) - [Modal](#modal) - [Skeleton](#skeleton) ## Codemod We've introduced a [new codemod package](https://github.com/Workday/canvas-kit/tree/master/modules/codemod) you can use to automatically update your code to work with a majority of the breaking changes in the upgrade from Canvas Kit v4 to v5. Simply run: ```sh > npx @workday/canvas-kit-codemod v5 [path] ``` > Note: This codemod only works on `.js`, `.jsx`, `.ts`, and `.tsx` extensions. You may need to make > some manual changes in other file types (`.json`, `.mdx`, `.md`, etc.). > Note: You may need to run your linter after executing the codemod, as it's resulting formatting > (spacing, quotes, etc.) may not match your project's styling. **Breaking changes accounted for by this codemod will be marked with a 🤖.** **Please verify all changes made by the codemod. As a safety precaution, we recommend committing the changes from the codemod as a single isolated commit (separate from other changes) so you can rollback more easily if necessary.** [Let us know](https://github.com/Workday/canvas-kit/issues/new?labels=bug&template=bug.md) if you encounter any issues or use cases that we've missed. The `@workday/canvas-kit-codemod` package will help us maintain additional codemod transforms to make future upgrades easier. ## General Changes ### Slash Imports Rather than having a separate module for each component, we've moved to a slash imports system. All of our React components are now bundled in one of three modules: - `@workday/canvas-kit-react` - `@workday/canvas-kit-labs-react` - `@workday/canvas-kit-preview-react` > Note: See [Canvas Kit Preview](#canvas-kit-preview) for more information about the new > `@workday/canvas-kit-preview-react` module. Consequently, you'll need to update your import statements: ```tsx // v4 import {TextInput} from '@workday/canvas-kit-react-text-input'; // v5 import {TextInput} from '@workday/canvas-kit-react/text-input'; ``` 🤖 The codemod will update import statements to use the new slash imports syntax. Recall that the codemod only works on `.js`, `.jsx`, `.ts`, and `.tsx` extensions. Other file types will need to be updated manually. --- ### Canvas Kit Preview Due to the broad range of stability in [Canvas Kit Labs](https://github.com/Workday/canvas-kit/tree/master/modules/labs-react) (`@workday/canvas-kit-labs-react`), we've introduced a new module called [Canvas Kit Preview](https://github.com/Workday/canvas-kit/tree/master/modules/preview-react) (`@workday/canvas-kit-preview-react`) to provide consumers with more clarity and confidence when uptaking experimental and upcoming components. The components in Preview have had a full design and accessibility review and are approved for use in product. Their functionality and design are set, but their APIs and/or underlying architecture are still subject to change. Preview serves as a staging ground for components that are ready to use, but may not be up to the high code standards upheld in the Main `@workday/canvas-kit-react` module. Think of Labs as a space for alpha components and Preview as a space for beta components. We've promoted several components from Labs to Preview in v5. See [Component Promotions](#component-promotions) for more details. --- ### Type Deprecations and Hierarchy Updates Canvas Kit v4 supported two type hierarchies, Beta and Legacy in `@workday/canvas-kit-labs-react-core` and `@workday/canvas-kit-react-core` respectively. However, v5 replaces those with a new, responsive type hierarchy in `@workday/canvas-kit-react/tokens`. We are also deprecating and updating our type variants. The v5 codemod handles almost all of these changes for you. That said, you'll want to review the transformation and your UI to ensure everything was updated as you expect. #### Automatic Updates - 🤖 Type Hierarchy Updates All type hierarchy updates are handled by the codemod. The tables below will help you understand the changes and provide a reference as you review your UI. Most teams are using the Beta type tokens in `@workday/canvas-kit-labs-react-core`, but some are using the Legacy type in `@workday/canvas-kit-react-core`. | Legacy Type (px) | Responsive Type (rem) | | ----------------- | ---------------------------------------- | | `dataViz1` (56px) | `levels.title.large` (3.5rem \ 56px) | | `dataViz2` (34px) | `levels.heading.large` (2rem \ 32px) | | `h1` (28px) | `levels.heading.medium` (1.75rem \ 28px) | | `h2` (24px) | `levels.heading.small` (1.5rem \ 24px) | | `h3` (20px) | `levels.body.large` (1.25rem,) \ 20px | | `h4` (16px) | `levels.body.small` (1rem \ 16px) | | `h5` (16px) | `levels.body.small` (1rem \ 16px) | | `body` (14px) | `levels.subtext.large` (0.875rem \ 14px) | | `body2` (13px) | `levels.subtext.medium` (0.75rem \ 12px) | | `small` (12px) | `levels.subtext.medium` (0.75rem \ 12px) | - 🤖 Property Updates All `fontFamily`, `fontSize`, and `fontWeight` property updates are handled by the codemod. | CSS Property | Corresponding Token | Notes | | ------------ | ------------------------------ | --------------------------------------------------------------- | | `fontFamily` | `type.properties.fontFamilies` | `default` (Roboto) and `monospace` (Roboto Mono) are available | | `fontSize` | `type.properties.fontSizes` | please consult the type hierarchies above to map values | | `fontWeight` | `type.properties.fontWeights` | `regular` (400), `medium` (500), and `bold` (700) are available | - 🤖 Variant Updates All `variant` updates _except `link`_ are handled by the codemod. Please see the [variants](#variants) section below for more information. | Variant | Transformation | Notes | | ---------------------- | ------------------------------------------------------------------------------ | --------------------------------------- | | `type.variant.error` | `type.variants.error` | name change only | | `type.variant.hint` | `type.variants.hint` | name change only | | `type.variant.inverse` | `type.variants.inverse` | name change only | | `type.variant.button` | `{fontWeight: type.properties.fontWeights.bold}` | variant deprecated, use type properties | | `type.variant.caps` | `{textTransform: 'uppercase', fontWeight: type.properties.fontWeights.medium}` | variant deprecated, use type properties | | `type.variant.label` | `{fontWeight: type.properties.fontWeights.medium}` | variant deprecated, use type properties | | `type.variant.mono` | `{fontFamily: type.properties.fontFamilies.monospace}` | variant deprecated, use type properties | #### Manual Updates ##### TypeScript Type Updates - `CanvasType` still exists, but the types are quite different and will likely throw errors if you're relying on them. - `CanvasTypeVariant` is now `CanvasTypeVariants` and has changed signicantly - We added `CanvasTypeHierarchy` (for type levels) and `CanvasTypeProperties` (for type properties) ##### Code Deprecations There are only two type deprecations not covered by the codemod: - Type Wrapper components (`H1`-`H5`) have been removed - `link` variant has been removed ##### Type Wrapper Components Migration To migrate, please refer to the hierachy tables about and use the type hierarchy tokens directly. Detailed usage information is available in the [levels section](#levels). ##### Link Variant Migration To migrate, please use the `Hyperlink` component instead. Detailed usage information is available in the [variants section](#variants). #### Type Updates In-Depth Overview The new type tokens introduce a few major changes: - Introducing rem units - Creating a new `type` object structure - Adding new type properties (`fontFamilies`, `fontSizes`, and `fontWeights`) - Updating and replacing variants #### Introducing Rem Units The new type hierarchy uses `rem` units instead of `px`. This update follows the guidance [from the WCAG spec](https://www.w3.org/TR/WCAG21/#resize-text) and provides better support for users who rely on zooming. If you'd like to learn more about `rem` and relative units, you can review this [documentation](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Values_and_units#ems_and_rems). > **Note:** We are using `16px` as our base font-size for these values. This is a browser standard > and also fairly common across Workday. However, _if your body text is set to a value other than > `16px`_, you will need to adjust that value for text to render properly. #### New Type Object Structure The Beta and Legacy type object structures were fairly flat, provided many levels in the type hierarchy, and included quite a few variants. While none of these were bad attributes, our research suggested they created a large amount of confusion. Both designers and engineers were unclear on when to use many of the tokens provided. We restructured the object to help users make more sense of it. The tokens are divided into three main parts: - `levels` (the type hierarchy) - `properties` (`fontFamilies`, `fontSizes`, and `fontWeights`) - `variants` (modifiers for type styles) ##### Levels Type `levels` contain our new type hierarchy. When applying type styles, we recommend using these tokens first. Each size applies `fontFamily`, `fontSize`, `fontWeight`, `lineHeight`, `letterSpacing`, and `color` styles for you, so you can create consistent type quickly and easily. Instead of the previous flat structure, the type hierarchy is now organized in four levels: - `title` (used for large page titles) - `heading` (used for headings and large text) - `body` (used for standard body text) - `subtext` (used for small subtext content or in tight spaces) And each level has three sizes: `large`, `medium`, and `small`. The previous hierarchy often mapped its levels 1:1 with semantic elements. This would often lead to awkward styling, such as this: ```tsx // v4 import {type} from '@workday/canvas-kit-labs-react-core'; // Why is an h2 styled with h3 styles? Is this intentional? Is this a mistake? I don't know. const PageSection = () => { return ( <section> <h2 css={type.h3}>Section Heading</h2> <p css={type.levels.body.small}>Section body text</p> </section> ); }; ``` But this new organization allows the hierarchy to be more flexible and create less confusion around usage. Below is an example: ```tsx // v5 import {type} from '@workday/canvas-kit-react/tokens'; const PageSection = () => { return ( <section> <h2 css={type.levels.heading.medium}>Section Heading</h2> <p css={type.levels.body.small}>Section body text</p> </section> ); }; ``` ##### Properties Most often you will want to reach for `levels`, but sometimes you only need one or two type values for styling. Previously, you had to use the hierarchy to apply these values, which is clunky and implicit. For example, using: `fontSize: type.h2.fontSize,` when all you really want is the token for `24px`. Type `properties` give you an atomic-level of control when you want to explicitly set a particular value. Here's an example using `fontFamilies`, `fontSizes`, and `fontWeights`. > _Note:_ `fontSizes` keys are in pixel values as a convenient reference, but the values are the > base-16 rem equivalent. E.g. `fontSizes[12]` returns `0.75rem`. ```tsx import {type} from '@workday/canvas-kit-react/tokens'; const boldTextStyles = { fontFamily: type.properties.fontFamilies.default, // 'Roboto' fontSize: type.properties.fontSizes[16], // 1rem (16px) fontWeight: type.properties.bold, // 700 }; const mediumMonoStyles = { fontFamily: type.properties.fontFamilies.monospace, // 'Roboto Mono' fontSize: type.properties.fontSizes[12], // 0.75rem (12px) fontWeight: type.properties.medium, // 500 }; ``` ##### Variants **Supported Variants** We're also reducing and simplifying our `variants`. In v5 we will only support: - `error` (used for making errors more visible) - `hint` (used for help text and secondary content) - `inverse` (used for any text on a dark or colored background) > **Note:** The `variant` key has been renamed to `variants` to be consistent with our other key > names. ```tsx //v4 import {type} from '@workday/canvas-kit-labs-react-core'; const errorStyles = type.variant.error; const hintStyles = type.variant.hint; const inverseStyles = type.variant.inverse; // v5 import {type} from '@workday/canvas-kit-react/tokens'; const errorStyles = type.variants.error; const hintStyles = type.variants.hint; const inverseStyles = type.variants.inverse; ``` **Deprecated Variants** We've deprecated a handful of variants: - `button` - `caps` - `label` - `link` - `mono` With the exception of `link`, which is discussed further below, all of these variants can be supported with `properties` and other styles. Here are examples of how to translate each deprecated variant: ```tsx //v4 import {type} from '@workday/canvas-kit-labs-react-core'; // button variant styles const buttonStyles = type.variant.button; // caps variant styles const capsStyles = type.variant.caps; // label variant styles const labelStyles = type.variant.label; // mono variant styles const monoStyles = type.variant.mono; // v5 import {type} from '@workday/canvas-kit-labs-react/tokens'; // button variant styles const buttonStyles = {fontWeight: type.properties.fontWeights.bold}; // caps variant styles const capsStyles = { fontWeight: type.properties.fontWeights.medium, textTransform: 'uppercase', }; // label variant styles const labelStyles = {fontWeight: type.properties.fontWeights.medium}; // mono variant styles const monoStyles = {fontFamily: type.properties.fontFamilies.monospace}; ``` **Link Variant** The `link` variant is also being deprecated in v5. You'll need to use the `Hyperlink` component instead. This is the only manual update needed for the type updates. Below are some examples: ```tsx // v4 import {type} from '@workday/canvas-kit-labs-react-core'; const Link = styled('a')(type.variant.link); return <Link href="https://workday.github.io/canvas-kit">View docs</Link>; // v5 import {Hyperlink} from '@workday/canvas-kit-labs-react/button'; return <Hyperlink href="https://workday.github.io/canvas-kit">View docs</Hyperlink>; ``` > **Note:** If you're mixing styles from type `levels`, you'll need to pull out the `color` style > when applying them to `Hyperlink`. Below is an example. ```tsx // v5 import {type} from '@workday/canvas-kit-labs-react/tokens'; import {Hyperlink} from '@workday/canvas-kit-labs-react/button'; // Remove `color` from type styles to prevent the color from overriding the link color const {color, ...headingLargeStyles} = type.levels.heading.large; const HeadingLink = () => ( <Hyperlink css={headingLargeStyles} href="https://workday.github.io/canvas-kit"> View docs </Hyperlink> ); ``` --- ### Canvas Kit CSS Maintenance Mode Due to the infrequent use of our CSS modules, we've placed them in maintenance mode in v5. Although we'll continue to support `@workday/canvas-kit-css` with bug fixes and significant visual updates, it most likely won't be receiving new components or additional features. This will allow us to provide more focused support and to dedicate our efforts to making bigger and better improvements to our most used components: Canvas Kit React. If you have questions or concerns, please [let us know](https://github.com/Workday/canvas-kit/discussions/new). ### Prop Interfaces Many components were updated to be polymorphic using the `createComponent` utility function. Most components in Canvas Kit extend from an HTML interface and spread extra props onto the HTML element. Since these components are now polymorphic, the exported props no longer extend from an HTML interface since the HTML interface is now determined by an optional `as` prop. It is common to wrap Canvas Kit components with your own component and extend from the Canvas Kit component's prop interface. To support this use-case in addition to polymorphic prop interfaces, `ExtractProp` was introduced. `ExtractProp` understands these polymorphic components and will return the base props in addition to the HTML interface. There is an optional second argument that can override the default HTML interface if your wrapper component uses the `as`. ```tsx // v4 import {TextInput, TextInputProps} from '@workday/canvas-kit-react-text-input'; const FancyTextInput: React.FC<TextInputProps> = props => <TextInput {...props} />; // v5 import {TextInput} from '@workday/canvas-kit-react/text-input'; import {ExtractProps} from '@workday/canvas-kit-react/common'; const FancyTextInput: React.FC<ExtractProps<typeof TextInput>> = props => {}; // v5 via createComponent import {TextInput} from '@workday/canvas-kit-react/text-input'; import {createComponent} from '@workday/canvas-kit-react/common'; const FancyTextInput = createComponent(TextInput)({ displayName: 'FancyTextInput', Component((props) => <TextInput {...props} />) }) ``` Components that made this change: - Button - IconButton - Card - Hyperlink - Select - TextArea - TextInput - Checkbox - Radio - ColorInput - ColorPreview - Modal - Popup - Skeleton - Tabs - Toast ## Component Changes ### Component Promotions #### Promotions from Labs to Preview The following components were promoted from Labs to the new Preview module: - Breadcrumbs - Color Picker - Menu - Select - Side Panel You'll need to update your imports for promoted components (this is _not_ handled by the codemod): ```tsx // v4 import {Breadcrumbs} from '@workday/canvas-kit-labs-react-breadcrumbs'; // v5 import {Breadcrumbs} from '@workday/canvas-kit-preview-react/breadcrumbs'; ``` #### Promotions from Labs to Main Generally, a component will begin in Labs before it's promoted to Preview and eventually to Main (although there is no guarantee a component will advance out of Labs). Given that Preview was just introduced in v5, however, we believe that a few components have incubated long enough in Labs and are ready for Main. The following components have been promoted straight from Labs to Main: - Pagination - Tabs These imports will need to be updated manually as well (this is _not_ handled by the codemod): ```tsx // v4 import {Pagination} from '@workday/canvas-kit-labs-react-pagination'; // v5 import {Pagination} from '@workday/canvas-kit-react/pagination'; ``` --- ### Core #### Remove Labs Core The Labs `core` package has been removed. The few utilities in that package were either promoted, deprecated, or found a better home in another package. These changes are listed below, most of which are handled by the v5 codemod. ##### Automatic Updates - 🤖 Move `StaticStates` component to Main `common` We use `StaticStates` internally for our visual regression tests. It didn't really make sense to live in `core`, and it's stable enough to move to Main, so it now lives in `common`. ```tsx // v4 import {StaticStates} from '@workday/canvas-kit-labs-react-core'; // v5 import {StaticStates} from '@workday/canvas-kit-react/common'; ``` - 🤖 Move `type` tokens to Main `tokens` (formerly `core`) This change is described in more detail in the [Type Section](#type), but suffice to say all `type` imports will be automatically migrated to the Main `token` package by the codemod. ```tsx // v4 import {type} from '@workday/canvas-kit-labs-react-core'; // v5 import {type} from '@workday/canvas-kit-react/tokens'; ``` ##### Manual Updates - Deprecate `space` in favor of `Box` The `space` function was a handy little utility that you could apply to `styled()` components to add space style props. However, with the addition of `Box` it is no longer needed. `Box` provides `space` style props and much more. While this is a manual migration, the process is fairly straight-forward. > **Note:** The `space` props use shorthand prop names for what `Box` provides. For example, `pt` > maps to `paddingTop`, `mr` maps to `marginRight`, and so on. You can see this in the example > below. ```tsx // v4 import {spaceNumbers} from '@workday/canvas-kit-react-core'; import {space} from '@workday/canvas-kit-labs-react-core'; // A styled div with space props const Box = styled('div')(space); const Card = () => <Box p={spaceNumbers.s}>Hello!</Box>; // v5 import {Box} from '@workday/canvas-kit-labs-react/common'; const Card = () => <Box padding="s">Hello!</Box>; ``` #### Rename Core to Tokens The distinction between our core and common packages is often unclear and creates confusion around what should be imported from where. To help alleviate this and better align with our design taxonomy, we've renamed our Main `core` module to `tokens`. These changes are listed below, all of which are handled by the v5 codemod. ##### Automatic Updates - 🤖 Rename Main `core` import statements to `tokens` ```tsx // v4 import {colors} from '@workday/canvas-kit-react-core'; // v5 import {colors} from '@workday/canvas-kit-react/tokens'; ``` #### Input Provider The `InputProvider` wrapper component (used to provide CSS-referencable data attributes for the user's current input method) has been moved from `@workday/canvas-kit-react-core` to `@workday/canvas-kit-react/common`. After renaming our `core` package to `tokens`, it no longer made sense in this location. ```tsx // v4 import {InputProvider} from '@workday/canvas-kit-react-core'; // v5 import {InputProvider} from '@workday/canvas-kit-react/common'; ``` 🤖 The codemod will update your `InputProvider` imports. --- ### Tokens #### Space To better align with our design taxonomy, we've renamed our space tokens in our `tokens` package (formerly in `core`). Instead of relying on `@workday/canvas-space-web` to supply our space values, we're now keeping those values in canvas-kit. We've also taken the opportunity to improve the space types (which were too generic) and their JSDoc hints. The following table describes each update: | Before | After | Change Description | | --------------------- | ------------------------- | -------------------------------- | | `spacing` | `space` | name change only | | `spacingNumbers` | `spaceNumbers` | name change only | | `CanvasSpacing` | `CanvasSpace` | name change and improved types\* | | `CanvasSpacingValue` | `CanvasSpaceValues` | name change only | | `CanvasSpacingNumber` | `CanvasSpaceNumbers` | name change and improved types\* | | `n/a` | `CanvasSpaceNumberValues` | new type! | \* Before, the types were too generic and not very useful. They now better reflect the values they represent. The codemod will handle _almost all_ of these changes for you.That said, you'll want to review your UI to ensure everything was updated as you expect. [Manual Updates](#manual-updates) below. ##### Automatic Updates - 🤖 Rename `spacing` and `spacingNumbers` imports. ```tsx // v4 import {spacing, spacingNumbers} from '@workday/canvas-kit-react-core'; // v5 import {space, spaceNumbers} from '@workday/canvas-kit-react/tokens'; ``` - 🤖 Rename `CanvasSpacing`, `CanvasSpacingValue`, and `CanvasSpacingNumber` imports. ```tsx // v4 import { CanvasSpacing, CanvasSpacingValue, CanvasSpacingNumber, } from '@workday/canvas-kit-react-core'; // v5 import { CanvasSpace, CanvasSpaceValues, CanvasSpaceNumbers, } from '@workday/canvas-kit-react/tokens'; ``` - 🤖 Update token expressions. ```tsx // v4 const iconPadding = spacing.s; // v5 const iconPadding = space.s; ``` - 🤖 Update type expressions. ```tsx // v4 const getSpace = (value: CanvasSpacingValue) => spacing[value]; // v5 const getSpace = (value: CanvasSpaceValue) => space[value]; ``` - 🤖 Update token properties. ```tsx // v4 const iconPadding = canvas.spacing.s; // v5 const iconPadding = canvas.space.s; ``` ##### Manual Updates As previously mentioned, the codemod should handle the vast majority of these updates. However, there are potentially a few changes that will need to be made manually. There may be more beyond what's listed below, but these were the most common issues found in our investigation. - Usage outside of `.js`, `.jsx`, `.ts`, and `.tsx` files - e.g. referencing `spacing` in documentation (`.md` files) - Usage in code comments or JSDoc comments - e.g. `// spacing.s = 16px` - Re-declararation `space` or `spaceNumbers` in the same files - e.g. importing or declaring a new `space` or `spaceNumbers` variable will prevent the codemod from updating the file - Aliasing existing variables as `spacing` or `spaceNumbers` - e.g. `import {spacingNumbers as spacing}` will prevent the codemod from updating the file #### Border Radius We've updated the border radius `zero` token value from `0` to `"0px"` for consistency given that all other border radius tokens use string pixel values. We highly doubt this change will cause any issues, but because the value's type is different, this is technically a breaking change. ```tsx // v4 import {borderRadius} from '@workday/canvas-kit-react-core'; console.log(borderRadius.zero); // returns `0` // v5 import {borderRadius} from '@workday/canvas-kit-react/tokens'; console.log(borderRadius.zero); // returns "0px" ``` --- ### Button #### Recategorization There has been common confusion around the large number of buttons Canvas supports and when each should be used. To improve the usability of our design system, we've been working to recategorize and simplify our button offering. To align with the recent changes in our Figma libraries, we've reorganized our buttons, renaming a few and removing others. The majority of button use cases have been simplified into three different components: `PrimaryButton`, `SecondaryButton`, and `TertiaryButton`, each level representing its emphasis and hierarchy in a UI. We hope this makes your usage of our buttons more intentional and clear. We've provided a codemod to make these changes automatically. **Renamed:** - 🤖 `Button` has been split into `PrimaryButton` and `SecondaryButton` (depending on the `variant` prop). - 🤖 `OutlineButton` (`secondary`) is now `SecondaryButton`. For accessibility reasons, the "outline" styling is the new styling for our secondary buttons. - 🤖 `OutlineButton` (`inverse`) is now `SecondaryButton` with an `inverse` variant. - 🤖 `TextButton` is now `TertiaryButton`. **Removed:** - 🤖 `HighlightButton`. Use `SecondaryButton` instead. - 🤖 `OutlineButton` with `primary` variant. Use `PrimaryButton` or `SecondaryButton` instead. The codemod will replace with `SecondaryButton`. - 🤖 `DropdownButton`. This can be achieved simply using `PrimaryButton` or `SecondaryButton` with an `icon` prop and `iconPosition="right"`. To see examples of code in v4 versus v5, see our [codemod tests](https://github.com/Workday/canvas-kit/tree/master/modules/codemod/lib/v5/spec/recategorizeButtons.spec.ts). #### Exports We've changed some of the Button module's export behavior: - 🤖 The `beta_Button` export was removed. The codemod will rename the import to `Button` instead, preserving local renaming if it exists. ```tsx // v4 import {beta_Button as Button} from '@workday/canvas-kit-react-button'; // v5 import {SecondaryButton} from '@workday/canvas-kit-react/button'; ``` - 🤖 The default export was removed. The codemod will change default imports to named imports. ```tsx // v4 import Button from '@workday/canvas-kit-react-button'; // v5 import {SecondaryButton} from '@workday/canvas-kit-react/button'; ``` #### Enums Enums have been removed from all buttons in favor of string literals. 🤖 The codemod will rewrite any usages of an enum to the string literal. If you used an enum as a type, the codemod will expand to a union of string literals. You could change the union manually instead to be something like `SecondaryButtonProps['variant']` if you prefer not to duplicate the union of string literals. ```tsx // v4 <Button size={Button.Size.Large} />; interface Props { size: ButtonSize; } // v5 <SecondaryButton size="large" />; interface Props { size: 'small' | 'medium' | 'large'; } ``` #### createComponent Buttons now use the `createComponent` utility from the `common` module which forwards `ref` and allows `as` to change the underlying element. ```tsx // v4 <Button buttonRef={ref} />; // v5 <SecondaryButton ref={ref} />; ``` 🤖 The codemod will update all buttons to use `ref` instead of `buttonRef`. Button prop interfaces no longer extend directly from `React.ButtonHTMLAttributes<HTMLButtonElement>`. `createComponent` returns a component that determines the element interface via the `as` prop. This is why Button props no longer contain an element interface directly. If you extend from a Button prop interface, or have code that uses a Button prop interface and accesses properties like `onClick`, you'll need to provide the button attribute yourself in order to avoid TypeScript issues (this doesn't affect runtime). This is not code-moddable since intent cannot be pre-determined. #### Props The exported props no longer extend from the `HTMLButtonElement` interface. Use [ExtractProps](#prop-interfaces) instead. ```tsx interface MyButtonProps extends ButtonProps {} // onClick no longer exists in `ButtonProps`, so TypeScript will complain about onClick not // existing in `MyButtonProps` (`onClick` does exist as a prop on `<Button>`, however) const MyButton = ({children, onClick}: MyButtonProps) => ( <SecondaryButton onClick={onClick}>{children}</SecondaryButton> ); // After interface MyButtonProps extends ExtractProps<typeof SecondaryButton> {} // After (alternate fix) interface MyButtonProps extends ExtractProps<> { onClick?: React.MouseEventHandler<HTMLButtonElement>; } ``` --- ### Card Card is now a [compound component](/getting-started/for-developers/resources/compound-components/) composed of a `Card.Body` and an optional `Card.Heading`. This allows direct access to the heading and body elements. ```tsx // v4 <Card header="Card Title" headerId="header-id"> Card Body </Card> // v5 <Card> <Card.Heading id="header-id">Card Title</Card.Heading> <Card.Body>Card Body</Card.Body> </Card> ``` 🤖 The codemod will attempt to rewrite your JSX to match the new API. Based on what we've seen of how Card has been used, the codemod should handle most of your use cases. It will work if you rename `Card` in the import or style the Card using `styled(Card)`: ```tsx // Handled by the codemod // Default import import Card from '@workday/canvas-kit-react-card' <Card header="Card Title">Card Body</Card> // Renamed import import {Card as CanvasCard} from '@workday/canvas-kit-react-card' <CanvasCard header="Card Title">Card Body</CanvasCard> // Styled card import {Card} from '@workday/canvas-kit-react-card' const StyledCard = styled(Card)(styles) <StyledCard header="Card Title">Card Body</StyledCard> ``` However, the codemod will _not_ work in cases where `header` or `headerId` are spreaded as props or if you're importing a re-exported Canvas Kit Card: ```tsx // NOT handled by the codemod // Spread props import {Card} from '@workday-canvas-kit-card' const props = { header: 'Card Title' } <Card {...props}>Card Body</Card> // Re-exporting import {Card} from './Card' // where `Card` is a re-exported Canvas Kit `Card` ``` #### Props The exported props no longer extend from the `HTMLDivElement` interface. Use [ExtractProps](#prop-interfaces) instead. ```tsx // NOT handled by the codemod // v4 interface MyCard extends CardProps {} // v5 interface MyCard extends ExtractProps<typeof Card> ``` --- ### Inputs All input components in the Main package now support [ref forwarding](https://reactjs.org/docs/forwarding-refs.html) through use of the `createComponent` utility from the `common` module. This includes: - Checkbox - Color Input - Color Preview - Radio - Select - Switch - Text Input - Text Area Additionally, the Select in Preview (formerly in Labs) has also been updated to support ref forwarding. Most of these input components previously supported an `inputRef` prop that could be used to obtain a ref to the component's underlying input element. For example, in v4, if you wanted to obtain a ref to a Text Input's underlying `<input type="text" />` element, you could pass a ref to the component using `inputRef`. In v5, you'll need to use `ref` instead of `inputRef`: ```tsx const ref = React.useRef(null); // v4 <TextInput inputRef={ref} />; // v5 <TextInput ref={ref} />; ``` 🤖 The codemod will update all input components that previously supported `inputRef` to use `ref` instead. For components that previously supported `inputRef`, `ref` is now forwarded to the same underlying element that `inputRef` was applied to previously. Select and Select (Preview) did not support `inputRef` in v4, but now support `ref` in v5. See each component's documentation for information on which element `ref` is forwarded to for that particular component. #### Props Input component prop interfaces no longer extend directly from their underlying element interface (e.g. `TextInputProps` no longer extends from `React.InputHTMLAttributes<HTMLInputElement>`). `createComponent` returns a component that determines the element interface via the `as` prop. This is why input component props no longer contain an element interface directly. If you extend from an input component prop interface, or have code that uses an input component prop interface and accesses properties like `onClick`, you'll need to use [ExtractProps](#prop-interfaces) instead. ```tsx interface MyTextInputProps extends TextInputProps {} // onClick no longer exists in `TextInputProps` so TypeScript will complain about onClick not // existing in `MyTextInputProps` (onClick does exist as a prop on `<TextInput>`, however) const MyTextInput = ({onClick}: MyTextInputProps) => <TextInput onClick={onClick} />; // Fix interface MyTextInputProps extends ExtractProps<typeof TextInput> {} // Alternate fix interface MyTextInputProps extends TextInputProps { onClick?: React.MouseEventHandler<HTMLInputElement>; } ``` As a final note, the following input components were previously class components and, thus, technically supported the `ref` attribute in v4: - Color Input - Color Preview - Select - Select (Preview) - Text Input - Text Area Passing `ref={ref}` to any of these components in v4 would have set `ref.current` to the mounted instance of the entire component ([source](https://reactjs.org/docs/refs-and-the-dom.html#accessing-refs)) rather than the underlying HTML element represented by the component. This is no longer the case in v5. --- ### Tabs In addition to [promoting Tabs](#promotions-from-labs-to-main) out of Labs and into the Main module, we've made a few updates to the component in v5: - `onTabsChange` is now `onActivateTab` and the signature is now: ```tsx function onActivateTab({data: {tab: string}, state: TabsState}): void; ``` - The `Tabs` component no longer accepts the `currentTab` property. Tabs uses a model now. See the component documentation for more details. --- ### Popper In v4, Popper rendered an empty `div` element as a child of the element created by the `PopupStack` and applied `ref` and `elemProps` (extra props) to that `div` element. We've updated Popper in v5 to instead apply `ref` directly to the element created by the `PopupStack`. The `PopupStack` is not React-specific; there is no easy way to spread extra props to this element as we do for other components, so we've discarded `elemProps`. If necessary, you can still target the element using `ref` and modify it using DOM APIs. There is no codemod for this change. --- ### Popups Popup has transitioned to a [compound component](/getting-started/for-developers/resources/compound-components/), along with all Popup-based behavior hooks. What was a `Popup` in v4 is now a `Popup.Card` in v5. The target button and `Popper` components have also been converted to subcomponents of `Popup`. #### v4 ```tsx import React from 'react'; import {Button, DeleteButton} from '@workday/canvas-kit-react-button'; import { Popper, Popup, usePopup, useCloseOnEscape, useCloseOnOutsideClick, } from '@workday/canvas-kit-react-popup'; export const MyPopup = () => { const {targetProps, closePopup, popperProps, stackRef} = usePopup(); useCloseOnOutsideClick(stackRef, closePopup); useCloseOnEscape(stackRef, closePopup); const onDeleteClick = () => { closePopup(); console.log('Delete'); }; return ( <> <DeleteButton {...targetProps}>Delete Item</DeleteButton> <Popper placement={'bottom'} {...popperProps}> <Popup width={400} heading={'Delete Item'} padding={Popup.Padding.s} handleClose={closePopup} > <p>Are you sure you'd like to delete the item titled 'My Item'?</p> <DeleteButton onClick={onDeleteClick}>Delete</DeleteButton> <Button onClick={closePopup}>Cancel</Button> </Popup> </Popper> </> ); }; ``` #### v5 ```tsx import React from 'react'; import {DeleteButton} from '@workday/canvas-kit-react/button'; import { Popup, usePopupModel, useCloseOnEscape, useCloseOnOutsideClick, useInitialFocus, useReturnFocus, } from '@workday/canvas-kit-react/popup'; export const MyPopup = () => { const model = usePopupModel(); useCloseOnOutsideClick(model); useCloseOnEscape(model); useInitialFocus(model); // new useReturnFocus(model); // new const onDeleteClick = () => { console.log('Delete'); }; return ( <Popup model={model}> <Popup.Target as={DeleteButton}>Delete Item</Popup.Target> <Popup.Popper placement={'bottom'}> <Popup.Card width={400} padding="s"> <Popup.CloseIcon aria-label="Close" /> <Popup.Heading>Delete Item</Popup.Heading> <Popup.Body> <p>Are you sure you'd like to delete the item titled 'My Item'?</p> <Popup.CloseButton as={DeleteButton} onClick={onDeleteClick}> Delete </Popup.CloseButton> <Popup.CloseButton>Cancel</Popup.CloseButton> </Popup.Body> </Popup.Card> </Popup.Popper> </Popup> ); }; ``` Most notably, `Popup` is now a container component that takes a `PopupModel` and has several subcomponents like `Popup.Target` and `Popup.CloseButton`. These components are hooked up to the `PopupModel` via React context and have access to state and events. `Popup.Card` is what the v4 `Popup` once was. All behavior hooks, like `useCloseOnEscape` now take a `model` instead of variable parameters. This allowed us to fix some subtle bugs. Using the `PopupModel` means all hooks have access to all Popup state and events without passing in many parameters. #### usePopup and usePopupModel As shown in the example above, `usePopupModel` should now be used instead of `usePopup`. All subcomponents have an associated behavior hook. For example, `Popup.Target` uses a hook called `usePopupTarget`. If you need to use your own components for any reason, these hooks are available. `Popup.Target` and `Popup.CloseButton` do not include any styling. They both render `SecondaryButton` by default. You can change this via the `as` prop. For example, the following will render an unstyled button: ```tsx <Popup.Target as="button">Show</Popup.Target> ``` Pass a `css` prop or a styled button instead to have a custom styled button. You could even pass `IconButton` if you need an icon button to show a Popup instead! If you were using `usePopup` before, here's a list of equivalent APIs: | Before | After | | ----------------------------------------------------------------------- | ------------------------------------- | | `const { popperProps, targetProps, closePopup, stackRef } = usePopup()` | `const model = usePopupModel()` | | `popperProps.open` | `model.state.visibility !== 'hidden'` | | `closePopup()` | `model.events.hide()` | | `stackRef` or `popperProps.ref` | `model.state.stackRef` | | `popperProps.anchorElement` | `model.state.targetRef.current` | | `targetProps.onClick` | `usePopupTarget(model).onClick` | #### New Focus Management A common theme we noticed in uses of Popup in the wild was focus management. Developers were manually passing a `ref` to the target button element and manually returning focus to it when closing the Popup. This use case should now be handled by the new `useReturnFocus` hook. By default, `useReturnFocus` will return focus to the `targetRef` in the model, which is set by `Popup.Target`. This can be overridden by passing `returnFocusRef` to the model on creation. `returnFocusRef` should make your migration easier if `Popup.Target` cannot be used for whatever reason. ```tsx // before const {closePopup} = usePopup(); // passed to some event handler const closeAndReturnFocus = () => { closePopup(); buttonRef.current.focus(); }; // after const model = usePopupModel({ returnFocusRef: buttonRef, // only use if you cannot use `Popup.Target` }); useReturnFocus(model); ``` Another common use case involved focusing something within the Popup when the Popup was shown. The `useInitialFocus` hook was created for this purpose. `useInitialFocus` will set focus to the first focusable element when the Popup becomes visible. This behavior can be overridden by passing `initialFocusRef` to the model. ```tsx // before const {stackRef, popperProps} = usePopup(); useLayoutEffect(() => { if (!open) { return; } stackRef.current.querySelector('input,...').focus(); }, [popperProps.open]); // after const model = usePopupModel({ initialFocusRef: someRef, // only use if you want to explicitly focus on something. Could be useful for an input. }); useInitialFocus(model); ``` #### Managing Positioning If you'd prefer to manage positioning yourself, you can use `Popup.Card` on its own. Without the model and behaviors, the following is equivalent: ```tsx // v4 <Popup width={width} handleClose={onClose} heading="Popup Heading"> Popup Content </Popup> // v5 <Popup.Card with={width}> <Popup.CloseIcon aria-label="Close" onClick={onClose} /> <Popup.Heading>Popup Heading</Popup.Heading> <Popup.Body>Popup Content</Popup.Body> </Popup.Card> ``` `Popup.Card` uses `Card`, which is now using `Box`. Consequently, the following props have changed: | Before | After | | ------------------------------ | ------------------------------------------ | | `padding={Popup.Padding.zero}` | `padding="zero"` or `padding={space.zero}` | | `depth={depth[0]}` | `depth={0}` | | `popupRef={ref}` | `ref={ref}` | #### Transitioning We noticed Popups were used in two different ways: always rendering and conditional rendering. ```tsx // Always rendering const MyPopup = () => { const targetRef = React.useRef(null) const {stackRef, popperProps, targetProps, closePopup} = usePopup() const handleClose = () => { closePopup() targetRef.current.focus() // focus back on target } useCloseOnEscape(stackRef, handleClose) return ( <> <button ref={targetRef} {...targetProps}>Open</button> <Popper {...popperProps}> <Popup> {/* content */} <button onClick={handleClose}>Close</button> </Popup> </Popper> </> ) } // Conditional rendering const MyOpenPopup = ({onClose, targetRef}) => { const {popperProps, closePopup} = usePopup() const handleClose = () => { onClose() closePopup() targetRef.current.focus() // focus back on target } useCloseOnEscape(stackRef, handleClose) return ( <Popper {...popperProps}> <Popup> {/* content */} <button onClick={handleClose}> </Popup> </Popper> ) } const MyPopup = () => { const targetRef = React.useRef(null) const [open, setOpen] = React.useState(false) const onClose = () => { setOpen(false) } return ( <> <button ref={targetRef} onClick={() => { setOpen(true) }}> {open && <MyOpenPopup onClose={onClose} />} </> ) } ``` The difference between the two is subtle, but in the always rendering example, the `usePopup` hook runs on every render. In the conditional rendering example, the `usePopup` hook only runs when `MyPopup` renders it. This means hooks like `useCloseOnEscape` need to function properly in both cases, but `open` is not passed to the hook. This caused subtle bugs. Now, `useCloseOnEscape` is passed a `PopupModel` which has access to the popup's visible state. `useCloseOnEscape` will now only run when the popup is visible, but this means the conditional rendering example will have to do extra work because the `target` is out of scope of the `MyOpenPopup` component. The following is equivalent to the example in v5: ```tsx const MyOpenPopup = ({onClose, targetRef}) => { const model = usePopupModel({ initialVisibility: 'visible', // needed for `useCloseOnEscape` and other hooks returnFocusRef: targetRef, // determines where return focus goes }) useCloseOnEscape(model) useReturnFocus(model) // handles return focus return ( <Popup> <Popup.Popper> <Popup.Card> {/* content */} <Popup.CloseButton as="button">Close</Popup.CloseButton> </Popup> </Popper> </Popup> ) } const MyPopup = () => { const targetRef = React.useRef(null) const [open, setOpen] = React.useState(false) const onClose = () => { setOpen(false) } return ( <> <button ref={targetRef} onClick={() => { setOpen(true) }}> {open && <MyOpenPopup onClose={onClose} />} </> ) } ``` --- ### Modal Modal has transitioned to a [compound component](/getting-started/for-developers/resources/compound-components/). What was `Modal` in v4 is now `Modal.Card` in v5. #### v4 ```tsx import React from 'react'; import {Modal} from '@workday/canvas-kit-react-modal'; import {DeleteButton, Button} from '@workday/canvas-kit-react-button'; const MyModal = () => { const handleDelete = () => { console.log('Deleted item'); }; const {targetProps, modalProps, closeModal} = useModal(); return ( <> <DeleteButton {...targetProps}>Delete Item</DeleteButton> <Modal heading={'Delete Item'} {...modalProps}> <p>Are you sure you want to delete the item?</p> <DeleteButton style={{marginRight: '16px'}} onClick={() => { closeModal(); handleDelete(); }} > Delete </DeleteButton> <Button onClick={closeModal} variant={Button.Variant.Secondary}> Cancel </Button> </Modal> </> ); }; ``` #### v5 ```tsx import React from 'react'; import {Modal} from '@workday/canvas-kit-react/modal'; import {DeleteButton} from '@workday/canvas-kit-react/button'; import {HStack} from '@workday/canvas-kit-labs-react'; const MyModal = () => { const handleDelete = () => { console.log('Deleted item'); }; return ( <Modal> <Modal.Target as={DeleteButton}>Delete Item</Modal.Target> <Modal.Overlay> <Modal.Card> <Modal.CloseIcon aria-label="Close" /> <Modal.Heading>Delete Item</Modal.Heading> <Modal.Body> <p>Are you sure you want to delete the item?</p> <HStack spacing="s"> <Modal.CloseButton as={DeleteButton} onClick={handleDelete}> Delete </Modal.CloseButton> <Modal.CloseButton>Cancel</Modal.CloseButton> </HStack> </Modal.Body> </Modal.Card> </Modal.Overlay> </Modal> ); }; ``` Most notably, `Modal` is now a container component that