From 84e99b906749fce636d48db21973893fac68408e Mon Sep 17 00:00:00 2001 From: Mikkel Laursen Date: Sun, 21 Jul 2024 09:19:50 -0400 Subject: [PATCH] chore(codemod): add supported list migrations --- .../(markdown)/migration/v5-to-v6/page.mdx | 154 +++++++++++ packages/codemod/transforms/types.ts | 7 + .../utils/isJsxExpressionContainer.ts | 14 +- .../utils/isPropConditionalExpression.ts | 9 +- .../codemod/transforms/v5-to-v6/README.md | 2 +- .../DeprecatedListItemLinkProps.input.tsx | 26 ++ .../DeprecatedListItemLinkProps.output.tsx | 14 + .../DeprecatedListItemProps.input.tsx | 25 ++ .../DeprecatedListItemProps.output.tsx | 13 + .../__testfixtures__/ListItemLink.input.tsx | 17 ++ .../__testfixtures__/ListItemLink.output.tsx | 15 + .../ListItemTextChildrenProp.input.tsx | 13 + .../ListItemTextChildrenProp.output.tsx | 11 + .../__testfixtures__/SimpleListItem.input.tsx | 45 +++ .../SimpleListItem.output.tsx | 40 +++ .../list/__tests__/update-list-item-props.ts | 13 + .../v5-to-v6/list/update-list-item-props.ts | 256 ++++++++++++++++++ packages/core/src/list/ListSubheader.tsx | 3 + 18 files changed, 659 insertions(+), 18 deletions(-) create mode 100644 packages/codemod/transforms/v5-to-v6/list/__testfixtures__/DeprecatedListItemLinkProps.input.tsx create mode 100644 packages/codemod/transforms/v5-to-v6/list/__testfixtures__/DeprecatedListItemLinkProps.output.tsx create mode 100644 packages/codemod/transforms/v5-to-v6/list/__testfixtures__/DeprecatedListItemProps.input.tsx create mode 100644 packages/codemod/transforms/v5-to-v6/list/__testfixtures__/DeprecatedListItemProps.output.tsx create mode 100644 packages/codemod/transforms/v5-to-v6/list/__testfixtures__/ListItemLink.input.tsx create mode 100644 packages/codemod/transforms/v5-to-v6/list/__testfixtures__/ListItemLink.output.tsx create mode 100644 packages/codemod/transforms/v5-to-v6/list/__testfixtures__/ListItemTextChildrenProp.input.tsx create mode 100644 packages/codemod/transforms/v5-to-v6/list/__testfixtures__/ListItemTextChildrenProp.output.tsx create mode 100644 packages/codemod/transforms/v5-to-v6/list/__testfixtures__/SimpleListItem.input.tsx create mode 100644 packages/codemod/transforms/v5-to-v6/list/__testfixtures__/SimpleListItem.output.tsx create mode 100644 packages/codemod/transforms/v5-to-v6/list/__tests__/update-list-item-props.ts create mode 100644 packages/codemod/transforms/v5-to-v6/list/update-list-item-props.ts diff --git a/apps/docs/src/app/(main)/(markdown)/migration/v5-to-v6/page.mdx b/apps/docs/src/app/(main)/(markdown)/migration/v5-to-v6/page.mdx index d4aee4000a..4ba238f818 100644 --- a/apps/docs/src/app/(main)/(markdown)/migration/v5-to-v6/page.mdx +++ b/apps/docs/src/app/(main)/(markdown)/migration/v5-to-v6/page.mdx @@ -1342,6 +1342,160 @@ The `Link` component updated the following props: ## List +There are a few breaking changes for the list components: + +- The `SimpleListItem` was removed in favor of using an `
  • ` with the + `ListItemChildren` component +- The `ListItem` and `ListItemLink` removed support for the ripple props + and updated the following props: + - The `forceAddonWrap` prop was removed in favor of the `leftAddonForceWrap` + and `rightAddonForceWrap` + - The `textChildren` prop was renamed to `disableTextChildren` +- The `ListItemLink` renamed `component` to `as` + +> !Warn! For Typescript users, the `ListItemLink` no longer allows +> `{ [key: string]: unknown }` when the `as` component is provided. + +### 🔧 update-list-item-props + +```sh +npx @react-md/codemod v5-to-v6/list/update-list-item-props +``` + +Update `textChildren` prop: + +```diff + import { type ReactElement } from "react"; + import { ListItem } from "react-md"; + + export default function Example(): ReactElement { + return ( + <> +- Hello, world! +- Hello, world! +- Hello, world! +- Hello, world! ++ Hello, world! ++ Hello, world! ++ Hello, world! ++ Hello, world! + + ); + } +``` + +Remove ripple props: + +```diff + import { type ReactElement } from "react"; + import { ListItem } from "react-md"; + import styles from "./styles.module.scss"; + + export default function Example(): ReactElement { + return ( + { + // do something + }} +- disableRipple +- disableProgrammaticRipple +- disableEnterClick +- disableSpacebarClick +- disablePressedFallback +- enablePressedAndRipple +- rippleTimeout={100} +- rippleClassName={styles.ripple} +- rippleClassNames={{ enter: "", exit: "" }} +- rippleContainerClassName="example" + > + Hello, world! + + ); + } +``` + +Rename `component` to `as`: + +```diff + export default function Example(): ReactElement { + return ( + <> +- ++ + Link 1 + +- ++ + Link 2 + + +``` + +Remove `SimpleListItem`: + +```diff + import type { ReactElement } from "react"; + import cn from "classnames"; +-import { FavoriteSVGIcon, List, SimpleListItem } from "react-md"; ++import { FavoriteSVGIcon, List, ListItemChildren } from "react-md"; + + import people from "./people"; + +@@ -12,33 +12,35 @@ export default function Demo(): ReactElement { + + + {people.slice(0, 10).map((name) => ( +- +- {name} +- ++ {name} ++
  • + ))} + + + {people.slice(11, 20).map((name) => ( +- +- {name} +- ++
  • ++ {name} ++
  • + ))} +
    + +- Primary Text} +- secondaryText={ +- Secondary Text +- } +- leftAddon={} +- rightAddon={} +- rightAddonType="media" +- > +- Other children +- ++
  • ++ Primary Text} ++ secondaryText={ ++ Secondary Text ++ } ++ leftAddon={} ++ rightAddon={} ++ rightAddonType="media" ++ > ++ Other children ++ ++
  • +
    + + ); +``` + ## Material Icons ## Media diff --git a/packages/codemod/transforms/types.ts b/packages/codemod/transforms/types.ts index 71b322af64..3359c81e7a 100644 --- a/packages/codemod/transforms/types.ts +++ b/packages/codemod/transforms/types.ts @@ -1,4 +1,6 @@ import { + type JSXEmptyExpression, + type JSXExpressionContainer, type ArrayPattern, type ArrowFunctionExpression, type AssignmentPattern, @@ -73,3 +75,8 @@ export type ComponentDefinition = ( | FunctionDeclaration | FunctionExpression ) & { body: BlockStatement }; + +export type NonEmptyJSXExpresson = Exclude< + JSXExpressionContainer["expression"], + JSXEmptyExpression +>; diff --git a/packages/codemod/transforms/utils/isJsxExpressionContainer.ts b/packages/codemod/transforms/utils/isJsxExpressionContainer.ts index 14a2832a02..b87804b986 100644 --- a/packages/codemod/transforms/utils/isJsxExpressionContainer.ts +++ b/packages/codemod/transforms/utils/isJsxExpressionContainer.ts @@ -1,19 +1,11 @@ -import { - type JSCodeshift, - type JSXEmptyExpression, - type JSXExpressionContainer, -} from "jscodeshift"; - -type DefinedExpression = Exclude< - JSXExpressionContainer["expression"], - JSXEmptyExpression ->; +import { type JSCodeshift, type JSXExpressionContainer } from "jscodeshift"; +import { type NonEmptyJSXExpresson } from "../types"; export function isJsxExpressionContainer( j: JSCodeshift, value: unknown ): value is JSXExpressionContainer & { - expression: DefinedExpression; + expression: NonEmptyJSXExpresson; } { return ( j.JSXExpressionContainer.check(value) && diff --git a/packages/codemod/transforms/utils/isPropConditionalExpression.ts b/packages/codemod/transforms/utils/isPropConditionalExpression.ts index ad56570028..547183d0f0 100644 --- a/packages/codemod/transforms/utils/isPropConditionalExpression.ts +++ b/packages/codemod/transforms/utils/isPropConditionalExpression.ts @@ -1,18 +1,15 @@ import { - type JSXText, - type Identifier, type JSXAttribute, type JSXExpressionContainer, - type Literal, - type NumericLiteral, - type StringLiteral, + type BooleanLiteral, } from "jscodeshift"; +import { type NonEmptyJSXExpresson } from "../types"; export function isPropConditionalExpression( attr: JSXAttribute ): attr is JSXAttribute & { value: JSXExpressionContainer & { - expression: Identifier | Literal | StringLiteral | NumericLiteral | JSXText; + expression: Exclude; }; } { return ( diff --git a/packages/codemod/transforms/v5-to-v6/README.md b/packages/codemod/transforms/v5-to-v6/README.md index cac697cd99..3554a85d65 100644 --- a/packages/codemod/transforms/v5-to-v6/README.md +++ b/packages/codemod/transforms/v5-to-v6/README.md @@ -96,7 +96,7 @@ build times by switching to `@react-md/core/{{FILE}}`. - [x] icon - [ ] layout - [x] link -- [ ] list +- [x] list - [x] material-icons - [ ] media - [ ] menu diff --git a/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/DeprecatedListItemLinkProps.input.tsx b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/DeprecatedListItemLinkProps.input.tsx new file mode 100644 index 0000000000..73faf890db --- /dev/null +++ b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/DeprecatedListItemLinkProps.input.tsx @@ -0,0 +1,26 @@ +import { type ReactElement } from "react"; +import { ListItemLink } from "react-md"; +import styles from "./styles.module.scss"; + +export default function Example(): ReactElement { + return ( + { + // do something + }} + disableRipple + disableProgrammaticRipple + disableEnterClick + disableSpacebarClick + disablePressedFallback + enablePressedAndRipple + rippleTimeout={100} + rippleClassName={styles.ripple} + rippleClassNames={{ enter: "", exit: "" }} + rippleContainerClassName="example" + > + Hello, world! + + ); +} diff --git a/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/DeprecatedListItemLinkProps.output.tsx b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/DeprecatedListItemLinkProps.output.tsx new file mode 100644 index 0000000000..5d6dea0d82 --- /dev/null +++ b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/DeprecatedListItemLinkProps.output.tsx @@ -0,0 +1,14 @@ +import { type ReactElement } from "react"; +import { ListItemLink } from "react-md"; +import styles from "./styles.module.scss"; + +export default function Example(): ReactElement { + return ( + ( { + // do something + }}>Hello, world! + ) + ); +} diff --git a/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/DeprecatedListItemProps.input.tsx b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/DeprecatedListItemProps.input.tsx new file mode 100644 index 0000000000..299dd7375d --- /dev/null +++ b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/DeprecatedListItemProps.input.tsx @@ -0,0 +1,25 @@ +import { type ReactElement } from "react"; +import { ListItem } from "react-md"; +import styles from "./styles.module.scss"; + +export default function Example(): ReactElement { + return ( + { + // do something + }} + disableRipple + disableProgrammaticRipple + disableEnterClick + disableSpacebarClick + disablePressedFallback + enablePressedAndRipple + rippleTimeout={100} + rippleClassName={styles.ripple} + rippleClassNames={{ enter: "", exit: "" }} + rippleContainerClassName="example" + > + Hello, world! + + ); +} diff --git a/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/DeprecatedListItemProps.output.tsx b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/DeprecatedListItemProps.output.tsx new file mode 100644 index 0000000000..8fed295a0e --- /dev/null +++ b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/DeprecatedListItemProps.output.tsx @@ -0,0 +1,13 @@ +import { type ReactElement } from "react"; +import { ListItem } from "react-md"; +import styles from "./styles.module.scss"; + +export default function Example(): ReactElement { + return ( + ( { + // do something + }}>Hello, world! + ) + ); +} diff --git a/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/ListItemLink.input.tsx b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/ListItemLink.input.tsx new file mode 100644 index 0000000000..769eebff7a --- /dev/null +++ b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/ListItemLink.input.tsx @@ -0,0 +1,17 @@ +import { type ReactElement } from "react"; +import { ListItemLink } from "react-md"; + +import { CustomLink } from "./CustomLink"; + +export default function Example(): ReactElement { + return ( + <> + + Link 1 + + + Link 2 + + + ); +} diff --git a/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/ListItemLink.output.tsx b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/ListItemLink.output.tsx new file mode 100644 index 0000000000..fcc927f770 --- /dev/null +++ b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/ListItemLink.output.tsx @@ -0,0 +1,15 @@ +import { type ReactElement } from "react"; +import { ListItemLink } from "react-md"; + +import { CustomLink } from "./CustomLink"; + +export default function Example(): ReactElement { + return (<> + + Link 1 + + + Link 2 + + ); +} diff --git a/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/ListItemTextChildrenProp.input.tsx b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/ListItemTextChildrenProp.input.tsx new file mode 100644 index 0000000000..08d9349860 --- /dev/null +++ b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/ListItemTextChildrenProp.input.tsx @@ -0,0 +1,13 @@ +import { type ReactElement } from "react"; +import { ListItem } from "react-md"; + +export default function Example(): ReactElement { + return ( + <> + Hello, world! + Hello, world! + Hello, world! + Hello, world! + + ); +} diff --git a/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/ListItemTextChildrenProp.output.tsx b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/ListItemTextChildrenProp.output.tsx new file mode 100644 index 0000000000..4c2b829c5c --- /dev/null +++ b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/ListItemTextChildrenProp.output.tsx @@ -0,0 +1,11 @@ +import { type ReactElement } from "react"; +import { ListItem } from "react-md"; + +export default function Example(): ReactElement { + return (<> + Hello, world! + Hello, world! + Hello, world! + Hello, world! + ); +} diff --git a/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/SimpleListItem.input.tsx b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/SimpleListItem.input.tsx new file mode 100644 index 0000000000..3f62241e55 --- /dev/null +++ b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/SimpleListItem.input.tsx @@ -0,0 +1,45 @@ +import type { ReactElement } from "react"; +import cn from "classnames"; +import { FavoriteSVGIcon, List, SimpleListItem } from "react-md"; + +import people from "./people"; + +import Container from "./Container"; +import styles from "./NonInteractable.module.scss"; + +export default function Demo(): ReactElement { + return ( + + + {people.slice(0, 10).map((name) => ( + + {name} + + ))} + + + {people.slice(11, 20).map((name) => ( + + {name} + + ))} + + + Primary Text} + secondaryText={ + Secondary Text + } + leftAddon={} + rightAddon={} + rightAddonType="media" + > + Other children + + + + ); +} diff --git a/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/SimpleListItem.output.tsx b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/SimpleListItem.output.tsx new file mode 100644 index 0000000000..34c93cb285 --- /dev/null +++ b/packages/codemod/transforms/v5-to-v6/list/__testfixtures__/SimpleListItem.output.tsx @@ -0,0 +1,40 @@ +import type { ReactElement } from "react"; +import cn from "classnames"; +import { FavoriteSVGIcon, List, ListItemChildren } from "react-md"; + +import people from "./people"; + +import Container from "./Container"; +import styles from "./NonInteractable.module.scss"; + +export default function Demo(): ReactElement { + return ( + ( + + {people.slice(0, 10).map((name) => ( +
  • + {name} +
  • + ))} +
    + + {people.slice(11, 20).map((name) => ( +
  • + {name} +
  • + ))} +
    + +
  • Primary Text} + secondaryText={ + Secondary Text + } + leftAddon={} + rightAddon={} + rightAddonType="media">Other children +
  • +
    +
    ) + ); +} diff --git a/packages/codemod/transforms/v5-to-v6/list/__tests__/update-list-item-props.ts b/packages/codemod/transforms/v5-to-v6/list/__tests__/update-list-item-props.ts new file mode 100644 index 0000000000..d9a87df83e --- /dev/null +++ b/packages/codemod/transforms/v5-to-v6/list/__tests__/update-list-item-props.ts @@ -0,0 +1,13 @@ +import { defineTest } from "jscodeshift/src/testUtils"; + +const test = (fixture: string): void => { + defineTest(__dirname, "update-list-item-props", null, fixture, { + parser: "tsx", + }); +}; + +test("DeprecatedListItemProps"); +test("ListItemTextChildrenProp"); +test("ListItemLink"); +test("DeprecatedListItemLinkProps"); +test("SimpleListItem"); diff --git a/packages/codemod/transforms/v5-to-v6/list/update-list-item-props.ts b/packages/codemod/transforms/v5-to-v6/list/update-list-item-props.ts new file mode 100644 index 0000000000..f00cb42375 --- /dev/null +++ b/packages/codemod/transforms/v5-to-v6/list/update-list-item-props.ts @@ -0,0 +1,256 @@ +import { + type API, + type Collection, + type FileInfo, + type JSCodeshift, + type JSXExpressionContainer, + type Options, +} from "jscodeshift"; +import { type JSXAttributes } from "../../types"; +import { addImportSpecifiers } from "../../utils/addImportSpecifiers"; +import { createJsxElement } from "../../utils/createJsxElement"; +import { getPropName } from "../../utils/getPropName"; +import { isJsxExpressionContainer } from "../../utils/isJsxExpressionContainer"; +import { isPropBooleanExpression } from "../../utils/isPropBooleanExpression"; +import { isPropEnabled } from "../../utils/isPropEnabled"; +import { negateExpression } from "../../utils/negateExpression"; +import { removeProps } from "../../utils/removeProps"; +import { renameImportSpecifier } from "../../utils/renameImportSpecifier"; +import { traverseImportSpecifiers } from "../../utils/traverseImportSpecifiers"; +import { REMOVED_INTERACTION_PROPS } from "../interaction/constants"; + +interface UpdateListItemPropsOptions { + j: JSCodeshift; + root: Collection; + name: string; + renames?: Record; +} + +function updateListItemProps(options: UpdateListItemPropsOptions): void { + const { j, root, name, renames } = options; + + traverseImportSpecifiers({ + j, + root, + name, + }).forEach((name) => { + removeProps({ + root, + props: REMOVED_INTERACTION_PROPS, + component: name, + }); + + root + .find(j.JSXOpeningElement, { name: { name } }) + .forEach((jsxOpeningElement) => { + const props: JSXAttributes = []; + jsxOpeningElement.node.attributes?.forEach((attr) => { + if (!j.JSXAttribute.check(attr)) { + props.push(attr); + return; + } + + const name = getPropName(attr); + switch (name) { + // convert: + // - `forceAddonWrap={false}` -> nothing + // - `forceAddonWrap`/`forceAddonWrap={true}` -> `leftAddonForceWrap rightAddonForceWrap` + // - `forceAddonWrap={flag}` -> `leftAddonForceWrap={flag} rightAddonForceWrap={flag}` + case "forceAddonWrap": { + if ( + isPropBooleanExpression(attr) && + !attr.value.expression.value + ) { + return; + } + + let value: JSXExpressionContainer | null = null; + if (isJsxExpressionContainer(j, attr.value)) { + ({ value } = attr); + } + + props.push( + j.jsxAttribute(j.jsxIdentifier("leftAddonForceWrap"), value), + j.jsxAttribute(j.jsxIdentifier("rightAddonForceWrap"), value) + ); + break; + } + + // convert: + // - `textChildren`/`textChildren={true}` -> nothing + // - `textChildren={false}` to `disableTextChildren` + // - `textChildren={someFlag}` to `disableTextChildren={!someFlag}` + case "textChildren": { + if ( + !isJsxExpressionContainer(j, attr.value) || + isPropEnabled(attr) + ) { + return; + } + + let value: JSXExpressionContainer | null = null; + if (!isPropBooleanExpression(attr)) { + value = j.jsxExpressionContainer( + negateExpression({ j, expr: attr.value.expression }) + ); + } + + props.push( + j.jsxAttribute(j.jsxIdentifier("disableTextChildren"), value) + ); + break; + } + + case "threeLines": + attr.name.name = "multiline"; + props.push(attr); + break; + + default: { + const rename = renames?.[name]; + if (rename) { + attr.name.name = rename; + } + + props.push(attr); + } + } + }); + + jsxOpeningElement.node.attributes = props; + }); + }); +} + +const LIST_ITEM_CHILDREN_PROPS = new Set([ + "textClassName", + "secondaryTextClassName", + "primaryText", + "secondaryText", + "leftAddon", + "leftAddonType", + "leftAddonPosition", + "rightAddon", + "rightAddonType", + "rightAddonPosition", + + // these are deprecated, but will be handled by the `updateListItemProps` + "threeLines", + "textChildren", +]); + +interface ConvertSimpleListItemToListItemChildrenOptions { + j: JSCodeshift; + root: Collection; + imports: Set; +} + +function convertSimpleListItemToListItemChildren( + options: ConvertSimpleListItemToListItemChildrenOptions +): void { + const { j, root, imports } = options; + traverseImportSpecifiers({ + j, + root, + name: "SimpleListItem", + remove: true, + }).forEach((name) => { + imports.add("ListItemChildren"); + + root + .find(j.JSXElement, { openingElement: { name: { name } } }) + .forEach((jsxElement) => { + const props: JSXAttributes = []; + const childrenProps: JSXAttributes = []; + jsxElement.node.openingElement.attributes?.forEach((attr) => { + if (j.JSXSpreadAttribute.check(attr)) { + props.push(attr); + return; + } + + const name = getPropName(attr); + if (LIST_ITEM_CHILDREN_PROPS.has(name)) { + childrenProps.push(attr); + return; + } + + switch (name) { + case "height": + case "clickable": + case "disabled": + case "disabledOpacity": + break; + default: + props.push(attr); + } + }); + + j(jsxElement).replaceWith( + createJsxElement({ + j, + name: "li", + props, + children: [ + createJsxElement({ + j, + name: "ListItemChildren", + props: childrenProps, + children: jsxElement.node.children, + }), + ], + }) + ); + }); + }); + + renameImportSpecifier({ + j, + root, + from: "SimpleListItemProps", + to: "ListItemChildrenProps", + }); +} + +export default function transformer( + file: FileInfo, + api: API, + options: Options +): string { + const j = api.jscodeshift; + const root = j(file.source); + const printOptions = options.printOptions; + + const imports = new Set(); + updateListItemProps({ + j, + root, + name: "ListItem", + }); + updateListItemProps({ + j, + root, + name: "ListItemLink", + renames: { + component: "as", + }, + }); + + convertSimpleListItemToListItemChildren({ + j, + root, + imports, + }); + updateListItemProps({ + j, + root, + name: "ListItemChildren", + }); + + addImportSpecifiers({ + j, + root, + imports, + }); + + return root.toSource(printOptions); +} diff --git a/packages/core/src/list/ListSubheader.tsx b/packages/core/src/list/ListSubheader.tsx index 20958825ab..4d7aa0abac 100644 --- a/packages/core/src/list/ListSubheader.tsx +++ b/packages/core/src/list/ListSubheader.tsx @@ -30,6 +30,9 @@ export function listSubheader( return cnb(styles({ inset }), className); } +/** + * @since 6.0.0 The `role` prop defaults to `"presentation"` + */ export interface ListSubheaderProps extends HTMLAttributes, ListSubheaderClassNameOptions {