diff --git a/README.md b/README.md index 467a62fb..96d149a2 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Several quick start options are available: -- [Download the latest release](https://github.com/coreui/coreui-react/archive/v5.7.0.zip) +- [Download the latest release](https://github.com/coreui/coreui-react/archive/v5.7.1.zip) - Clone the repo: `git clone https://github.com/coreui/coreui-react.git` - Install with [npm](https://www.npmjs.com/): `npm install @coreui/react` - Install with [yarn](https://yarnpkg.com/): `yarn add @coreui/react` diff --git a/lerna.json b/lerna.json index e456a603..d86538ff 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "npmClient": "yarn", "packages": ["packages/*"], - "version": "5.7.0", + "version": "5.7.1", "$schema": "node_modules/lerna/schemas/lerna-schema.json" } diff --git a/package.json b/package.json index e51bf9e0..b7e32b5f 100644 --- a/package.json +++ b/package.json @@ -22,18 +22,18 @@ "test:update": "npm-run-all charts:test:update icons:test:update lib:test:update" }, "devDependencies": { - "@typescript-eslint/parser": "^8.32.1", - "eslint": "^9.27.0", + "@typescript-eslint/parser": "^8.36.0", + "eslint": "^9.31.0", "eslint-config-prettier": "^10.1.5", - "eslint-plugin-prettier": "^5.4.0", + "eslint-plugin-prettier": "^5.5.1", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-unicorn": "^59.0.1", - "globals": "^16.1.0", - "lerna": "^8.2.2", + "globals": "^16.3.0", + "lerna": "^8.2.3", "npm-run-all": "^4.1.5", - "prettier": "^3.5.3", - "typescript-eslint": "^8.32.1" + "prettier": "^3.6.2", + "typescript-eslint": "^8.36.0" }, "overrides": { "gatsby-remark-external-links": { diff --git a/packages/coreui-react/README.md b/packages/coreui-react/README.md index c98015a2..190eafc0 100644 --- a/packages/coreui-react/README.md +++ b/packages/coreui-react/README.md @@ -46,7 +46,7 @@ Several quick start options are available: -- [Download the latest release](https://github.com/coreui/coreui-react/archive/v5.7.0.zip) +- [Download the latest release](https://github.com/coreui/coreui-react/archive/v5.7.1.zip) - Clone the repo: `git clone https://github.com/coreui/coreui-react.git` - Install with [npm](https://www.npmjs.com/): `npm install @coreui/react` - Install with [yarn](https://yarnpkg.com/): `yarn add @coreui/react` diff --git a/packages/coreui-react/package.json b/packages/coreui-react/package.json index 6952b2c9..4e62fee7 100644 --- a/packages/coreui-react/package.json +++ b/packages/coreui-react/package.json @@ -1,6 +1,6 @@ { "name": "@coreui/react", - "version": "5.7.0", + "version": "5.7.1", "description": "UI Components Library for React.js", "keywords": [ "react", @@ -41,21 +41,21 @@ "test:update": "jest --coverage --updateSnapshot" }, "dependencies": { - "@coreui/coreui": "^5.4.0", + "@coreui/coreui": "^5.4.1", "@popperjs/core": "^2.11.8", "prop-types": "^15.8.1" }, "devDependencies": { - "@rollup/plugin-commonjs": "^28.0.3", + "@rollup/plugin-commonjs": "^28.0.6", "@rollup/plugin-node-resolve": "^16.0.1", - "@rollup/plugin-typescript": "^12.1.2", + "@rollup/plugin-typescript": "^12.1.4", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@types/jest": "^29.5.14", - "@types/prop-types": "15.7.14", - "@types/react": "^19.1.4", - "@types/react-dom": "^19.1.5", + "@types/prop-types": "15.7.15", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", "@types/react-transition-group": "^4.4.12", "classnames": "^2.5.1", "cross-env": "^7.0.3", @@ -64,8 +64,8 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-transition-group": "^4.4.5", - "rollup": "^4.41.0", - "ts-jest": "^29.3.4", + "rollup": "^4.45.0", + "ts-jest": "^29.4.0", "tslib": "^2.8.1", "typescript": "^5.8.3" }, diff --git a/packages/coreui-react/src/components/button/CButton.tsx b/packages/coreui-react/src/components/button/CButton.tsx index a12115d5..cbb003d7 100644 --- a/packages/coreui-react/src/components/button/CButton.tsx +++ b/packages/coreui-react/src/components/button/CButton.tsx @@ -74,9 +74,10 @@ export const CButton: PolymorphicRefForwardingComponent<'button', CButtonProps> {...(!rest.href && { type: type })} className={classNames( 'btn', - variant && color ? `btn-${variant}-${color}` : `btn-${variant}`, { - [`btn-${color}`]: color && !variant, + [`btn-${variant}-${color}`]: variant && color, + [`btn-${variant}`]: variant && !color, + [`btn-${color}`]: !variant && color, [`btn-${size}`]: size, }, shape, diff --git a/packages/coreui-react/src/components/dropdown/CDropdown.tsx b/packages/coreui-react/src/components/dropdown/CDropdown.tsx index 5b5e35d4..2d8a7bbd 100644 --- a/packages/coreui-react/src/components/dropdown/CDropdown.tsx +++ b/packages/coreui-react/src/components/dropdown/CDropdown.tsx @@ -1,4 +1,13 @@ -import React, { ElementType, forwardRef, HTMLAttributes, useEffect, useRef, useState } from 'react' +import React, { + ElementType, + forwardRef, + HTMLAttributes, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' import type { Options } from '@popperjs/core' @@ -190,59 +199,64 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps> ref ) => { const dropdownRef = useRef(null) - const dropdownToggleRef = useRef(null) const dropdownMenuRef = useRef(null) const forkedRef = useForkedRef(ref, dropdownRef) + const [dropdownToggleElement, setDropdownToggleElement] = useState(null) const [_visible, setVisible] = useState(visible) const { initPopper, destroyPopper } = usePopper() - const Component = variant === 'nav-item' ? 'li' : as - - // Disable popper if responsive aligment is set. - if (typeof alignment === 'object') { - popper = false - } + const dropdownToggleRef = useCallback((node: HTMLElement | null) => { + if (node) { + setDropdownToggleElement(node) + } + }, []) - const contextValues = { - alignment, - container, - dark, - dropdownToggleRef, - dropdownMenuRef, - popper, - portal, - variant, - visible: _visible, - setVisible, - } + const allowPopperUse = popper && typeof alignment !== 'object' + const Component = variant === 'nav-item' ? 'li' : as - const defaultPopperConfig = { - modifiers: [ - { - name: 'offset', - options: { - offset: offset, + const computedPopperConfig: Partial = useMemo(() => { + const defaultPopperConfig = { + modifiers: [ + { + name: 'offset', + options: { + offset, + }, }, - }, - ], - placement: getPlacement(placement, direction, alignment, isRTL(dropdownMenuRef.current)), - } + ], + placement: getPlacement(placement, direction, alignment, isRTL(dropdownMenuRef.current)), + } - const computedPopperConfig: Partial = { - ...defaultPopperConfig, - ...(typeof popperConfig === 'function' ? popperConfig(defaultPopperConfig) : popperConfig), - } + return { + ...defaultPopperConfig, + ...(typeof popperConfig === 'function' ? popperConfig(defaultPopperConfig) : popperConfig), + } + }, [offset, placement, direction, alignment, popperConfig]) useEffect(() => { - setVisible(visible) + if (visible) { + handleShow() + } else { + handleHide() + } }, [visible]) useEffect(() => { - const toggleElement = dropdownToggleRef.current + const toggleElement = dropdownToggleElement + const menuElement = dropdownMenuRef.current + if (allowPopperUse && menuElement && toggleElement && _visible) { + initPopper(toggleElement, menuElement, computedPopperConfig) + } + }, [dropdownToggleElement]) + + const handleShow = () => { + const toggleElement = dropdownToggleElement const menuElement = dropdownMenuRef.current - if (_visible && toggleElement && menuElement) { - if (popper) { + if (toggleElement && menuElement) { + setVisible(true) + + if (allowPopperUse) { initPopper(toggleElement, menuElement, computedPopperConfig) } @@ -255,28 +269,26 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps> onShow?.() } + } - return () => { - if (popper) { - destroyPopper() - } - - toggleElement?.removeEventListener('keydown', handleKeydown) - menuElement?.removeEventListener('keydown', handleKeydown) + const handleHide = () => { + setVisible(false) - window.removeEventListener('mouseup', handleMouseUp) - window.removeEventListener('keyup', handleKeyup) + const toggleElement = dropdownToggleElement + const menuElement = dropdownMenuRef.current - onHide?.() + if (allowPopperUse) { + destroyPopper() } - }, [ - computedPopperConfig, - destroyPopper, - dropdownMenuRef, - dropdownToggleRef, - initPopper, - _visible, - ]) + + toggleElement?.removeEventListener('keydown', handleKeydown) + menuElement?.removeEventListener('keydown', handleKeydown) + + window.removeEventListener('mouseup', handleMouseUp) + window.removeEventListener('keyup', handleKeyup) + + onHide?.() + } const handleKeydown = (event: KeyboardEvent) => { if ( @@ -299,16 +311,16 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps> } if (event.key === 'Escape') { - setVisible(false) + handleHide() } } const handleMouseUp = (event: Event) => { - if (!dropdownToggleRef.current || !dropdownMenuRef.current) { + if (!dropdownToggleElement || !dropdownMenuRef.current) { return } - if (dropdownToggleRef.current.contains(event.target as HTMLElement)) { + if (dropdownToggleElement.contains(event.target as HTMLElement)) { return } @@ -317,11 +329,25 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps> (autoClose === 'inside' && dropdownMenuRef.current.contains(event.target as HTMLElement)) || (autoClose === 'outside' && !dropdownMenuRef.current.contains(event.target as HTMLElement)) ) { - setTimeout(() => setVisible(false), 1) + setTimeout(() => handleHide(), 1) return } } + const contextValues = { + alignment, + container, + dark, + dropdownMenuRef, + dropdownToggleRef, + handleHide, + handleShow, + popper: allowPopperUse, + portal, + variant, + visible: _visible, + } + return ( {variant === 'input-group' ? ( diff --git a/packages/coreui-react/src/components/dropdown/CDropdownContext.ts b/packages/coreui-react/src/components/dropdown/CDropdownContext.ts index 81af43aa..d46b69d2 100644 --- a/packages/coreui-react/src/components/dropdown/CDropdownContext.ts +++ b/packages/coreui-react/src/components/dropdown/CDropdownContext.ts @@ -5,10 +5,10 @@ export interface CDropdownContextProps { alignment?: Alignments container?: DocumentFragment | Element | (() => DocumentFragment | Element | null) | null dark?: boolean - // eslint-disable-next-line @typescript-eslint/no-explicit-any - dropdownToggleRef: RefObject dropdownMenuRef: RefObject - setVisible: React.Dispatch> + dropdownToggleRef: (node: HTMLElement | null) => void + handleHide?: () => void + handleShow?: () => void popper?: boolean portal?: boolean variant?: 'btn-group' | 'dropdown' | 'input-group' | 'nav-item' diff --git a/packages/coreui-react/src/components/dropdown/CDropdownToggle.tsx b/packages/coreui-react/src/components/dropdown/CDropdownToggle.tsx index ee8dfe82..def263e5 100644 --- a/packages/coreui-react/src/components/dropdown/CDropdownToggle.tsx +++ b/packages/coreui-react/src/components/dropdown/CDropdownToggle.tsx @@ -45,18 +45,24 @@ export const CDropdownToggle: FC = ({ trigger = 'click', ...rest }) => { - const { dropdownToggleRef, variant, visible, setVisible } = useContext(CDropdownContext) + const { dropdownToggleRef, handleHide, handleShow, variant, visible } = + useContext(CDropdownContext) const triggers = { ...((trigger === 'click' || trigger.includes('click')) && { onClick: (event: React.MouseEvent) => { event.preventDefault() - setVisible(!visible) + + if (visible) { + handleHide?.() + } else { + handleShow?.() + } }, }), ...((trigger === 'focus' || trigger.includes('focus')) && { - onFocus: () => setVisible(true), - onBlur: () => setVisible(false), + onFocus: () => handleShow?.(), + onBlur: () => handleHide?.(), }), } @@ -74,36 +80,32 @@ export const CDropdownToggle: FC = ({ ...(!rest.disabled && { ...triggers }), } - const Toggler = () => { - if (custom && React.isValidElement(children)) { - return ( - <> - {React.cloneElement(children as React.ReactElement, { - 'aria-expanded': visible, - ...(!rest.disabled && { ...triggers }), - ref: dropdownToggleRef, - })} - - ) - } - - if (variant === 'nav-item' && navLink) { - return ( - - {children} - - ) - } + if (custom && React.isValidElement(children)) { + return ( + <> + {React.cloneElement(children as React.ReactElement, { + 'aria-expanded': visible, + ...(!rest.disabled && { ...triggers }), + ref: dropdownToggleRef, + })} + + ) + } + if (variant === 'nav-item' && navLink) { return ( - + {children} - {split && Toggle Dropdown} - + ) } - return + return ( + + {children} + {split && Toggle Dropdown} + + ) } CDropdownToggle.propTypes = { diff --git a/packages/docs/content/forms/checkbox/bootstrap.mdx b/packages/docs/content/forms/checkbox/bootstrap.mdx index 185c6753..842bb911 100644 --- a/packages/docs/content/forms/checkbox/bootstrap.mdx +++ b/packages/docs/content/forms/checkbox/bootstrap.mdx @@ -1,69 +1,71 @@ --- -title: React Checkbox Components -name: Checkbox -description: Create consistent cross-browser and cross-device checkboxes with our React checkbox components. +title: React Bootstrap Checkbox Component +name: Checkbox with Bootstrap Styling +description: Learn how to build accessible and consistent Bootstrap-style checkboxes in React using CoreUI components. route: /forms/checkbox/ -other_frameworks: checkbox +bootstrap_component: true --- -## Approach +## How to use React Bootstrap Checkbox component -Browser default checkboxes and radios are replaced with the help of ``. Checkboxes are for selecting one or several options in a list. +Use CoreUI’s `` to create cross-browser, accessible, and Bootstrap-styled checkboxes in React. Checkboxes are ideal for selecting one or more options from a list and can be styled, stacked, or grouped using layout utilities. -## Checkboxes +### Basic example - +Use the `` component to render a standard Bootstrap-style checkbox. -### Indeterminate + -Checkboxes can utilize the `:indeterminate` pseudo-class when manually set via `indeterminate` property. +### Indeterminate state - +Set the `indeterminate` property to render a checkbox in an indeterminate state, commonly used to indicate partial selections. -### Disabled + -Add the `disabled` attribute and the associated `