From 97208a0b0e67f4948553306ffd3054e722a97482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 1 Jul 2024 16:37:58 -0400 Subject: [PATCH] BREAKING: Use `render` prop instead of `as` for composition --- CHANGELOG.md | 4 ++ package-lock.json | 4 +- package.json | 2 +- src/avatar/avatar.tsx | 4 +- src/button/button.tsx | 5 +- src/heading/heading.tsx | 3 +- src/loading/loading.tsx | 27 +++---- src/menu/menu.test.tsx | 2 +- src/menu/menu.tsx | 96 ++++++++++++------------- src/modal/modal-examples.stories.tsx | 10 ++- src/modal/modal.tsx | 103 ++++++++++++++------------- src/prose/prose.tsx | 8 +-- src/tabs/tabs.test.tsx | 16 ++--- src/tabs/tabs.tsx | 54 +++++++------- src/tooltip/tooltip.tsx | 37 +++++----- src/utils/common-types.ts | 20 ++++++ src/utils/polymorphism.ts | 27 ++----- 17 files changed, 216 insertions(+), 206 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5a47f932..5549d7d6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ Reactist follows [semantic versioning](https://semver.org/) and doesn't introduce breaking changes (API-wise) in minor or patch releases. However, the appearance of a component might change in a minor or patch release so keep an eye on redesigns and make sure your app still looks and feels like you expect it. +# v25.0.0-beta.1 + +- [BREAKING] User an explicit `render` prop for composition, instead of the `as` prop + # v24.2.0-beta - [Fix] Include changes from [v23.3.0](#v2330) in the beta release diff --git a/package-lock.json b/package-lock.json index b5307c84c..394fad260 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@doist/reactist", - "version": "24.2.0-beta", + "version": "25.0.0-beta.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@doist/reactist", - "version": "24.2.0-beta", + "version": "25.0.0-beta.1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 9d0063b83..5a0efadfe 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "email": "henning@doist.com", "url": "http://doist.com" }, - "version": "24.2.0-beta", + "version": "25.0.0-beta.1", "license": "MIT", "homepage": "https://github.com/Doist/reactist#readme", "repository": { diff --git a/src/avatar/avatar.tsx b/src/avatar/avatar.tsx index f0081701c..c52f0a63f 100644 --- a/src/avatar/avatar.tsx +++ b/src/avatar/avatar.tsx @@ -5,6 +5,7 @@ import { getInitials, emailToIndex } from './utils' import { getClassNames, ResponsiveProp } from '../utils/responsive-props' import styles from './avatar.module.css' import { Box } from '../box' +import type { ObfuscatedClassName } from '../utils/common-types' const AVATAR_COLORS = [ '#fcc652', @@ -29,10 +30,9 @@ const AVATAR_COLORS = [ type AvatarSize = 'xxs' | 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl' | 'xxxl' -type Props = { +type Props = ObfuscatedClassName & { /** @deprecated Please use `exceptionallySetClassName` */ className?: string - exceptionallySetClassName?: string /** @deprecated */ colorList?: string[] size?: ResponsiveProp diff --git a/src/button/button.tsx b/src/button/button.tsx index 23a58e395..488856738 100644 --- a/src/button/button.tsx +++ b/src/button/button.tsx @@ -1,6 +1,7 @@ import * as React from 'react' import { BaseButton } from '../base-button' import type { BaseButtonProps } from '../base-button' +import type { ObfuscatedClassName } from '../utils/common-types' type NativeButtonProps = Omit< React.AllHTMLAttributes, @@ -8,9 +9,9 @@ type NativeButtonProps = Omit< > export type ButtonProps = NativeButtonProps & - BaseButtonProps & { + BaseButtonProps & + ObfuscatedClassName & { type?: 'button' | 'submit' | 'reset' - exceptionallySetClassName?: string } /** diff --git a/src/heading/heading.tsx b/src/heading/heading.tsx index 4f2887bba..898f3ac32 100644 --- a/src/heading/heading.tsx +++ b/src/heading/heading.tsx @@ -2,8 +2,7 @@ import * as React from 'react' import { getClassNames } from '../utils/responsive-props' import { Box } from '../box' import styles from './heading.module.css' -import type { ObfuscatedClassName } from '../utils/polymorphism' -import type { Tone } from '../utils/common-types' +import type { ObfuscatedClassName, Tone } from '../utils/common-types' import type { BoxProps } from '../box' type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6 | '1' | '2' | '3' | '4' | '5' | '6' diff --git a/src/loading/loading.tsx b/src/loading/loading.tsx index ce8db51f1..b97c2ce24 100644 --- a/src/loading/loading.tsx +++ b/src/loading/loading.tsx @@ -1,6 +1,7 @@ import * as React from 'react' import { Box } from '../box' import { Spinner } from '../spinner' +import type { ObfuscatedClassName } from '../utils/common-types' type Size = 'xsmall' | 'small' | 'medium' | 'large' @@ -9,19 +10,19 @@ type NativeProps = Omit< 'className' | 'aria-describedby' | 'aria-label' | 'aria-labelledby' | 'role' | 'size' > -type LoadingProps = NativeProps & { - /** - * The size of the loading spinner. - * @default 'small' - */ - size?: Size - /** - * A escape hatch in case you need to provide a custom class name to the container element. - */ - exceptionallySetClassName?: string - /** Identifies the element (or elements) that describes the loading component for assistive technologies. */ - 'aria-describedby'?: string -} & ( +type LoadingProps = NativeProps & + ObfuscatedClassName & { + /** + * The size of the loading spinner. + * @default 'small' + */ + size?: Size + + /** + * Identifies the element (or elements) that describes the loading component for assistive technologies. + */ + 'aria-describedby'?: string + } & ( | { /** Defines a string value that labels the current loading component for assistive technologies. */ 'aria-label': string diff --git a/src/menu/menu.test.tsx b/src/menu/menu.test.tsx index 2bbd4a8b7..16c9a4d5e 100644 --- a/src/menu/menu.test.tsx +++ b/src/menu/menu.test.tsx @@ -158,7 +158,7 @@ describe('Menu', () => { Links - + }> Github repo diff --git a/src/menu/menu.tsx b/src/menu/menu.tsx index 225be21ff..a4ce2b07f 100644 --- a/src/menu/menu.tsx +++ b/src/menu/menu.tsx @@ -1,18 +1,6 @@ import * as React from 'react' import classNames from 'classnames' -import { polymorphicComponent } from '../utils/polymorphism' - -// -// Reactist menu is a thin wrapper around Ariakit's menu components. This may or may not be -// temporary. Our goal is to make it transparent for the users of Reactist of this implementation -// detail. We may change in the future the external lib we use, or even implement it all internally, -// as long as we keep the same outer interface as intact as possible. -// -// Around the heavy lifting of the external lib we just add some features to better integrate the -// menu to Reactist's more opinionated approach (e.g. using our button with its custom variants and -// other features, easily show keyboard shortcuts in menu items, etc.) -// import { Portal, MenuStore, @@ -22,13 +10,14 @@ import { Menu as AriakitMenu, MenuGroup as AriakitMenuGroup, MenuItem as AriakitMenuItem, + MenuItemProps as AriakitMenuItemProps, MenuButton as AriakitMenuButton, MenuButtonProps as AriakitMenuButtonProps, + Role, } from '@ariakit/react' import './menu.less' - -type NativeProps = React.DetailedHTMLProps, E> +import type { NativeProps, ObfuscatedClassName } from '../utils/common-types' type MenuContextState = { menuStore: MenuStore @@ -50,7 +39,7 @@ const MenuContext = React.createContext( // Menu // -type MenuProps = Omit & { +interface MenuProps extends Omit { /** * The `Menu` must contain a `MenuList` that defines the menu options. It must also contain a * `MenuButton` that triggers the menu to be opened or closed. @@ -88,12 +77,14 @@ function Menu({ children, onItemSelect, ...props }: MenuProps) { // MenuButton // -type MenuButtonProps = Omit +interface MenuButtonProps + extends Omit, + ObfuscatedClassName {} /** * A button to toggle a dropdown menu open or closed. */ -const MenuButton = polymorphicComponent<'button', MenuButtonProps>(function MenuButton( +const MenuButton = React.forwardRef(function MenuButton( { exceptionallySetClassName, ...props }, ref, ) { @@ -111,39 +102,45 @@ const MenuButton = polymorphicComponent<'button', MenuButtonProps>(function Menu // // ContextMenuTrigger // -const ContextMenuTrigger = polymorphicComponent<'div', unknown>(function ContextMenuTrigger( - { as: component = 'div', ...props }, - ref, -) { - const { setAnchorRect, menuStore } = React.useContext(MenuContext) - - const handleContextMenu = React.useCallback( - function handleContextMenu(event: React.MouseEvent) { - event.preventDefault() - setAnchorRect({ x: event.clientX, y: event.clientY }) - menuStore.show() - }, - [setAnchorRect, menuStore], - ) - const isOpen = menuStore.useState('open') - React.useEffect(() => { - if (!isOpen) setAnchorRect(null) - }, [isOpen, setAnchorRect]) +interface ContextMenuTriggerProps extends ObfuscatedClassName, NativeProps { + render?: React.ReactElement +} - return React.createElement(component, { ...props, onContextMenu: handleContextMenu, ref }) -}) +const ContextMenuTrigger = React.forwardRef( + function ContextMenuTrigger({ render, ...props }, ref) { + const { setAnchorRect, menuStore } = React.useContext(MenuContext) + + const handleContextMenu = React.useCallback( + function handleContextMenu(event: React.MouseEvent) { + event.preventDefault() + setAnchorRect({ x: event.clientX, y: event.clientY }) + menuStore.show() + }, + [setAnchorRect, menuStore], + ) + + const isOpen = menuStore.useState('open') + React.useEffect(() => { + if (!isOpen) setAnchorRect(null) + }, [isOpen, setAnchorRect]) + + return + }, +) // // MenuList // -type MenuListProps = Omit +interface MenuListProps + extends Omit, + ObfuscatedClassName {} /** * The dropdown menu itself, containing a list of menu items. */ -const MenuList = polymorphicComponent<'div', MenuListProps>(function MenuList( +const MenuList = React.forwardRef(function MenuList( { exceptionallySetClassName, modal = true, ...props }, ref, ) { @@ -170,7 +167,7 @@ const MenuList = polymorphicComponent<'div', MenuListProps>(function MenuList( // MenuItem // -type MenuItemProps = { +interface MenuItemProps extends AriakitMenuItemProps, ObfuscatedClassName { /** * An optional value given to this menu item. It is passed on to the parent `Menu`'s * `onItemSelect` when you provide that instead of (or alongside) providing individual @@ -178,11 +175,6 @@ type MenuItemProps = { */ value?: string - /** - * The content inside the menu item. - */ - children: React.ReactNode - /** * When `true` the menu item is disabled and won't be selectable or be part of the keyboard * navigation across the menu options. @@ -230,7 +222,7 @@ type MenuItemProps = { * A menu item inside a menu list. It can be selected by the user, triggering the `onSelect` * callback. */ -const MenuItem = polymorphicComponent<'button', MenuItemProps>(function MenuItem( +const MenuItem = React.forwardRef(function MenuItem( { value, children, @@ -238,7 +230,6 @@ const MenuItem = polymorphicComponent<'button', MenuItemProps>(function MenuItem hideOnSelect = true, onClick, exceptionallySetClassName, - as = 'button', ...props }, ref, @@ -247,7 +238,7 @@ const MenuItem = polymorphicComponent<'button', MenuItemProps>(function MenuItem const { hide } = menuStore const handleClick = React.useCallback( - function handleClick(event: React.MouseEvent) { + function handleClick(event: React.MouseEvent) { onClick?.(event) const onSelectResult: unknown = onSelect && !event.defaultPrevented ? onSelect() : undefined @@ -261,7 +252,6 @@ const MenuItem = polymorphicComponent<'button', MenuItemProps>(function MenuItem return ( (function SubMenu( return ( - + {renderMenuButton} {list} @@ -340,7 +330,9 @@ const SubMenu = React.forwardRef(function SubMenu( // MenuGroup // -type MenuGroupProps = Omit, 'className'> & { +interface MenuGroupProps + extends Omit, 'className'>, + ObfuscatedClassName { /** * A label to be shown visually and also used to semantically label the group. */ @@ -353,7 +345,7 @@ type MenuGroupProps = Omit, 'className'> & { * This group does not add any visual separator. You can do that yourself adding `
` elements * before and/or after the group if you so wish. */ -const MenuGroup = polymorphicComponent<'div', MenuGroupProps>(function MenuGroup( +const MenuGroup = React.forwardRef(function MenuGroup( { label, children, exceptionallySetClassName, ...props }, ref, ) { diff --git a/src/modal/modal-examples.stories.tsx b/src/modal/modal-examples.stories.tsx index a7c587fba..287b5f07c 100644 --- a/src/modal/modal-examples.stories.tsx +++ b/src/modal/modal-examples.stories.tsx @@ -65,9 +65,13 @@ export function ModalWithStandardActionsFooter() { } + render={ + } + aria-label="Open menu" + /> + } /> Edit diff --git a/src/modal/modal.tsx b/src/modal/modal.tsx index 0ee3ed514..5edd2642e 100644 --- a/src/modal/modal.tsx +++ b/src/modal/modal.tsx @@ -13,6 +13,7 @@ import { Box } from '../box' import { Button, ButtonProps } from '../button' import styles from './modal.module.css' +import type { ObfuscatedClassName } from '../utils/common-types' type ModalWidth = 'small' | 'medium' | 'large' | 'xlarge' | 'full' type ModalHeightMode = 'expand' | 'fitContent' @@ -40,25 +41,29 @@ type DivProps = Omit< 'className' | 'children' | `aria-label` | `aria-labelledby` > -export type ModalProps = DivProps & { +export interface ModalProps extends DivProps, ObfuscatedClassName { /** * The content of the modal. */ children: React.ReactNode + /** * Whether the modal is open and visible or not. */ isOpen: boolean + /** * Called when the user triggers closing the modal. */ onDismiss?(): void + /** * A descriptive setting for how wide the modal should aim to be, depending on how much space * it has on screen. * @default 'medium' */ width?: ModalWidth + /** * A descriptive setting for how tall the modal should aim to be. * @@ -74,30 +79,37 @@ export type ModalProps = DivProps & { * the inner layout to ensure scroll, or whatever other strategy you may want. */ height?: ModalHeightMode + /** * Whether to set or not the focus initially to the first focusable element inside the modal. */ autoFocus?: boolean + /** * Controls if the modal is dismissed when pressing "Escape". */ hideOnEscape?: DialogOptions['hideOnEscape'] + /** * Controls if the modal is dismissed when clicking outside the modal body, on the overlay. */ hideOnInteractOutside?: DialogOptions['hideOnInteractOutside'] - /** - * An escape hatch in case you need to provide a custom class name to the container element. - */ - exceptionallySetClassName?: string + /** * An escape hatch in case you need to provide a custom class name to the overlay element. */ exceptionallySetOverlayClassName?: string - /** Defines a string value that labels the current modal for assistive technologies. */ + + /** + * Defines a string value that labels the current modal for assistive technologies. + */ 'aria-label'?: string - /** Identifies the element (or elements) that labels the current modal for assistive technologies. */ + + /** + * Identifies the element (or elements) that labels the current modal for assistive technologies. + */ 'aria-labelledby'?: string + /** * An HTML element or a memoized callback function that returns an HTML element to be used as * the portal element. By default, the portal element will be a `div` element appended to the @@ -148,6 +160,8 @@ export function Modal({ children, portalElement, onKeyDown, + // @ts-expect-error we want to make sure to not pass it to the Dialog component + className, ...props }: ModalProps) { const setOpen = React.useCallback( @@ -227,9 +241,8 @@ export function Modal({ exceptionallySetOverlayClassName, )} /** - * We're using `onPointerDown` instead of `onClick` to prevent - * the modal from closing when the click starts inside the modal - * and ends on the backdrop. + * We're using `onPointerDown` instead of `onClick` to prevent the modal from + * closing when the click starts inside the modal and ends on the backdrop. */ onPointerDown={hideOnInteractOutside ? handleBackdropClick : undefined} ref={backdropRef} @@ -238,17 +251,20 @@ export function Modal({ + } + className={classNames(exceptionallySetClassName, styles.container)} store={store} preventBodyScroll - borderRadius="full" - background="default" - display="flex" - flexDirection="column" - overflow="hidden" - height={height === 'expand' ? 'full' : undefined} - flexGrow={height === 'expand' ? 1 : 0} - className={[exceptionallySetClassName, styles.container]} // Disable focus lock as we set up our own using ReactFocusLock modal={false} autoFocus={false} @@ -275,20 +291,21 @@ export function Modal({ // ModalCloseButton // -export type ModalCloseButtonProps = Omit< - ButtonProps, - | 'type' - | 'children' - | 'variant' - | 'icon' - | 'startIcon' - | 'endIcon' - | 'disabled' - | 'loading' - | 'tabIndex' - | 'width' - | 'align' -> & { +export interface ModalCloseButtonProps + extends Omit< + ButtonProps, + | 'type' + | 'children' + | 'variant' + | 'icon' + | 'startIcon' + | 'endIcon' + | 'disabled' + | 'loading' + | 'tabIndex' + | 'width' + | 'align' + > { /** * The descriptive label of the button. */ @@ -332,25 +349,23 @@ export function ModalCloseButton(props: ModalCloseButtonProps) { // ModalHeader // -export type ModalHeaderProps = DivProps & { +export interface ModalHeaderProps extends DivProps, ObfuscatedClassName { /** * The content of the header. */ children: React.ReactNode + /** * Allows to provide a custom button element, or to omit the close button if set to false. * @see ModalCloseButton */ button?: React.ReactNode | boolean + /** * Whether to render a divider line below the header. * @default false */ withDivider?: boolean - /** - * A escape hatch in case you need to provide a custom class name to the container element. - */ - exceptionallySetClassName?: string } /** @@ -405,15 +420,11 @@ export function ModalHeader({ // ModalBody // -export type ModalBodyProps = DivProps & { +export interface ModalBodyProps extends DivProps, ObfuscatedClassName { /** * The content of the modal body. */ children: React.ReactNode - /** - * A escape hatch in case you need to provide a custom class name to the container element. - */ - exceptionallySetClassName?: string } /** @@ -449,7 +460,7 @@ export function ModalBody({ exceptionallySetClassName, children, ...props }: Mod // ModalFooter // -export type ModalFooterProps = DivProps & { +export interface ModalFooterProps extends DivProps, ObfuscatedClassName { /** * The contant of the modal footer. */ @@ -459,10 +470,6 @@ export type ModalFooterProps = DivProps & { * @default false */ withDivider?: boolean - /** - * A escape hatch in case you need to provide a custom class name to the container element. - */ - exceptionallySetClassName?: string } /** diff --git a/src/prose/prose.tsx b/src/prose/prose.tsx index 9363e5917..2da96c374 100644 --- a/src/prose/prose.tsx +++ b/src/prose/prose.tsx @@ -1,8 +1,9 @@ import * as React from 'react' import { Box } from '../box' import styles from './prose.module.css' +import type { ObfuscatedClassName } from '../utils/common-types' -type ProseProps = { +interface ProseProps extends ObfuscatedClassName { /** * The prose content. * @@ -37,11 +38,6 @@ type ProseProps = { * This does not apply a dark theme on the text. That's still the consumer apps’ responsibility. */ darkModeTypography: boolean - - /** - * An escape hatch in case you need to provide custom styles. - */ - exceptionallySetClassName?: string } /** diff --git a/src/tabs/tabs.test.tsx b/src/tabs/tabs.test.tsx index 841d36b5a..d2865e63c 100644 --- a/src/tabs/tabs.test.tsx +++ b/src/tabs/tabs.test.tsx @@ -46,13 +46,13 @@ describe('Tabs', () => { Tab 2 Tab 3 - + Content of tab 1 - + Content of tab 2 - + Content of tab 3 , @@ -84,13 +84,13 @@ describe('Tabs', () => { Tab 2 Tab 3 - + Content of tab 1 - + Content of tab 2 - + Content of tab 3 , @@ -228,10 +228,10 @@ describe('Tabs', () => { Tab 1 Tab 2 - + }> Content of tab 1 - + }> Content of tab 2 , diff --git a/src/tabs/tabs.tsx b/src/tabs/tabs.tsx index 5cc728a12..3c1b36618 100644 --- a/src/tabs/tabs.tsx +++ b/src/tabs/tabs.tsx @@ -5,11 +5,11 @@ import { Tab as BaseTab, TabList as BaseTabList, TabPanel as BaseTabPanel, + TabPanelProps as BaseTabPanelProps, TabStore, } from '@ariakit/react' import { Inline } from '../inline' -import { polymorphicComponent } from '../utils/polymorphism' -import type { Space } from '../utils/common-types' +import type { NativeProps, ObfuscatedClassName, Space } from '../utils/common-types' import styles from './tabs.module.css' import { Box } from '../box' @@ -20,23 +20,29 @@ type TabsContextValue = Required> & { const TabsContext = React.createContext(null) -type TabsProps = { - /** The `` component must be composed from a `` and corresponding `` components */ +interface TabsProps { + /** + * The `` component must be composed from a `` and corresponding `` + * components + */ children: React.ReactNode + /** - * Determines the look and feel of the tabs. + * Determines the look and feel of the tabs */ variant?: 'themed' | 'neutral' + /** - * The id of the selected tab. Assigning a value makes this a - * controlled component + * The id of the selected tab. Assigning a value makes this a controlled component */ selectedId?: string | null + /** * The tab to initially select. This can be used if the component should not * be a controlled component but needs to have a tab selected */ defaultSelectedId?: string | null + /** * Called with the tab id when a tab is selected */ @@ -67,7 +73,7 @@ function Tabs({ return {children} } -type TabProps = { +interface TabProps extends ObfuscatedClassName { /** The content to render inside of the tab button */ children: React.ReactNode @@ -78,8 +84,8 @@ type TabProps = { /** * Represents the individual tab elements within the group. Each `` must have a corresponding `` component. */ -const Tab = polymorphicComponent<'button', TabProps>(function Tab( - { as, children, id, exceptionallySetClassName, ...props }, +const Tab = React.forwardRef(function Tab( + { children, id, exceptionallySetClassName, ...props }, ref, ): React.ReactElement | null { const tabContextValue = React.useContext(TabsContext) @@ -89,7 +95,7 @@ const Tab = polymorphicComponent<'button', TabProps>(function Tab( const className = classNames(exceptionallySetClassName, styles.tab, styles[`tab-${variant}`]) return ( - + {children} ) @@ -138,24 +144,22 @@ function TabList({ children, space, ...props }: TabListProps): React.ReactElemen const { tabStore, variant } = tabContextValue return ( - // The extra prevents 's negative margins from collapsing when used in a flex container + // The extra
prevents 's negative margins from collapsing when used in a flex container // which will render the track with the wrong height - +
} {...props} > {children} - +
) } -type TabPanelProps = { +interface TabPanelProps extends NativeProps, Pick { /** The content to be rendered inside the tab */ children?: React.ReactNode @@ -168,15 +172,15 @@ type TabPanelProps = { * meaning while inactive tab panels will not be rendered initially, they will remain mounted * once they are active until the entire Tabs tree is unmounted. */ - render?: 'always' | 'active' | 'lazy' + renderMode?: 'always' | 'active' | 'lazy' } /** * Used to define the content to be rendered when a tab is active. Each `` must have a * corresponding `` component. */ -const TabPanel = polymorphicComponent<'div', TabPanelProps, 'omitClassName'>(function TabPanel( - { children, id, as, render = 'always', ...props }, +const TabPanel = React.forwardRef(function TabPanel( + { children, id, renderMode = 'always', ...props }, ref, ): React.ReactElement | null { const tabContextValue = React.useContext(TabsContext) @@ -199,12 +203,12 @@ const TabPanel = polymorphicComponent<'div', TabPanelProps, 'omitClassName'>(fun const { tabStore } = tabContextValue const shouldRender = - render === 'always' || - (render === 'active' && tabIsActive) || - (render === 'lazy' && (tabIsActive || tabRendered)) + renderMode === 'always' || + (renderMode === 'active' && tabIsActive) || + (renderMode === 'lazy' && (tabIsActive || tabRendered)) return shouldRender ? ( - + {children} ) : null diff --git a/src/tooltip/tooltip.tsx b/src/tooltip/tooltip.tsx index 50256c40b..780773074 100644 --- a/src/tooltip/tooltip.tsx +++ b/src/tooltip/tooltip.tsx @@ -11,8 +11,9 @@ import { Box } from '../box' import type { TooltipStoreState } from '@ariakit/react' import styles from './tooltip.module.css' +import type { ObfuscatedClassName } from '../utils/common-types' -type TooltipProps = { +interface TooltipProps extends ObfuscatedClassName { /** * The element that triggers the tooltip. Generally a button or link. * @@ -65,11 +66,6 @@ type TooltipProps = { * @default false */ withArrow?: boolean - - /** - * An escape hatch, in case you need to provide a custom class name to the tooltip. - */ - exceptionallySetClassName?: string } function Tooltip({ @@ -99,23 +95,26 @@ function Tooltip({ <> {isOpen && content ? ( - + } > {withArrow ? : null} {typeof content === 'function' ? content() : content} - + ) : null} ) diff --git a/src/utils/common-types.ts b/src/utils/common-types.ts index ff3bbfc2a..c59e04f8a 100644 --- a/src/utils/common-types.ts +++ b/src/utils/common-types.ts @@ -32,3 +32,23 @@ export interface OpenInNewTab { target?: never rel?: never } + +export type ObfuscatedClassName = { + /** + * Used internally to set the `className` prop of the main container element for this component. + * + * Aside from the different name, the prop behaves the same as the native `className`. The only + * reason for the name change is to discourage applying custom CSS to the design system + * components, which are supposed to _eventually_ provide all the styling features we may need. + * + * This prop is meant to be used only in certain circumstances, when you really need a escape + * hatch to apply custom styles to a component. Before reaching for this feature, try harder to + * see if you can solve your needs with what the design system provides. + * + * For instance, instead of applying layout-related styles to a non-layout component, consider + * wrapping it inside a layout component. + */ + exceptionallySetClassName?: string +} + +export type NativeProps = React.DetailedHTMLProps, E> diff --git a/src/utils/polymorphism.ts b/src/utils/polymorphism.ts index cca9a2ae2..e3957f0a6 100644 --- a/src/utils/polymorphism.ts +++ b/src/utils/polymorphism.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as React from 'react' +import type { ObfuscatedClassName } from './common-types' type Merge = Omit & P2 @@ -10,26 +11,6 @@ type EmptyObject = { type ObfuscateClassNameMode = 'keepClassName' | 'obfuscateClassName' | 'omitClassName' -type ObfuscatedClassName = { - /** - * Used internally to set the `className` prop of the main container element for this component. - * - * Aside from the different name, the prop behaves the same as the native `className`. The only - * reason for the name change is to discourage applying custom CSS to the design system - * components, which are supposed to _eventually_ provide all the styling features we may need. - * - * This prop is meant to be used only in certain circumstances, when you really need a escape - * hatch to apply custom styles to a component. Before reaching for this feature, try harder to - * see if you can solve your needs with what the design system provides. - * - * For instance, instead of applying layout-related styles to a non-layout component, consider - * wrapping it inside a layout component. - * - * @see PolymorphicComponent - */ - exceptionallySetClassName?: string -} - /** * If a set of props include the `className` prop, we replace it with a `exceptionallySetClassName` * prop instead. @@ -176,6 +157,8 @@ interface ForwardRefFunction< * This behaviour can be customized via an optional second generic argument that allows to disable * this feature, or to omit the `className` altogether without replacing it with the obfuscated prop * name. + * + * @deprecated Use Ariakit's composition instead (https://ariakit.org/guide/composition) */ interface PolymorphicComponent< ComponentType extends React.ElementType, @@ -200,7 +183,7 @@ interface PolymorphicComponent< * convenience over merely using React.forwardRef directly, and then manually forcing the resulting * value to be typed using `as PolymorphicComponent<…>`. * - * @see PolymorphicComponent for details about what this type does + * @deprecated Use Ariakit's composition instead (https://ariakit.org/guide/composition) */ function polymorphicComponent< ComponentType extends React.ElementType = 'div', @@ -214,5 +197,5 @@ function polymorphicComponent< > } -export type { PolymorphicComponent, ObfuscatedClassName } +export type { PolymorphicComponent } export { polymorphicComponent }