diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index f623800d..00000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -/dist/** -.eslintrc.js \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 2f74cc21..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright (c) 2013-present, creativeLabs Lukasz Holeczek. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -'use strict' - -module.exports = { - root: true, // So parent files don't get applied - env: { - es6: true, - browser: true, - node: true, - }, - extends: [ - 'plugin:react/recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:prettier/recommended', - 'plugin:unicorn/recommended', - ], - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 2020, - sourceType: 'module', - ecmaFeatures: { - jsx: true, - }, - }, - plugins: ['@typescript-eslint', 'react', 'react-hooks'], - settings: { - react: { - pragma: 'React', - version: 'detect', - }, - }, - rules: { - 'unicorn/filename-case': 'off', - 'unicorn/no-array-for-each': 'off', - 'unicorn/no-null': 'off', - 'unicorn/prefer-dom-node-append': 'off', - 'unicorn/prefer-export-from': 'off', - 'unicorn/prefer-query-selector': 'off', - 'unicorn/prevent-abbreviations': 'off', - }, - overrides: [ - { - files: ['packages/docs/build/**'], - env: { - browser: false, - node: true, - }, - parserOptions: { - sourceType: 'script', - }, - rules: { - '@typescript-eslint/no-var-requires': 'off', - 'no-console': 'off', - 'unicorn/prefer-module': 'off', - 'unicorn/prefer-top-level-await': 'off', - }, - }, - { - files: ['packages/docs/**'], - rules: { - '@typescript-eslint/no-var-requires': 'off', - 'unicorn/prefer-module': 'off', - }, - }, - ], -} diff --git a/.prettierrc.js b/.prettierrc.js deleted file mode 100644 index 415ca057..00000000 --- a/.prettierrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - semi: false, - trailingComma: "all", - singleQuote: true, - printWidth: 100, - tabWidth: 2 -}; \ No newline at end of file diff --git a/LICENSE b/LICENSE index 94e4f4d1..fbb053e0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 creativeLabs Łukasz Holeczek +Copyright (c) 2025 creativeLabs Łukasz Holeczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index f1d28a27..467a62fb 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.4.1.zip) +- [Download the latest release](https://github.com/coreui/coreui-react/archive/v5.7.0.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` @@ -227,4 +227,4 @@ CoreUI is an MIT-licensed open source project and is completely free to use. How ## Copyright and license -Copyright 2024 creativeLabs Łukasz Holeczek. Code released under the [MIT License](https://github.com/coreui/coreui-react/blob/main/LICENSE). Docs released under [Creative Commons](https://creativecommons.org/licenses/by/3.0/). \ No newline at end of file +Copyright 2025 creativeLabs Łukasz Holeczek. Code released under the [MIT License](https://github.com/coreui/coreui-react/blob/main/LICENSE). Docs released under [Creative Commons](https://creativecommons.org/licenses/by/3.0/). \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..2498120d --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,104 @@ +import eslint from '@eslint/js' +import tsParser from '@typescript-eslint/parser' +import eslintPluginUnicorn from 'eslint-plugin-unicorn' +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended' +import eslintPluginReact from 'eslint-plugin-react' +import eslintPluginReactHooks from 'eslint-plugin-react-hooks' +import globals from 'globals' +import typescriptEslint from 'typescript-eslint' + +export default typescriptEslint.config( + { ignores: ['**/*.d.ts', '**/coverage', '**/dist', 'eslint.config.mjs'] }, + { + extends: [ + eslint.configs.recommended, + ...typescriptEslint.configs.recommended, + eslintPluginUnicorn.configs['flat/recommended'], + eslintPluginReact.configs.flat.recommended, + eslintPluginReact.configs.flat['jsx-runtime'], + ], + plugins: { + 'react-hooks': eslintPluginReactHooks, + }, + files: ['packages/**/src/**/*.{js,ts,tsx}'], + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + }, + parser: tsParser, + ecmaVersion: 'latest', + sourceType: 'module', + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + settings: { + react: { + pragma: 'React', + version: 'detect', + }, + }, + rules: { + ...eslintPluginReactHooks.configs.recommended.rules, + 'no-console': 'off', + 'no-debugger': 'off', + 'unicorn/filename-case': 'off', + 'unicorn/no-array-for-each': 'off', + 'unicorn/no-null': 'off', + 'unicorn/prefer-dom-node-append': 'off', + 'unicorn/prefer-export-from': 'off', + 'unicorn/prefer-query-selector': 'off', + 'unicorn/prevent-abbreviations': 'off', + 'vue/require-default-prop': 'off', + }, + }, + { + files: ['**/*.mjs'], + languageOptions: { + globals: { + ...Object.fromEntries(Object.entries(globals.browser).map(([key]) => [key, 'off'])), + ...globals.node, + }, + + ecmaVersion: 5, + sourceType: 'module', + }, + }, + { + files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)'], + languageOptions: { + globals: { + ...globals.jest, + }, + }, + }, + { + files: ['packages/docs/build/**'], + languageOptions: { + globals: { + ...Object.fromEntries(Object.entries(globals.browser).map(([key]) => [key, 'off'])), + ...globals.node, + }, + + ecmaVersion: 5, + sourceType: 'commonjs', + }, + rules: { + '@typescript-eslint/no-var-requires': 'off', + 'no-console': 'off', + 'unicorn/prefer-module': 'off', + 'unicorn/prefer-top-level-await': 'off', + }, + }, + { + files: ['packages/docs/**'], + rules: { + '@typescript-eslint/no-var-requires': 'off', + 'unicorn/prefer-module': 'off', + }, + }, + eslintPluginPrettierRecommended, +) diff --git a/lerna.json b/lerna.json index 6fea4400..e456a603 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "npmClient": "yarn", "packages": ["packages/*"], - "version": "5.4.1", + "version": "5.7.0", "$schema": "node_modules/lerna/schemas/lerna-schema.json" } diff --git a/package.json b/package.json index f27bd5f4..e51bf9e0 100644 --- a/package.json +++ b/package.json @@ -22,17 +22,18 @@ "test:update": "npm-run-all charts:test:update icons:test:update lib:test:update" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^8.12.1", - "@typescript-eslint/parser": "^8.12.1", - "eslint": "8.57.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.2.1", - "eslint-plugin-react": "^7.37.2", - "eslint-plugin-react-hooks": "^5.0.0", - "eslint-plugin-unicorn": "^56.0.0", - "lerna": "^8.1.8", + "@typescript-eslint/parser": "^8.32.1", + "eslint": "^9.27.0", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-prettier": "^5.4.0", + "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", "npm-run-all": "^4.1.5", - "prettier": "^3.3.3" + "prettier": "^3.5.3", + "typescript-eslint": "^8.32.1" }, "overrides": { "gatsby-remark-external-links": { diff --git a/packages/coreui-react/LICENSE b/packages/coreui-react/LICENSE index 94e4f4d1..fbb053e0 100644 --- a/packages/coreui-react/LICENSE +++ b/packages/coreui-react/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 creativeLabs Łukasz Holeczek +Copyright (c) 2025 creativeLabs Łukasz Holeczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/coreui-react/README.md b/packages/coreui-react/README.md index 0dec4d28..c98015a2 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.4.1.zip) +- [Download the latest release](https://github.com/coreui/coreui-react/archive/v5.7.0.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` @@ -259,4 +259,4 @@ Thanks to all the backers and sponsors! Support this project by [becoming a back ## Copyright and license -Copyright 2024 creativeLabs Łukasz Holeczek. Code released under the [MIT License](https://github.com/coreui/coreui-react/blob/main/LICENSE). Docs released under [Creative Commons](https://creativecommons.org/licenses/by/3.0/). +Copyright 2025 creativeLabs Łukasz Holeczek. Code released under the [MIT License](https://github.com/coreui/coreui-react/blob/main/LICENSE). Docs released under [Creative Commons](https://creativecommons.org/licenses/by/3.0/). diff --git a/packages/coreui-react/package.json b/packages/coreui-react/package.json index e106f5a0..6952b2c9 100644 --- a/packages/coreui-react/package.json +++ b/packages/coreui-react/package.json @@ -1,6 +1,6 @@ { "name": "@coreui/react", - "version": "5.4.1", + "version": "5.7.0", "description": "UI Components Library for React.js", "keywords": [ "react", @@ -41,20 +41,22 @@ "test:update": "jest --coverage --updateSnapshot" }, "dependencies": { - "@coreui/coreui": "^5.2.0", + "@coreui/coreui": "^5.4.0", "@popperjs/core": "^2.11.8", "prop-types": "^15.8.1" }, "devDependencies": { - "@rollup/plugin-commonjs": "^28.0.1", - "@rollup/plugin-node-resolve": "^15.3.0", - "@rollup/plugin-typescript": "^12.1.1", - "@testing-library/jest-dom": "^6.6.2", - "@testing-library/react": "^16.0.1", + "@rollup/plugin-commonjs": "^28.0.3", + "@rollup/plugin-node-resolve": "^16.0.1", + "@rollup/plugin-typescript": "^12.1.2", + "@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/react": "18.3.12", - "@types/react-dom": "^18.3.1", - "@types/react-transition-group": "^4.4.11", + "@types/prop-types": "15.7.14", + "@types/react": "^19.1.4", + "@types/react-dom": "^19.1.5", + "@types/react-transition-group": "^4.4.12", "classnames": "^2.5.1", "cross-env": "^7.0.3", "jest": "^29.7.0", @@ -62,10 +64,10 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-transition-group": "^4.4.5", - "rollup": "^4.24.2", - "ts-jest": "^29.2.5", - "tslib": "^2.8.0", - "typescript": "^5.6.3" + "rollup": "^4.41.0", + "ts-jest": "^29.3.4", + "tslib": "^2.8.1", + "typescript": "^5.8.3" }, "peerDependencies": { "react": ">=17", diff --git a/packages/coreui-react/src/components/accordion/CAccordion.tsx b/packages/coreui-react/src/components/accordion/CAccordion.tsx index 9fe4c634..a3a275ae 100644 --- a/packages/coreui-react/src/components/accordion/CAccordion.tsx +++ b/packages/coreui-react/src/components/accordion/CAccordion.tsx @@ -1,7 +1,9 @@ -import React, { createContext, forwardRef, HTMLAttributes, useState } from 'react' +import React, { forwardRef, HTMLAttributes, useState } from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' +import { CAccordionContext } from './CAccordionContext' + export interface CAccordionProps extends HTMLAttributes { /** * The active item key. @@ -21,14 +23,6 @@ export interface CAccordionProps extends HTMLAttributes { flush?: boolean } -export interface CAccordionContextProps { - _activeItemKey?: number | string - alwaysOpen?: boolean - setActiveKey: React.Dispatch> -} - -export const CAccordionContext = createContext({} as CAccordionContextProps) - export const CAccordion = forwardRef( ({ children, activeItemKey, alwaysOpen = false, className, flush, ...rest }, ref) => { const [_activeItemKey, setActiveKey] = useState(activeItemKey) @@ -48,8 +42,8 @@ export const CAccordion = forwardRef( ) CAccordion.propTypes = { - alwaysOpen: PropTypes.bool, activeItemKey: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + alwaysOpen: PropTypes.bool, children: PropTypes.node, className: PropTypes.string, flush: PropTypes.bool, diff --git a/packages/coreui-react/src/components/accordion/CAccordionBody.tsx b/packages/coreui-react/src/components/accordion/CAccordionBody.tsx index 532f3ff6..d98e8c43 100644 --- a/packages/coreui-react/src/components/accordion/CAccordionBody.tsx +++ b/packages/coreui-react/src/components/accordion/CAccordionBody.tsx @@ -2,9 +2,8 @@ import React, { forwardRef, HTMLAttributes, useContext } from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' -import { CAccordionItemContext } from './CAccordionItem' - import { CCollapse } from './../collapse/CCollapse' +import { CAccordionItemContext } from './CAccordionItemContext' export interface CAccordionBodyProps extends HTMLAttributes { /** @@ -15,16 +14,16 @@ export interface CAccordionBodyProps extends HTMLAttributes { export const CAccordionBody = forwardRef( ({ children, className, ...rest }, ref) => { - const { visible } = useContext(CAccordionItemContext) + const { id, visible } = useContext(CAccordionItemContext) return ( - +
{children}
) - }, + } ) CAccordionBody.propTypes = { diff --git a/packages/coreui-react/src/components/accordion/CAccordionButton.tsx b/packages/coreui-react/src/components/accordion/CAccordionButton.tsx index 4235ca6e..b5e9ca4c 100644 --- a/packages/coreui-react/src/components/accordion/CAccordionButton.tsx +++ b/packages/coreui-react/src/components/accordion/CAccordionButton.tsx @@ -2,7 +2,7 @@ import React, { forwardRef, HTMLAttributes, useContext } from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' -import { CAccordionItemContext } from './CAccordionItem' +import { CAccordionItemContext } from './CAccordionItemContext' export interface CAccordionButtonProps extends HTMLAttributes { /** @@ -13,13 +13,14 @@ export interface CAccordionButtonProps extends HTMLAttributes export const CAccordionButton = forwardRef( ({ children, className, ...rest }, ref) => { - const { visible, setVisible } = useContext(CAccordionItemContext) + const { id, visible, setVisible } = useContext(CAccordionItemContext) return ( - -`; - -exports[`loads and displays CCloseButton component 1`] = ` -
- -
-`; diff --git a/packages/coreui-react/src/components/callout/__tests__/CCallout.spec.tsx b/packages/coreui-react/src/components/callout/__tests__/CCallout.spec.tsx index 86f63675..637727c7 100644 --- a/packages/coreui-react/src/components/callout/__tests__/CCallout.spec.tsx +++ b/packages/coreui-react/src/components/callout/__tests__/CCallout.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' -import { CCallout } from '../../../index' +import { CCallout } from '../index' test('loads and displays CCallout component', async () => { const { container } = render(Test) diff --git a/packages/coreui-react/src/components/card/__tests__/CCard.spec.tsx b/packages/coreui-react/src/components/card/__tests__/CCard.spec.tsx index c22e6080..6b4f9b11 100644 --- a/packages/coreui-react/src/components/card/__tests__/CCard.spec.tsx +++ b/packages/coreui-react/src/components/card/__tests__/CCard.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' -import { CCard } from '../../../index' +import { CCard } from '../index' test('loads and displays CCard component', async () => { const { container } = render(Test) diff --git a/packages/coreui-react/src/components/card/__tests__/CCardBody.spec.tsx b/packages/coreui-react/src/components/card/__tests__/CCardBody.spec.tsx index 8b79b9f5..0bf1ccd9 100644 --- a/packages/coreui-react/src/components/card/__tests__/CCardBody.spec.tsx +++ b/packages/coreui-react/src/components/card/__tests__/CCardBody.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' -import { CCardBody } from '../../../index' +import { CCardBody } from '../index' test('loads and displays CCardBody component', async () => { const { container } = render(Test) diff --git a/packages/coreui-react/src/components/card/__tests__/CCardFooter.spec.tsx b/packages/coreui-react/src/components/card/__tests__/CCardFooter.spec.tsx index 0b47f51f..991e3c7c 100644 --- a/packages/coreui-react/src/components/card/__tests__/CCardFooter.spec.tsx +++ b/packages/coreui-react/src/components/card/__tests__/CCardFooter.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' -import { CCardFooter } from '../../../index' +import { CCardFooter } from '../index' test('loads and displays CCardFooter component', async () => { const { container } = render(Test) diff --git a/packages/coreui-react/src/components/card/__tests__/CCardGroup.spec.tsx b/packages/coreui-react/src/components/card/__tests__/CCardGroup.spec.tsx index a0228126..239c1fd9 100644 --- a/packages/coreui-react/src/components/card/__tests__/CCardGroup.spec.tsx +++ b/packages/coreui-react/src/components/card/__tests__/CCardGroup.spec.tsx @@ -12,7 +12,7 @@ import { CCardTitle, CCardText, CCardGroup, -} from '../../../index' +} from '../index' test('loads and displays CCardGroup component', async () => { const { container } = render(Test) diff --git a/packages/coreui-react/src/components/card/__tests__/CCardHeader.spec.tsx b/packages/coreui-react/src/components/card/__tests__/CCardHeader.spec.tsx index ffb43bab..0477b503 100644 --- a/packages/coreui-react/src/components/card/__tests__/CCardHeader.spec.tsx +++ b/packages/coreui-react/src/components/card/__tests__/CCardHeader.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' -import { CCardHeader } from '../../../index' +import { CCardHeader } from '../index' test('loads and displays CCardHeader component', async () => { const { container } = render(Test) diff --git a/packages/coreui-react/src/components/card/__tests__/CCardImage.spec.tsx b/packages/coreui-react/src/components/card/__tests__/CCardImage.spec.tsx index 88c20fd1..e1214093 100644 --- a/packages/coreui-react/src/components/card/__tests__/CCardImage.spec.tsx +++ b/packages/coreui-react/src/components/card/__tests__/CCardImage.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' -import { CCardImage } from '../../../index' +import { CCardImage } from '../index' test('loads and displays CCardImage component', async () => { const { container } = render() diff --git a/packages/coreui-react/src/components/card/__tests__/CCardImageOverlay.spec.tsx b/packages/coreui-react/src/components/card/__tests__/CCardImageOverlay.spec.tsx index d161d9c2..6574856b 100644 --- a/packages/coreui-react/src/components/card/__tests__/CCardImageOverlay.spec.tsx +++ b/packages/coreui-react/src/components/card/__tests__/CCardImageOverlay.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' -import { CCardImageOverlay } from '../../../index' +import { CCardImageOverlay } from '../index' test('loads and displays CCardImageOverlay component', async () => { const { container } = render() diff --git a/packages/coreui-react/src/components/card/__tests__/CCardLink.spec.tsx b/packages/coreui-react/src/components/card/__tests__/CCardLink.spec.tsx index 2251ac29..2373537f 100644 --- a/packages/coreui-react/src/components/card/__tests__/CCardLink.spec.tsx +++ b/packages/coreui-react/src/components/card/__tests__/CCardLink.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' -import { CCardLink } from '../../../index' +import { CCardLink } from '../index' test('loads and displays CCardLink component', async () => { const { container } = render(Test) diff --git a/packages/coreui-react/src/components/card/__tests__/CCardSubtitle.spec.tsx b/packages/coreui-react/src/components/card/__tests__/CCardSubtitle.spec.tsx index a44165f4..8a23f9c8 100644 --- a/packages/coreui-react/src/components/card/__tests__/CCardSubtitle.spec.tsx +++ b/packages/coreui-react/src/components/card/__tests__/CCardSubtitle.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' -import { CCardSubtitle } from '../../../index' +import { CCardSubtitle } from '../index' test('loads and displays CCardSubtitle component', async () => { const { container } = render(Test) diff --git a/packages/coreui-react/src/components/card/__tests__/CCardText.spec.tsx b/packages/coreui-react/src/components/card/__tests__/CCardText.spec.tsx index 06d2ca3a..3cbb4ab2 100644 --- a/packages/coreui-react/src/components/card/__tests__/CCardText.spec.tsx +++ b/packages/coreui-react/src/components/card/__tests__/CCardText.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' -import { CCardText } from '../../../index' +import { CCardText } from '../index' test('loads and displays CCardText component', async () => { const { container } = render(Test) diff --git a/packages/coreui-react/src/components/card/__tests__/CCardTitle.spec.tsx b/packages/coreui-react/src/components/card/__tests__/CCardTitle.spec.tsx index ada47122..9733a7f4 100644 --- a/packages/coreui-react/src/components/card/__tests__/CCardTitle.spec.tsx +++ b/packages/coreui-react/src/components/card/__tests__/CCardTitle.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' -import { CCardTitle } from '../../../index' +import { CCardTitle } from '../index' test('loads and displays CCardTitle component', async () => { const { container } = render(Test) diff --git a/packages/coreui-react/src/components/carousel/CCarousel.tsx b/packages/coreui-react/src/components/carousel/CCarousel.tsx index 4e7e95ca..3d3186d3 100644 --- a/packages/coreui-react/src/components/carousel/CCarousel.tsx +++ b/packages/coreui-react/src/components/carousel/CCarousel.tsx @@ -1,6 +1,5 @@ import React, { Children, - createContext, forwardRef, HTMLAttributes, TouchEvent, @@ -14,6 +13,8 @@ import classNames from 'classnames' import { isInViewport } from '../../utils' import { useForkedRef } from '../../hooks' +import { CCarouselContext } from './CCarouselContext' + export interface CCarouselProps extends HTMLAttributes { /** * index of the active item. @@ -71,13 +72,6 @@ interface DataType { timeout?: null | ReturnType } -export interface ContextProps { - setAnimating: (a: boolean) => void - setCustomInterval: (a: boolean | number) => void -} - -export const CCarouselContext = createContext({} as ContextProps) - export const CCarousel = forwardRef( ( { @@ -96,7 +90,7 @@ export const CCarousel = forwardRef( wrap = true, ...rest }, - ref, + ref ) => { const carouselRef = useRef(null) const forkedRef = useForkedRef(ref, carouselRef) @@ -141,7 +135,7 @@ export const CCarousel = forwardRef( if (typeof interval === 'number') { data.timeout = setTimeout( () => nextItemWhenVisible(), - typeof customInterval === 'number' ? customInterval : interval, + typeof customInterval === 'number' ? customInterval : interval ) } } @@ -228,7 +222,7 @@ export const CCarousel = forwardRef( { 'carousel-fade': transition === 'crossfade', }, - className, + className )} {...(dark && { 'data-coreui-theme': 'dark' })} onMouseEnter={_pause} @@ -288,7 +282,7 @@ export const CCarousel = forwardRef( ) - }, + } ) CCarousel.propTypes = { diff --git a/packages/coreui-react/src/components/carousel/CCarouselCaption.tsx b/packages/coreui-react/src/components/carousel/CCarouselCaption.tsx index 6e1cd5d1..353a6b1b 100644 --- a/packages/coreui-react/src/components/carousel/CCarouselCaption.tsx +++ b/packages/coreui-react/src/components/carousel/CCarouselCaption.tsx @@ -12,7 +12,7 @@ export interface CCarouselCaptionProps extends HTMLAttributes { export const CCarouselCaption = forwardRef( ({ className, ...rest }, ref) => { return
- }, + } ) CCarouselCaption.propTypes = { diff --git a/packages/coreui-react/src/components/carousel/CCarouselContext.ts b/packages/coreui-react/src/components/carousel/CCarouselContext.ts new file mode 100644 index 00000000..6e21932b --- /dev/null +++ b/packages/coreui-react/src/components/carousel/CCarouselContext.ts @@ -0,0 +1,8 @@ +import { createContext } from 'react' + +export interface CCarouselContextProps { + setAnimating: (a: boolean) => void + setCustomInterval: (a: boolean | number) => void +} + +export const CCarouselContext = createContext({} as CCarouselContextProps) \ No newline at end of file diff --git a/packages/coreui-react/src/components/carousel/CCarouselItem.tsx b/packages/coreui-react/src/components/carousel/CCarouselItem.tsx index 3b8744cc..90f4f6ca 100644 --- a/packages/coreui-react/src/components/carousel/CCarouselItem.tsx +++ b/packages/coreui-react/src/components/carousel/CCarouselItem.tsx @@ -3,7 +3,9 @@ import PropTypes from 'prop-types' import classNames from 'classnames' import { useForkedRef } from '../../hooks' -import { CCarouselContext } from './CCarousel' + +import { CCarouselContext } from './CCarouselContext' + export interface CCarouselItemProps extends HTMLAttributes { /** * @ignore @@ -29,7 +31,7 @@ export const CCarouselItem = forwardRef( const carouselItemRef = useRef(null) const forkedRef = useForkedRef(ref, carouselItemRef) - const prevActive = useRef() + const prevActive = useRef(undefined) const [directionClassName, setDirectionClassName] = useState() const [orderClassName, setOrderClassName] = useState() const [activeClassName, setActiveClassName] = useState(active && 'active') @@ -101,7 +103,7 @@ export const CCarouselItem = forwardRef( activeClassName, directionClassName, orderClassName, - className, + className )} ref={forkedRef} {...rest} @@ -109,7 +111,7 @@ export const CCarouselItem = forwardRef( {children}
) - }, + } ) CCarouselItem.propTypes = { diff --git a/packages/coreui-react/src/components/carousel/__tests__/CCarousel.spec.tsx b/packages/coreui-react/src/components/carousel/__tests__/CCarousel.spec.tsx index 4abb647b..c681e5b7 100644 --- a/packages/coreui-react/src/components/carousel/__tests__/CCarousel.spec.tsx +++ b/packages/coreui-react/src/components/carousel/__tests__/CCarousel.spec.tsx @@ -1,8 +1,7 @@ import React from 'react' -import { render, fireEvent } from '@testing-library/react' -import { getByText } from '@testing-library/dom' +import { render, fireEvent, getByText } from '@testing-library/react' import '@testing-library/jest-dom' -import { CCarousel, CCarouselCaption, CCarouselItem } from '../../../index' +import { CCarousel, CCarouselCaption, CCarouselItem } from '../index' test('loads and displays CCarousel component', async () => { const { container } = render( diff --git a/packages/coreui-react/src/components/close-button/__tests__/CCloseButton.spec.tsx b/packages/coreui-react/src/components/close-button/__tests__/CCloseButton.spec.tsx index a70c90ea..3eb0dbc0 100644 --- a/packages/coreui-react/src/components/close-button/__tests__/CCloseButton.spec.tsx +++ b/packages/coreui-react/src/components/close-button/__tests__/CCloseButton.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' -import { CCloseButton } from '../../../index' +import { CCloseButton } from '../index' test('loads and displays CCloseButton component', async () => { const { container } = render() diff --git a/packages/coreui-react/src/components/collapse/__tests__/CCollapse.spec.tsx b/packages/coreui-react/src/components/collapse/__tests__/CCollapse.spec.tsx index 7d701afe..faee720d 100644 --- a/packages/coreui-react/src/components/collapse/__tests__/CCollapse.spec.tsx +++ b/packages/coreui-react/src/components/collapse/__tests__/CCollapse.spec.tsx @@ -1,7 +1,7 @@ import React from 'react' -import { render, screen } from '@testing-library/react' +import { render, screen, act } from '@testing-library/react' import '@testing-library/jest-dom' -import { CCollapse } from '../../../index' +import { CCollapse } from '../index' test('loads and displays CCollapse component', async () => { const { container } = render(Test) @@ -15,26 +15,45 @@ test('CCollapse customize', async () => { }) test('CCollapse use case test', async () => { + jest.useFakeTimers() + const { rerender } = render(Test) + expect(screen.getByText('Test')).toHaveClass('collapse') expect(screen.getByText('Test')).not.toHaveClass('show') expect(screen.getByText('Test')).not.toHaveClass('collapsing') - rerender(Test) + + act(() => { + rerender(Test) + }) + expect(screen.getByText('Test')).not.toHaveClass('collapse') expect(screen.getByText('Test')).not.toHaveClass('show') expect(screen.getByText('Test')).toHaveClass('collapsing') - await new Promise((r) => setTimeout(r, 1000)) + + act(() => { + jest.runAllTimers() + }) + expect(screen.getByText('Test')).toHaveClass('collapse') expect(screen.getByText('Test')).toHaveClass('show') expect(screen.getByText('Test')).not.toHaveClass('collapsing') - rerender(Test) + + act(() => { + rerender(Test) + }) + expect(screen.getByText('Test')).not.toHaveClass('collapse') expect(screen.getByText('Test')).not.toHaveClass('show') expect(screen.getByText('Test')).toHaveClass('collapsing') - await new Promise((r) => setTimeout(r, 1000)) + + act(() => { + jest.runAllTimers() + }) + expect(screen.getByText('Test')).toHaveClass('collapse') expect(screen.getByText('Test')).not.toHaveClass('show') expect(screen.getByText('Test')).not.toHaveClass('collapsing') - jest.runAllTimers() + jest.useRealTimers() }) diff --git a/packages/coreui-react/src/components/dropdown/CDropdown.tsx b/packages/coreui-react/src/components/dropdown/CDropdown.tsx index 076c1812..5b5e35d4 100644 --- a/packages/coreui-react/src/components/dropdown/CDropdown.tsx +++ b/packages/coreui-react/src/components/dropdown/CDropdown.tsx @@ -1,15 +1,9 @@ -import React, { - createContext, - ElementType, - forwardRef, - HTMLAttributes, - RefObject, - useEffect, - useRef, - useState, -} from 'react' +import React, { ElementType, forwardRef, HTMLAttributes, useEffect, useRef, useState } from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' +import type { Options } from '@popperjs/core' + +import { CDropdownContext } from './CDropdownContext' import { PolymorphicRefForwardingComponent } from '../../helpers' import { useForkedRef, usePopper } from '../../hooks' @@ -22,91 +16,152 @@ import { getPlacement } from './utils' export interface CDropdownProps extends HTMLAttributes { /** - * Set aligment of dropdown menu. + * Specifies the alignment of the React Dropdown Menu within this React Dropdown. + * + * @example + * // Align dropdown menu to the end on large devices, otherwise start + * + * Toggle dropdown + * + * Action + * Another Action + * + * * * @type 'start' | 'end' | { xs: 'start' | 'end' } | { sm: 'start' | 'end' } | { md: 'start' | 'end' } | { lg: 'start' | 'end' } | { xl: 'start' | 'end'} | { xxl: 'start' | 'end'} */ alignment?: Alignments + /** - * Component used for the root node. Either a string to use a HTML element or a component. + * Determines the root node component (native HTML element or a custom React component) for the React Dropdown. */ as?: ElementType + /** - * Configure the auto close behavior of the dropdown: - * - `true` - the dropdown will be closed by clicking outside or inside the dropdown menu. - * - `false` - the dropdown will be closed by clicking the toggle button and manually calling hide or toggle method. (Also will not be closed by pressing esc key) - * - `'inside'` - the dropdown will be closed (only) by clicking inside the dropdown menu. - * - `'outside'` - the dropdown will be closed (only) by clicking outside the dropdown menu. + * Configures automatic closing behavior for the React Dropdown: + * - `true` - Close on clicks inside or outside of the React Dropdown Menu. + * - `false` - Disable auto-close; manually call `hide` or `toggle` (also not closed by `Escape`). + * - `'inside'` - Close only when clicking inside the React Dropdown Menu. + * - `'outside'` - Close only when clicking outside the React Dropdown Menu. + * + * @example + * // Close only when user clicks outside of the menu + * */ autoClose?: 'inside' | 'outside' | boolean + /** - * A string of all className you want applied to the base component. + * Adds custom classes to the React Dropdown root element. */ className?: string + /** - * Appends the react dropdown menu to a specific element. You can pass an HTML element or function that returns a single element. By default `document.body`. + * Appends the React Dropdown Menu to a specific element. You can pass an HTML element or a function returning an element. Defaults to `document.body`. + * + * @example + * // Append the menu to a custom container + * const myContainer = document.getElementById('my-container') + * + * * * @since 4.11.0 */ container?: DocumentFragment | Element | (() => DocumentFragment | Element | null) | null + /** - * Sets a darker color scheme to match a dark navbar. + * Applies a darker color scheme to the React Dropdown Menu, often used within dark navbars. */ dark?: boolean + /** - * Sets a specified direction and location of the dropdown menu. + * Specifies the direction of the React Dropdown. */ direction?: 'center' | 'dropup' | 'dropup-center' | 'dropend' | 'dropstart' + /** - * Offset of the dropdown menu relative to its target. + * Defines x and y offsets ([x, y]) for the React Dropdown Menu relative to its target. + * + * @example + * // Offset the menu 10px in X and 5px in Y direction + * + * ... + * */ offset?: [number, number] + /** - * Callback fired when the component requests to be hidden. + * Callback fired right before the React Dropdown becomes hidden. * * @since 4.9.0 */ onHide?: () => void + /** - * Callback fired when the component requests to be shown. + * Callback fired immediately after the React Dropdown is displayed. */ onShow?: () => void + /** - * Describes the placement of your component after Popper.js has applied all the modifiers that may have flipped or altered the originally provided placement property. + * Determines the placement of the React Dropdown Menu after Popper.js modifiers. * - * @type 'auto' | 'top-end' | 'top' | 'top-start' | 'bottom-end' | 'bottom' | 'bottom-start' | 'right-start' | 'right' | 'right-end' | 'left-start' | 'left' | 'left-end' + * @type 'auto' | 'auto-start' | 'auto-end' | 'top-end' | 'top' | 'top-start' | 'bottom-end' | 'bottom' | 'bottom-start' | 'right-start' | 'right' | 'right-end' | 'left-start' | 'left' | 'left-end' */ placement?: Placements + /** - * If you want to disable dynamic positioning set this property to `true`. + * Enables or disables dynamic positioning via Popper.js for the React Dropdown Menu. */ popper?: boolean + /** - * Generates dropdown menu using createPortal. + * Provides a custom Popper.js configuration or a function that returns a modified Popper.js configuration for advanced positioning of the React Dropdown Menu. [Read more](https://popper.js.org/docs/v2/constructors/#options) + * + * @example + * // Providing a custom popper config + * ... + * + * @since 5.5.0 + */ + popperConfig?: Partial | ((defaultPopperConfig: Partial) => Partial) + + /** + * Renders the React Dropdown Menu using a React Portal, allowing it to escape the DOM hierarchy for improved positioning. * * @since 4.8.0 */ portal?: boolean + /** - * Set the dropdown variant to an btn-group, dropdown, input-group, and nav-item. + * Defines the visual variant of the React Dropdown */ variant?: 'btn-group' | 'dropdown' | 'input-group' | 'nav-item' + /** - * Toggle the visibility of dropdown menu component. + * Controls the visibility of the React Dropdown Menu: + * - `true` - Visible + * - `false` - Hidden + * + * @example + * // Programmatically manage the dropdown visibility + * const [visible, setVisible] = useState(false) + * + * + * ... + * + * */ visible?: boolean } -interface ContextProps extends CDropdownProps { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - dropdownToggleRef: RefObject - dropdownMenuRef: RefObject - setVisible: React.Dispatch> - portal: boolean -} - -export const CDropdownContext = createContext({} as ContextProps) - export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps> = forwardRef< HTMLDivElement | HTMLLIElement, CDropdownProps @@ -126,16 +181,16 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps> onShow, placement = 'bottom-start', popper = true, + popperConfig, portal = false, variant = 'btn-group', visible = false, ...rest }, - ref, + ref ) => { const dropdownRef = useRef(null) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const dropdownToggleRef = useRef(null) + const dropdownToggleRef = useRef(null) const dropdownMenuRef = useRef(null) const forkedRef = useForkedRef(ref, dropdownRef) const [_visible, setVisible] = useState(visible) @@ -161,7 +216,7 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps> setVisible, } - const popperConfig = { + const defaultPopperConfig = { modifiers: [ { name: 'offset', @@ -173,32 +228,55 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps> placement: getPlacement(placement, direction, alignment, isRTL(dropdownMenuRef.current)), } + const computedPopperConfig: Partial = { + ...defaultPopperConfig, + ...(typeof popperConfig === 'function' ? popperConfig(defaultPopperConfig) : popperConfig), + } + useEffect(() => { setVisible(visible) }, [visible]) useEffect(() => { - if (_visible && dropdownToggleRef.current && dropdownMenuRef.current) { - dropdownToggleRef.current.focus() - popper && initPopper(dropdownToggleRef.current, dropdownMenuRef.current, popperConfig) + const toggleElement = dropdownToggleRef.current + const menuElement = dropdownMenuRef.current + + if (_visible && toggleElement && menuElement) { + if (popper) { + initPopper(toggleElement, menuElement, computedPopperConfig) + } + + toggleElement.focus() + toggleElement.addEventListener('keydown', handleKeydown) + menuElement.addEventListener('keydown', handleKeydown) + window.addEventListener('mouseup', handleMouseUp) window.addEventListener('keyup', handleKeyup) - dropdownToggleRef.current.addEventListener('keydown', handleKeydown) - dropdownMenuRef.current.addEventListener('keydown', handleKeydown) - onShow && onShow() + + onShow?.() } return () => { - popper && destroyPopper() + if (popper) { + destroyPopper() + } + + toggleElement?.removeEventListener('keydown', handleKeydown) + menuElement?.removeEventListener('keydown', handleKeydown) + window.removeEventListener('mouseup', handleMouseUp) window.removeEventListener('keyup', handleKeyup) - dropdownToggleRef.current && - dropdownToggleRef.current.removeEventListener('keydown', handleKeydown) - dropdownMenuRef.current && - dropdownMenuRef.current.removeEventListener('keydown', handleKeydown) - onHide && onHide() + + onHide?.() } - }, [_visible]) + }, [ + computedPopperConfig, + destroyPopper, + dropdownMenuRef, + dropdownToggleRef, + initPopper, + _visible, + ]) const handleKeydown = (event: KeyboardEvent) => { if ( @@ -209,7 +287,7 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps> event.preventDefault() const target = event.target as HTMLElement const items: HTMLElement[] = Array.from( - dropdownMenuRef.current.querySelectorAll('.dropdown-item:not(.disabled):not(:disabled)'), + dropdownMenuRef.current.querySelectorAll('.dropdown-item:not(.disabled):not(:disabled)') ) getNextActiveElement(items, target, event.key === 'ArrowDown', true).focus() } @@ -258,7 +336,7 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps> [`${direction}`]: direction && direction !== 'center' && direction !== 'dropup-center', }, - className, + className )} {...rest} ref={forkedRef} @@ -268,7 +346,7 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps> )} ) - }, + } ) const alignmentDirection = PropTypes.oneOf(['start', 'end']) @@ -297,6 +375,7 @@ CDropdown.propTypes = { onShow: PropTypes.func, placement: placementPropType, popper: PropTypes.bool, + popperConfig: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), portal: PropTypes.bool, variant: PropTypes.oneOf(['btn-group', 'dropdown', 'input-group', 'nav-item']), visible: PropTypes.bool, diff --git a/packages/coreui-react/src/components/dropdown/CDropdownContext.ts b/packages/coreui-react/src/components/dropdown/CDropdownContext.ts new file mode 100644 index 00000000..81af43aa --- /dev/null +++ b/packages/coreui-react/src/components/dropdown/CDropdownContext.ts @@ -0,0 +1,18 @@ +import { createContext, RefObject } from 'react' +import { Alignments } from './types' + +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> + popper?: boolean + portal?: boolean + variant?: 'btn-group' | 'dropdown' | 'input-group' | 'nav-item' + visible?: boolean +} + +export const CDropdownContext = createContext({} as CDropdownContextProps) diff --git a/packages/coreui-react/src/components/dropdown/CDropdownDivider.tsx b/packages/coreui-react/src/components/dropdown/CDropdownDivider.tsx index c9fa58de..57b86e1e 100644 --- a/packages/coreui-react/src/components/dropdown/CDropdownDivider.tsx +++ b/packages/coreui-react/src/components/dropdown/CDropdownDivider.tsx @@ -12,7 +12,7 @@ export interface CDropdownDividerProps extends HTMLAttributes { export const CDropdownDivider = forwardRef( ({ className, ...rest }, ref) => { return
- }, + } ) CDropdownDivider.propTypes = { diff --git a/packages/coreui-react/src/components/dropdown/CDropdownHeader.tsx b/packages/coreui-react/src/components/dropdown/CDropdownHeader.tsx index 0315a0eb..89e53673 100644 --- a/packages/coreui-react/src/components/dropdown/CDropdownHeader.tsx +++ b/packages/coreui-react/src/components/dropdown/CDropdownHeader.tsx @@ -23,7 +23,7 @@ export const CDropdownHeader: PolymorphicRefForwardingComponent<'h6', CDropdownH {children} ) - }, + } ) CDropdownHeader.propTypes = { diff --git a/packages/coreui-react/src/components/dropdown/CDropdownItemPlain.tsx b/packages/coreui-react/src/components/dropdown/CDropdownItemPlain.tsx index 5b22bd40..bc4ae0bb 100644 --- a/packages/coreui-react/src/components/dropdown/CDropdownItemPlain.tsx +++ b/packages/coreui-react/src/components/dropdown/CDropdownItemPlain.tsx @@ -25,7 +25,7 @@ export const CDropdownItemPlain: PolymorphicRefForwardingComponent< {children} ) - }, + } ) CDropdownItemPlain.propTypes = { diff --git a/packages/coreui-react/src/components/dropdown/CDropdownMenu.tsx b/packages/coreui-react/src/components/dropdown/CDropdownMenu.tsx index 35a4e98a..59442d63 100644 --- a/packages/coreui-react/src/components/dropdown/CDropdownMenu.tsx +++ b/packages/coreui-react/src/components/dropdown/CDropdownMenu.tsx @@ -2,8 +2,8 @@ import React, { ElementType, forwardRef, HTMLAttributes, useContext } from 'reac import PropTypes from 'prop-types' import classNames from 'classnames' -import { CDropdownContext } from './CDropdown' import { CConditionalPortal } from '../conditional-portal' +import { CDropdownContext } from './CDropdownContext' import { PolymorphicRefForwardingComponent } from '../../helpers' import { useForkedRef } from '../../hooks' @@ -38,11 +38,10 @@ export const CDropdownMenu: PolymorphicRefForwardingComponent<'ul', CDropdownMen show: visible, }, alignment && getAlignmentClassNames(alignment), - className, + className )} ref={forkedRef} role="menu" - aria-hidden={!visible} {...(!popper && { 'data-coreui-popper': 'static' })} {...(dark && { 'data-coreui-theme': 'dark' })} {...rest} @@ -58,7 +57,7 @@ export const CDropdownMenu: PolymorphicRefForwardingComponent<'ul', CDropdownMen ) - }, + } ) CDropdownMenu.propTypes = { diff --git a/packages/coreui-react/src/components/dropdown/CDropdownToggle.tsx b/packages/coreui-react/src/components/dropdown/CDropdownToggle.tsx index 3d7ce44c..ee8dfe82 100644 --- a/packages/coreui-react/src/components/dropdown/CDropdownToggle.tsx +++ b/packages/coreui-react/src/components/dropdown/CDropdownToggle.tsx @@ -3,8 +3,7 @@ import PropTypes from 'prop-types' import classNames from 'classnames' import { CButton, CButtonProps } from '../button/CButton' - -import { CDropdownContext } from './CDropdown' +import { CDropdownContext } from './CDropdownContext' import { triggerPropType } from '../../props' import type { Triggers } from '../../types' @@ -69,7 +68,7 @@ export const CDropdownToggle: FC = ({ 'dropdown-toggle-split': split, show: visible, }, - className, + className ), 'aria-expanded': visible, ...(!rest.disabled && { ...triggers }), @@ -90,7 +89,7 @@ export const CDropdownToggle: FC = ({ if (variant === 'nav-item' && navLink) { return ( - + {children} ) diff --git a/packages/coreui-react/src/components/dropdown/__tests__/CDropdown.spec.tsx b/packages/coreui-react/src/components/dropdown/__tests__/CDropdown.spec.tsx index d45f8c9d..12b6e3cd 100644 --- a/packages/coreui-react/src/components/dropdown/__tests__/CDropdown.spec.tsx +++ b/packages/coreui-react/src/components/dropdown/__tests__/CDropdown.spec.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { render, screen, fireEvent } from '@testing-library/react' +import { render, screen, fireEvent, waitFor } from '@testing-library/react' import '@testing-library/jest-dom' import { CDropdown, @@ -9,7 +9,7 @@ import { CDropdownItemPlain, CDropdownHeader, CDropdownDivider, -} from '../../../index' +} from '../index' test('loads and displays CDropdown component', async () => { const { container } = render(Test) @@ -52,26 +52,50 @@ test('CDropdown customize', async () => { // jest.useRealTimers() // }) -test('CDropdown click', async () => { +test('CDropdown opens on toggle click and closes on clicking outside', async () => { render( - - Test - - A - B - - , +
+ {/* External element to simulate clicking outside the dropdown */} +
External Area
+ + {/* The dropdown component */} + + Test + + A + B + + +
, ) - expect(screen.getByText('Test')).not.toHaveClass('show') - const el = screen.getByText('Test') - if (el !== null) { - fireEvent.click(el) //click on element - } - jest.runAllTimers() - expect(screen.getByText('Test').closest('div')).toHaveClass('show') - fireEvent.mouseUp(document.body) //click outside - await new Promise((r) => setTimeout(r, 1000)) - expect(screen.getByText('Test').closest('div')).not.toHaveClass('show') + + // Ensure the dropdown is initially closed + const toggleButton = screen.getByText('Test') + expect(toggleButton).toBeInTheDocument() + + // Assuming the 'show' class is applied to the CDropdownMenu + const dropdownMenu = screen.getByRole('menu', { hidden: true }) // Adjust role if different + expect(dropdownMenu).not.toHaveClass('show') + + // Click on the toggle to open the dropdown + fireEvent.click(toggleButton) + + // Wait for the dropdown menu to become visible + await waitFor(() => { + const openedMenu = screen.getByRole('menu') // Adjust role if different + expect(openedMenu).toBeVisible() + expect(openedMenu).toHaveClass('show') + }) + + // Click outside the dropdown to close it + const externalArea = screen.getByTestId('external-area') + fireEvent.mouseUp(externalArea) + + // Wait for the dropdown menu to be hidden + await waitFor(() => { + const closedMenu = screen.getByRole('menu', { hidden: true }) // Adjust role if different + expect(closedMenu).not.toHaveClass('show') + }) }) test('CDropdown example', async () => { diff --git a/packages/coreui-react/src/components/dropdown/__tests__/CDropdownDivider.spec.tsx b/packages/coreui-react/src/components/dropdown/__tests__/CDropdownDivider.spec.tsx index bc5f6667..98b9de48 100644 --- a/packages/coreui-react/src/components/dropdown/__tests__/CDropdownDivider.spec.tsx +++ b/packages/coreui-react/src/components/dropdown/__tests__/CDropdownDivider.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' -import { CDropdownDivider } from '../../../index' +import { CDropdownDivider } from '../index' test('loads and displays CDropdownDivider component', async () => { const { container } = render() diff --git a/packages/coreui-react/src/components/dropdown/__tests__/CDropdownHeader.spec.tsx b/packages/coreui-react/src/components/dropdown/__tests__/CDropdownHeader.spec.tsx index fe11ca28..602b8d26 100644 --- a/packages/coreui-react/src/components/dropdown/__tests__/CDropdownHeader.spec.tsx +++ b/packages/coreui-react/src/components/dropdown/__tests__/CDropdownHeader.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' -import { CDropdownHeader } from '../../../index' +import { CDropdownHeader } from '../index' test('loads and displays CDropdownHeader component', async () => { const { container } = render(Test) diff --git a/packages/coreui-react/src/components/dropdown/__tests__/CDropdownItem.spec.tsx b/packages/coreui-react/src/components/dropdown/__tests__/CDropdownItem.spec.tsx index 88091d6f..f484cc2f 100644 --- a/packages/coreui-react/src/components/dropdown/__tests__/CDropdownItem.spec.tsx +++ b/packages/coreui-react/src/components/dropdown/__tests__/CDropdownItem.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' -import { CDropdownItem } from '../../../index' +import { CDropdownItem } from '../index' test('loads and displays CDropdownItem component', async () => { const { container } = render(Test) diff --git a/packages/coreui-react/src/components/dropdown/__tests__/CDropdownItemPlain.spec.tsx b/packages/coreui-react/src/components/dropdown/__tests__/CDropdownItemPlain.spec.tsx index 8407502c..b1aafd07 100644 --- a/packages/coreui-react/src/components/dropdown/__tests__/CDropdownItemPlain.spec.tsx +++ b/packages/coreui-react/src/components/dropdown/__tests__/CDropdownItemPlain.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' -import { CDropdownItemPlain } from '../../../index' +import { CDropdownItemPlain } from '../index' test('loads and displays CDropdownItemPlain component', async () => { const { container } = render(Test) diff --git a/packages/coreui-react/src/components/dropdown/__tests__/CDropdownMenu.spec.tsx b/packages/coreui-react/src/components/dropdown/__tests__/CDropdownMenu.spec.tsx index 73300334..8b630833 100644 --- a/packages/coreui-react/src/components/dropdown/__tests__/CDropdownMenu.spec.tsx +++ b/packages/coreui-react/src/components/dropdown/__tests__/CDropdownMenu.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' -import { CDropdown, CDropdownMenu } from '../../../index' +import { CDropdown, CDropdownMenu } from '../index' test('loads and displays CDropdownMenu component', async () => { const { container } = render(Test) diff --git a/packages/coreui-react/src/components/dropdown/__tests__/CDropdownToggle.spec.tsx b/packages/coreui-react/src/components/dropdown/__tests__/CDropdownToggle.spec.tsx index 5d9db7e7..341c1048 100644 --- a/packages/coreui-react/src/components/dropdown/__tests__/CDropdownToggle.spec.tsx +++ b/packages/coreui-react/src/components/dropdown/__tests__/CDropdownToggle.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' -import { CDropdownToggle } from '../../../index' +import { CDropdownToggle } from '../index' test('loads and displays CDropdownToggle component', async () => { const { container } = render(Test) diff --git a/packages/coreui-react/src/components/dropdown/__tests__/__snapshots__/CDropdown.spec.tsx.snap b/packages/coreui-react/src/components/dropdown/__tests__/__snapshots__/CDropdown.spec.tsx.snap index f6741bfd..ed01b1d9 100644 --- a/packages/coreui-react/src/components/dropdown/__tests__/__snapshots__/CDropdown.spec.tsx.snap +++ b/packages/coreui-react/src/components/dropdown/__tests__/__snapshots__/CDropdown.spec.tsx.snap @@ -24,7 +24,6 @@ exports[`CDropdown example 1`] = ` Test