diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 1846284b..00000000 --- a/.eslintignore +++ /dev/null @@ -1,3 +0,0 @@ -/dist/** -/docs/** -.eslintrc.js \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index a0662dcd..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,75 +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, - }, - parser: '@typescript-eslint/parser', // Specifies the ESLint parser - parserOptions: { - ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features - sourceType: 'module', // Allows for the use of imports - extraFileExtensions: ['.vue'], - }, - extends: [ - 'eslint:recommended', - 'plugin:vue/vue3-recommended', - 'plugin:prettier/recommended', - 'plugin:unicorn/recommended', - '@vue/eslint-config-typescript/recommended', - '@vue/eslint-config-prettier', - ], - rules: { - 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', - 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : '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', - }, - overrides: [ - { - files: ['**/*.mjs'], - env: { - browser: false, - node: true, - }, - parserOptions: { - sourceType: 'module', - }, - }, - { - files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)'], - env: { - jest: true, - }, - }, - { - files: ['packages/docs/build/**'], - env: { - browser: false, - node: true, - }, - parserOptions: { - sourceType: 'script', - }, - rules: { - 'no-console': 'off', - strict: 'error', - }, - }, - ], -} 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 f19fc729..fbb053e0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 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 @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/README.md b/README.md index ccf1caa1..86647480 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-vue/archive/v5.0.0-rc.0.zip) +- [Download the latest release](https://github.com/coreui/coreui-vue/archive/v5.5.0.zip) - Clone the repo: `git clone https://github.com/coreui/coreui-vue.git` - Install with [npm](https://www.npmjs.com/): `npm install @coreui/vue` - Install with [yarn](https://yarnpkg.com/): `yarn add @coreui/vue` @@ -134,6 +134,7 @@ import "bootstrap/dist/css/bootstrap.min.css"; - [Vue Progress](https://coreui.io/vue/docs/components/progress.html) - [Vue Radio](https://coreui.io/vue/docs/forms/radio.html) - [Vue Range](https://coreui.io/vue/docs/forms/range.html) +- [Vue Rating](https://coreui.io/vue/docs/forms/rating.html) - [Vue Select](https://coreui.io/vue/docs/forms/select.html) - [Vue Sidebar](https://coreui.io/vue/docs/components/sidebar.html) - [Vue Smart Pagination](https://coreui.io/vue/docs/components/smart-pagination.html) **PRO** @@ -226,4 +227,4 @@ CoreUI is an MIT-licensed open source project and is completely free to use. How ## Copyright and license -Copyright 2023 creativeLabs Łukasz Holeczek. Code released under the [MIT License](https://github.com/coreui/coreui-vue/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-vue/blob/main/LICENSE). Docs released under [Creative Commons](https://creativecommons.org/licenses/by/3.0/). diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..15164bf5 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,76 @@ +import eslint from '@eslint/js' +import eslintPluginUnicorn from 'eslint-plugin-unicorn' +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended' +import eslintPluginVue from 'eslint-plugin-vue' +import globals from 'globals' +import typescriptEslint from 'typescript-eslint' + +export default typescriptEslint.config( + { ignores: ['**/*.d.ts', '**/coverage', '**/dist', '**/docs'] }, + { + extends: [ + eslint.configs.recommended, + ...typescriptEslint.configs.recommended, + ...eslintPluginVue.configs['flat/recommended'], + eslintPluginUnicorn.configs['flat/recommended'], + ], + files: ['packages/**/src/**/*.{js,ts,tsx}'], + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + globals: globals.browser, + parserOptions: { + parser: typescriptEslint.parser, + }, + }, + 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: 'latest', + 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: { + 'no-console': 'off', + strict: 'error', + }, + }, + eslintPluginPrettierRecommended, +) diff --git a/lerna.json b/lerna.json index 7aa00b6e..8b1ec4d7 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "npmClient": "yarn", "packages": ["packages/*"], - "version": "5.0.0-rc.0", + "version": "5.5.0", "$schema": "node_modules/lerna/schemas/lerna-schema.json" } diff --git a/package.json b/package.json index 6d1ddb62..7b5343e7 100644 --- a/package.json +++ b/package.json @@ -17,23 +17,21 @@ "lib:build": "lerna run --scope \"@coreui/vue\" build --stream", "lib:test": "lerna run --scope \"@coreui/vue\" test --stream", "lib:test:update": "lerna run --scope \"@coreui/vue\" test:update --stream", - "lint": "eslint \"packages/**/src/**/*.{js,ts,tsx}\"", + "lint": "eslint", "test": "npm-run-all charts:test icons:test lib:test", "test:update": "npm-run-all charts:test:update icons:test:update lib:test:update" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^6.11.0", - "@typescript-eslint/parser": "^6.11.0", - "@vue/eslint-config-prettier": "^8.0.0", - "@vue/eslint-config-typescript": "^12.0.0", "@vue/vue3-jest": "29.2.6", - "eslint": "8.53.0", - "eslint-plugin-prettier": "^5.0.1", - "eslint-plugin-vue": "^9.18.1", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-unicorn": "^49.0.0", - "lerna": "^7.4.2", + "eslint": "^9.28.0", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-prettier": "^5.4.1", + "eslint-plugin-unicorn": "^59.0.1", + "eslint-plugin-vue": "^10.1.0", + "globals": "^16.2.0", + "lerna": "^8.2.2", "npm-run-all": "^4.1.5", - "prettier": "^3.1.0" + "prettier": "^3.5.3", + "typescript-eslint": "^8.33.1" } } diff --git a/packages/coreui-icons-vue b/packages/coreui-icons-vue index b5f44477..009583d5 160000 --- a/packages/coreui-icons-vue +++ b/packages/coreui-icons-vue @@ -1 +1 @@ -Subproject commit b5f44477ddae0e06bb5c9adedcdcde1d2aa95713 +Subproject commit 009583d58f7ea9a2a7bc7a2e62861eba48c0d911 diff --git a/packages/coreui-vue-chartjs b/packages/coreui-vue-chartjs index 4915760c..f3b8364d 160000 --- a/packages/coreui-vue-chartjs +++ b/packages/coreui-vue-chartjs @@ -1 +1 @@ -Subproject commit 4915760c5f87bd0106778d414288681edc086fa9 +Subproject commit f3b8364d04dcd94c273ead1f740e1d792a5fb041 diff --git a/packages/coreui-vue/README.md b/packages/coreui-vue/README.md index 8293a55c..a1cfd697 100644 --- a/packages/coreui-vue/README.md +++ b/packages/coreui-vue/README.md @@ -46,7 +46,7 @@ Several quick start options are available: -- [Download the latest release](https://github.com/coreui/coreui-vue/archive/v4.4.0.zip) +- [Download the latest release](https://github.com/coreui/coreui-vue/archive/v5.5.0.zip) - Clone the repo: `git clone https://github.com/coreui/coreui-vue.git` - Install with [npm](https://www.npmjs.com/): `npm install @coreui/vue` - Install with [yarn](https://yarnpkg.com/): `yarn add @coreui/vue` @@ -134,6 +134,7 @@ import "bootstrap/dist/css/bootstrap.min.css"; - [Vue Progress](https://coreui.io/vue/docs/components/progress.html) - [Vue Radio](https://coreui.io/vue/docs/forms/radio.html) - [Vue Range](https://coreui.io/vue/docs/forms/range.html) +- [Vue Rating](https://coreui.io/vue/docs/forms/rating.html) - [Vue Select](https://coreui.io/vue/docs/forms/select.html) - [Vue Sidebar](https://coreui.io/vue/docs/components/sidebar.html) - [Vue Smart Pagination](https://coreui.io/vue/docs/components/smart-pagination.html) **PRO** diff --git a/packages/coreui-vue/package.json b/packages/coreui-vue/package.json index b72da2a7..39b08b3a 100644 --- a/packages/coreui-vue/package.json +++ b/packages/coreui-vue/package.json @@ -1,6 +1,6 @@ { "name": "@coreui/vue", - "version": "5.0.0-rc.0", + "version": "5.5.0", "description": "UI Components Library for Vue.js", "keywords": [ "vue", @@ -41,27 +41,27 @@ "test:update": "jest --coverage --updateSnapshot" }, "dependencies": { - "@coreui/coreui": "^5.0.0-rc.0", + "@coreui/coreui": "^5.4.0", "@popperjs/core": "^2.11.8" }, "devDependencies": { - "@rollup/plugin-commonjs": "^25.0.7", - "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-typescript": "^11.1.5", - "@types/jest": "^29.5.8", - "@vue/test-utils": "^2.4.2", + "@rollup/plugin-commonjs": "^28.0.3", + "@rollup/plugin-node-resolve": "^16.0.1", + "@rollup/plugin-typescript": "^12.1.2", + "@types/jest": "^29.5.14", + "@vue/test-utils": "^2.4.6", "@vue/vue3-jest": "29.2.6", "cross-env": "^7.0.3", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", - "rollup": "^4.4.1", + "rollup": "^4.41.1", "rollup-plugin-vue": "^6.0.0", - "ts-jest": "^29.1.1", - "typescript": "^5.2.2", - "vue": "^3.3.8", - "vue-types": "^5.1.1" + "ts-jest": "^29.3.4", + "typescript": "^5.8.3", + "vue": "^3.5.16", + "vue-types": "^6.0.0" }, "peerDependencies": { - "vue": "^3.2.21" + "vue": "^3.5.0" } } diff --git a/packages/coreui-vue/src/components/accordion/CAccordion.ts b/packages/coreui-vue/src/components/accordion/CAccordion.ts index 1422d40b..639b5af2 100644 --- a/packages/coreui-vue/src/components/accordion/CAccordion.ts +++ b/packages/coreui-vue/src/components/accordion/CAccordion.ts @@ -1,4 +1,4 @@ -import { defineComponent, h, provide, ref } from 'vue' +import { defineComponent, h, provide, ref, watch } from 'vue' const CAccordion = defineComponent({ name: 'CAccordion', @@ -21,6 +21,12 @@ const CAccordion = defineComponent({ const setActiveItemKey = (key: string | number) => { activeItemKey.value = key } + + watch( + () => props.activeItemKey, + (value) => (activeItemKey.value = value), + ) + provide('activeItemKey', activeItemKey) provide('alwaysOpen', props.alwaysOpen) provide('setActiveItemKey', setActiveItemKey) diff --git a/packages/coreui-vue/src/components/accordion/CAccordionBody.ts b/packages/coreui-vue/src/components/accordion/CAccordionBody.ts index 4a8d83ff..c8907021 100644 --- a/packages/coreui-vue/src/components/accordion/CAccordionBody.ts +++ b/packages/coreui-vue/src/components/accordion/CAccordionBody.ts @@ -4,11 +4,12 @@ import { CCollapse } from '../collapse/CCollapse' const CAccordionBody = defineComponent({ name: 'CAccordionBody', setup(_, { slots }) { + const id = inject('id') const visible = inject('visible') as Ref return () => h( CCollapse, - { class: 'accordion-collapse', visible: visible.value }, + { class: 'accordion-collapse', id, visible: visible.value }, { default: () => h('div', { class: ['accordion-body'] }, slots.default && slots.default()), }, diff --git a/packages/coreui-vue/src/components/accordion/CAccordionButton.ts b/packages/coreui-vue/src/components/accordion/CAccordionButton.ts index 69ab7a00..f9820b4a 100644 --- a/packages/coreui-vue/src/components/accordion/CAccordionButton.ts +++ b/packages/coreui-vue/src/components/accordion/CAccordionButton.ts @@ -3,6 +3,7 @@ import { defineComponent, h, inject, Ref } from 'vue' const CAccordionButton = defineComponent({ name: 'CAccordionButton', setup(_, { slots }) { + const id = inject('id') as string const toggleVisibility = inject('toggleVisibility') as () => void const visible = inject('visible') as Ref @@ -11,7 +12,8 @@ const CAccordionButton = defineComponent({ 'button', { type: 'button', - 'aria-expanded': !visible.value, + 'aria-control': id, + 'aria-expanded': visible.value, class: ['accordion-button', { ['collapsed']: !visible.value }], onClick: () => toggleVisibility(), }, diff --git a/packages/coreui-vue/src/components/accordion/CAccordionItem.ts b/packages/coreui-vue/src/components/accordion/CAccordionItem.ts index 20575085..f2ebe594 100644 --- a/packages/coreui-vue/src/components/accordion/CAccordionItem.ts +++ b/packages/coreui-vue/src/components/accordion/CAccordionItem.ts @@ -1,8 +1,12 @@ -import { defineComponent, h, inject, provide, ref, watch, Ref } from 'vue' +import { defineComponent, h, inject, provide, ref, watch, Ref, useId } from 'vue' const CAccordionItem = defineComponent({ name: 'CAccordionItem', props: { + /** + * The id global attribute defines an identifier (ID) that must be unique in the whole document. + */ + id: String, /** * The item key. */ @@ -13,16 +17,20 @@ const CAccordionItem = defineComponent({ const alwaysOpen = inject('alwaysOpen') as boolean const setActiveItemKey = inject('setActiveItemKey') as (key: number | string) => void - const itemKey = ref(props.itemKey ?? Math.random().toString(36).slice(2, 11)) + const id = props.id ?? useId() + const itemKey = ref(props.itemKey ?? id) const visible = ref(Boolean(activeItemKey.value === itemKey.value)) watch(activeItemKey, () => (visible.value = Boolean(activeItemKey.value === itemKey.value))) const toggleVisibility = () => { visible.value = !visible.value - !alwaysOpen && visible && setActiveItemKey(itemKey.value) + if (!alwaysOpen && visible) { + setActiveItemKey(itemKey.value) + } } + provide('id', id) provide('visible', visible) provide('toggleVisibility', toggleVisibility) diff --git a/packages/coreui-vue/src/components/accordion/index.ts b/packages/coreui-vue/src/components/accordion/index.ts index ee2326db..0d3d51dd 100644 --- a/packages/coreui-vue/src/components/accordion/index.ts +++ b/packages/coreui-vue/src/components/accordion/index.ts @@ -7,11 +7,11 @@ import { CAccordionItem } from './CAccordionItem' const CAccordionPlugin = { install: (app: App): void => { - app.component(CAccordion.name, CAccordion) - app.component(CAccordionBody.name, CAccordionBody) - app.component(CAccordionButton.name, CAccordionButton) - app.component(CAccordionHeader.name, CAccordionHeader) - app.component(CAccordionItem.name, CAccordionItem) + app.component(CAccordion.name as string, CAccordion) + app.component(CAccordionBody.name as string, CAccordionBody) + app.component(CAccordionButton.name as string, CAccordionButton) + app.component(CAccordionHeader.name as string, CAccordionHeader) + app.component(CAccordionItem.name as string, CAccordionItem) }, } diff --git a/packages/coreui-vue/src/components/alert/CAlertHeading.ts b/packages/coreui-vue/src/components/alert/CAlertHeading.ts index fbf38c69..94ed187c 100644 --- a/packages/coreui-vue/src/components/alert/CAlertHeading.ts +++ b/packages/coreui-vue/src/components/alert/CAlertHeading.ts @@ -6,7 +6,7 @@ export const CAlertHeading = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'h4', }, @@ -14,7 +14,7 @@ export const CAlertHeading = defineComponent({ setup(props, { slots }) { return () => h( - props.component, + props.as, { class: 'alert-heading', }, diff --git a/packages/coreui-vue/src/components/alert/__tests__/CAlertHeading.spec.ts b/packages/coreui-vue/src/components/alert/__tests__/CAlertHeading.spec.ts index 628ba7ad..73448f41 100644 --- a/packages/coreui-vue/src/components/alert/__tests__/CAlertHeading.spec.ts +++ b/packages/coreui-vue/src/components/alert/__tests__/CAlertHeading.spec.ts @@ -12,7 +12,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: 'h2', + as: 'h2', }, slots: { default: 'Default slot', diff --git a/packages/coreui-vue/src/components/alert/index.ts b/packages/coreui-vue/src/components/alert/index.ts index c8f7b404..d3fadac4 100644 --- a/packages/coreui-vue/src/components/alert/index.ts +++ b/packages/coreui-vue/src/components/alert/index.ts @@ -5,9 +5,9 @@ import { CAlertLink } from './CAlertLink' const CAlertPlugin = { install: (app: App): void => { - app.component(CAlert.name, CAlert) - app.component(CAlertHeading.name, CAlertHeading) - app.component(CAlertLink.name, CAlertLink) + app.component(CAlert.name as string, CAlert) + app.component(CAlertHeading.name as string, CAlertHeading) + app.component(CAlertLink.name as string, CAlertLink) }, } diff --git a/packages/coreui-vue/src/components/avatar/index.ts b/packages/coreui-vue/src/components/avatar/index.ts index 650fb4e6..479ab9f4 100644 --- a/packages/coreui-vue/src/components/avatar/index.ts +++ b/packages/coreui-vue/src/components/avatar/index.ts @@ -3,7 +3,7 @@ import { CAvatar } from './CAvatar' const CAvatarPlugin = { install: (app: App): void => { - app.component(CAvatar.name, CAvatar) + app.component(CAvatar.name as string, CAvatar) }, } diff --git a/packages/coreui-vue/src/components/backdrop/index.ts b/packages/coreui-vue/src/components/backdrop/index.ts index 7202b8fd..440292ba 100644 --- a/packages/coreui-vue/src/components/backdrop/index.ts +++ b/packages/coreui-vue/src/components/backdrop/index.ts @@ -3,7 +3,7 @@ import { CBackdrop } from './CBackdrop' const CBackdropPlugin = { install: (app: App): void => { - app.component(CBackdrop.name, CBackdrop) + app.component(CBackdrop.name as string, CBackdrop) }, } diff --git a/packages/coreui-vue/src/components/badge/CBadge.ts b/packages/coreui-vue/src/components/badge/CBadge.ts index 765d998a..3802015d 100644 --- a/packages/coreui-vue/src/components/badge/CBadge.ts +++ b/packages/coreui-vue/src/components/badge/CBadge.ts @@ -5,19 +5,19 @@ import { Color, Shape, TextColor } from '../../props' const CBadge = defineComponent({ name: 'CBadge', props: { - /** - * Sets the color context of the component to one of CoreUI’s themed colors. - * - * @values 'primary', 'secondary', 'success', 'danger', 'warning', 'info', 'dark', 'light' - */ - color: Color, /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'span', }, + /** + * Sets the color context of the component to one of CoreUI’s themed colors. + * + * @values 'primary', 'secondary', 'success', 'danger', 'warning', 'info', 'dark', 'light' + */ + color: Color, /** * Position badge in one of the corners of a link or button. * @@ -46,6 +46,13 @@ const CBadge = defineComponent({ return value === 'sm' }, }, + /** + * Sets the component's color scheme to one of CoreUI's themed colors, ensuring the text color contrast adheres to the WCAG 4.5:1 contrast ratio standard for accessibility. + * + * @values 'primary', 'secondary', 'success', 'danger', 'warning', 'info', 'dark', 'light' + * @since 5.0.0 + */ + textBgColor: Color, /** * Sets the text color of the component to one of CoreUI’s themed colors. * @@ -56,7 +63,7 @@ const CBadge = defineComponent({ setup(props, { slots }) { return () => h( - props.component, + props.as, { class: [ 'badge', @@ -69,6 +76,7 @@ const CBadge = defineComponent({ 'start-0': props.position && props.position.includes('start'), [`badge-${props.size}`]: props.size, [`text-${props.textColor}`]: props.textColor, + [`text-bg-${props.textBgColor}`]: props.textBgColor, }, props.shape, ], diff --git a/packages/coreui-vue/src/components/badge/index.ts b/packages/coreui-vue/src/components/badge/index.ts index 3bd6f86a..9a2e47a3 100644 --- a/packages/coreui-vue/src/components/badge/index.ts +++ b/packages/coreui-vue/src/components/badge/index.ts @@ -3,7 +3,7 @@ import { CBadge } from './CBadge' const CBadgePlugin = { install: (app: App): void => { - app.component(CBadge.name, CBadge) + app.component(CBadge.name as string, CBadge) }, } diff --git a/packages/coreui-vue/src/components/breadcrumb/index.ts b/packages/coreui-vue/src/components/breadcrumb/index.ts index 90b2b6f1..b7bad130 100644 --- a/packages/coreui-vue/src/components/breadcrumb/index.ts +++ b/packages/coreui-vue/src/components/breadcrumb/index.ts @@ -4,8 +4,8 @@ import { CBreadcrumb } from './CBreadcrumb' const CBreadcrumbPlugin = { install: (app: App): void => { - app.component(CBreadcrumb.name, CBreadcrumb) - app.component(CBreadcrumbItem.name, CBreadcrumbItem) + app.component(CBreadcrumb.name as string, CBreadcrumb) + app.component(CBreadcrumbItem.name as string, CBreadcrumbItem) }, } diff --git a/packages/coreui-vue/src/components/button-group/index.ts b/packages/coreui-vue/src/components/button-group/index.ts index ed8c71c5..f038168b 100644 --- a/packages/coreui-vue/src/components/button-group/index.ts +++ b/packages/coreui-vue/src/components/button-group/index.ts @@ -4,8 +4,8 @@ import { CButtonGroup } from './CButtonGroup' const CButtonGroupPlugin = { install: (app: App): void => { - app.component(CButtonToolbar.name, CButtonToolbar) - app.component(CButtonGroup.name, CButtonGroup) + app.component(CButtonToolbar.name as string, CButtonToolbar) + app.component(CButtonGroup.name as string, CButtonGroup) }, } diff --git a/packages/coreui-vue/src/components/button/CButton.ts b/packages/coreui-vue/src/components/button/CButton.ts index 235bd972..70f7e90d 100644 --- a/packages/coreui-vue/src/components/button/CButton.ts +++ b/packages/coreui-vue/src/components/button/CButton.ts @@ -9,19 +9,19 @@ export const CButton = defineComponent({ * Toggle the active state for the component. */ active: Boolean, - /** - * Sets the color context of the component to one of CoreUI’s themed colors. - * - * @values 'primary', 'secondary', 'success', 'danger', 'warning', 'info', 'dark', 'light' - */ - color: Color, /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'button', }, + /** + * Sets the color context of the component to one of CoreUI’s themed colors. + * + * @values 'primary', 'secondary', 'success', 'danger', 'warning', 'info', 'dark', 'light' + */ + color: Color, /** * Toggle the disabled state for the component. */ @@ -79,7 +79,7 @@ export const CButton = defineComponent({ 'click', ], setup(props, { emit, slots }) { - const component = props.href ? 'a' : props.component + const component = props.href ? 'a' : props.as const handleClick = (event: Event) => { if (props.disabled) { return @@ -93,8 +93,9 @@ export const CButton = defineComponent({ { class: [ 'btn', - props.variant ? `btn-${props.variant}-${props.color}` : `btn-${props.color}`, + props.variant && props.color ? `btn-${props.variant}-${props.color}` : `btn-${props.variant}`, { + [`btn-${props.color}`]: props.color && !props.variant, [`btn-${props.size}`]: props.size, active: props.active, disabled: props.disabled, diff --git a/packages/coreui-vue/src/components/button/__tests__/CButton.spec.ts b/packages/coreui-vue/src/components/button/__tests__/CButton.spec.ts index a739380f..a0aeded1 100644 --- a/packages/coreui-vue/src/components/button/__tests__/CButton.spec.ts +++ b/packages/coreui-vue/src/components/button/__tests__/CButton.spec.ts @@ -13,8 +13,8 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { active: true, + as: 'div', color: 'warning', - component: 'div', disabled: true, href: '/bazinga', shape: 'rounded-pill', @@ -28,8 +28,8 @@ const customWrapper = mount(Component, { const customWrapperTwo = mount(Component, { propsData: { + as: 'a', color: 'warning', - component: 'a', disabled: true, }, slots: { diff --git a/packages/coreui-vue/src/components/button/index.ts b/packages/coreui-vue/src/components/button/index.ts index 8a2531c0..679616e0 100644 --- a/packages/coreui-vue/src/components/button/index.ts +++ b/packages/coreui-vue/src/components/button/index.ts @@ -3,7 +3,7 @@ import { CButton } from './CButton' const CButtonPlugin = { install: (app: App): void => { - app.component(CButton.name, CButton) + app.component(CButton.name as string, CButton) }, } diff --git a/packages/coreui-vue/src/components/callout/index.ts b/packages/coreui-vue/src/components/callout/index.ts index 1fe788de..e0baf185 100644 --- a/packages/coreui-vue/src/components/callout/index.ts +++ b/packages/coreui-vue/src/components/callout/index.ts @@ -3,7 +3,7 @@ import { CCallout } from './CCallout' const CCalloutPlugin = { install: (app: App): void => { - app.component(CCallout.name, CCallout) + app.component(CCallout.name as string, CCallout) }, } diff --git a/packages/coreui-vue/src/components/card/CCard.ts b/packages/coreui-vue/src/components/card/CCard.ts index 34e0531a..580dc207 100644 --- a/packages/coreui-vue/src/components/card/CCard.ts +++ b/packages/coreui-vue/src/components/card/CCard.ts @@ -11,6 +11,13 @@ const CCard = defineComponent({ * @values 'primary', 'secondary', 'success', 'danger', 'warning', 'info', 'dark', 'light' */ color: Color, + /** + * Sets the component's color scheme to one of CoreUI's themed colors, ensuring the text color contrast adheres to the WCAG 4.5:1 contrast ratio standard for accessibility. + * + * @values 'primary', 'secondary', 'success', 'danger', 'warning', 'info', 'dark', 'light' + * @since 5.0.0 + */ + textBgColor: Color, /** * Sets the text color context of the component to one of CoreUI’s themed colors. * @@ -28,6 +35,7 @@ const CCard = defineComponent({ { [`bg-${props.color}`]: props.color, [`text-${props.textColor}`]: props.textColor, + [`text-bg-${props.textBgColor}`]: props.textBgColor, }, ], }, diff --git a/packages/coreui-vue/src/components/card/CCardHeader.ts b/packages/coreui-vue/src/components/card/CCardHeader.ts index 722ba187..d216d892 100644 --- a/packages/coreui-vue/src/components/card/CCardHeader.ts +++ b/packages/coreui-vue/src/components/card/CCardHeader.ts @@ -6,13 +6,13 @@ const CCardHeader = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'div', }, }, setup(props, { slots }) { - return () => h(props.component, { class: 'card-header' }, slots.default && slots.default()) + return () => h(props.as, { class: 'card-header' }, slots.default && slots.default()) }, }) diff --git a/packages/coreui-vue/src/components/card/CCardImage.ts b/packages/coreui-vue/src/components/card/CCardImage.ts index ee8695b9..81ff7fc0 100644 --- a/packages/coreui-vue/src/components/card/CCardImage.ts +++ b/packages/coreui-vue/src/components/card/CCardImage.ts @@ -6,7 +6,7 @@ const CCardImage = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'img', }, @@ -25,7 +25,7 @@ const CCardImage = defineComponent({ setup(props, { slots }) { return () => h( - props.component, + props.as, { class: `card-img${props.orientation ? `-${props.orientation}` : ''}`, }, diff --git a/packages/coreui-vue/src/components/card/CCardSubtitle.ts b/packages/coreui-vue/src/components/card/CCardSubtitle.ts index 5a950776..b3bdf933 100644 --- a/packages/coreui-vue/src/components/card/CCardSubtitle.ts +++ b/packages/coreui-vue/src/components/card/CCardSubtitle.ts @@ -6,13 +6,13 @@ const CCardSubtitle = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'h6', }, }, setup(props, { slots }) { - return () => h(props.component, { class: 'card-subtitle' }, slots.default && slots.default()) + return () => h(props.as, { class: 'card-subtitle' }, slots.default && slots.default()) }, }) export { CCardSubtitle } diff --git a/packages/coreui-vue/src/components/card/CCardText.ts b/packages/coreui-vue/src/components/card/CCardText.ts index 759ff7e8..0f26e875 100644 --- a/packages/coreui-vue/src/components/card/CCardText.ts +++ b/packages/coreui-vue/src/components/card/CCardText.ts @@ -6,13 +6,13 @@ const CCardText = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'p', }, }, setup(props, { slots }) { - return () => h(props.component, { class: 'card-text' }, slots.default && slots.default()) + return () => h(props.as, { class: 'card-text' }, slots.default && slots.default()) }, }) diff --git a/packages/coreui-vue/src/components/card/CCardTitle.ts b/packages/coreui-vue/src/components/card/CCardTitle.ts index bc540e15..4564bdf0 100644 --- a/packages/coreui-vue/src/components/card/CCardTitle.ts +++ b/packages/coreui-vue/src/components/card/CCardTitle.ts @@ -6,13 +6,13 @@ const CCardTitle = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'h5', }, }, setup(props, { slots }) { - return () => h(props.component, { class: 'card-title' }, slots.default && slots.default()) + return () => h(props.as, { class: 'card-title' }, slots.default && slots.default()) }, }) diff --git a/packages/coreui-vue/src/components/card/__tests__/CCardHeader.spec.ts b/packages/coreui-vue/src/components/card/__tests__/CCardHeader.spec.ts index ded11542..01e950c4 100644 --- a/packages/coreui-vue/src/components/card/__tests__/CCardHeader.spec.ts +++ b/packages/coreui-vue/src/components/card/__tests__/CCardHeader.spec.ts @@ -12,7 +12,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: 'span', + as: 'span', }, slots: { default: 'Default slot', diff --git a/packages/coreui-vue/src/components/card/__tests__/CCardImage.spec.ts b/packages/coreui-vue/src/components/card/__tests__/CCardImage.spec.ts index da8cf0fb..342a74fa 100644 --- a/packages/coreui-vue/src/components/card/__tests__/CCardImage.spec.ts +++ b/packages/coreui-vue/src/components/card/__tests__/CCardImage.spec.ts @@ -12,7 +12,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: 'a', + as: 'a', orientation: 'bottom', }, slots: { diff --git a/packages/coreui-vue/src/components/card/__tests__/CCardSubtitle.spec.ts b/packages/coreui-vue/src/components/card/__tests__/CCardSubtitle.spec.ts index ae90329c..d78f05ad 100644 --- a/packages/coreui-vue/src/components/card/__tests__/CCardSubtitle.spec.ts +++ b/packages/coreui-vue/src/components/card/__tests__/CCardSubtitle.spec.ts @@ -12,7 +12,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: 'h4', + as: 'h4', }, slots: { default: 'Default slot', diff --git a/packages/coreui-vue/src/components/card/__tests__/CCardText.spec.ts b/packages/coreui-vue/src/components/card/__tests__/CCardText.spec.ts index b77ce9ff..cf03e29a 100644 --- a/packages/coreui-vue/src/components/card/__tests__/CCardText.spec.ts +++ b/packages/coreui-vue/src/components/card/__tests__/CCardText.spec.ts @@ -12,7 +12,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: 'h4', + as: 'h4', }, slots: { default: 'Default slot', diff --git a/packages/coreui-vue/src/components/card/__tests__/CCardTitle.spec.ts b/packages/coreui-vue/src/components/card/__tests__/CCardTitle.spec.ts index b6628c9f..a3c28d5d 100644 --- a/packages/coreui-vue/src/components/card/__tests__/CCardTitle.spec.ts +++ b/packages/coreui-vue/src/components/card/__tests__/CCardTitle.spec.ts @@ -12,7 +12,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: 'h2', + as: 'h2', }, slots: { default: 'Default slot', diff --git a/packages/coreui-vue/src/components/card/index.ts b/packages/coreui-vue/src/components/card/index.ts index ef61b81a..cad0a6ca 100644 --- a/packages/coreui-vue/src/components/card/index.ts +++ b/packages/coreui-vue/src/components/card/index.ts @@ -13,17 +13,17 @@ import { CCardTitle } from './CCardTitle' const CCardPlugin = { install: (app: App): void => { - app.component(CCard.name, CCard) - app.component(CCardBody.name, CCardBody) - app.component(CCardFooter.name, CCardFooter) - app.component(CCardGroup.name, CCardGroup) - app.component(CCardHeader.name, CCardHeader) - app.component(CCardImage.name, CCardImage) - app.component(CCardImageOverlay.name, CCardImageOverlay) - app.component(CCardLink.name, CCardLink) - app.component(CCardSubtitle.name, CCardSubtitle) - app.component(CCardText.name, CCardText) - app.component(CCardTitle.name, CCardTitle) + app.component(CCard.name as string, CCard) + app.component(CCardBody.name as string, CCardBody) + app.component(CCardFooter.name as string, CCardFooter) + app.component(CCardGroup.name as string, CCardGroup) + app.component(CCardHeader.name as string, CCardHeader) + app.component(CCardImage.name as string, CCardImage) + app.component(CCardImageOverlay.name as string, CCardImageOverlay) + app.component(CCardLink.name as string, CCardLink) + app.component(CCardSubtitle.name as string, CCardSubtitle) + app.component(CCardText.name as string, CCardText) + app.component(CCardTitle.name as string, CCardTitle) }, } diff --git a/packages/coreui-vue/src/components/carousel/index.ts b/packages/coreui-vue/src/components/carousel/index.ts index 68c66fa5..59566ee8 100644 --- a/packages/coreui-vue/src/components/carousel/index.ts +++ b/packages/coreui-vue/src/components/carousel/index.ts @@ -5,9 +5,9 @@ import { CCarouselItem } from './CCarouselItem' const CCarouselPlugin = { install: (app: App): void => { - app.component(CCarousel.name, CCarousel) - app.component(CCarouselCaption.name, CCarouselCaption) - app.component(CCarouselItem.name, CCarouselItem) + app.component(CCarousel.name as string, CCarousel) + app.component(CCarouselCaption.name as string, CCarouselCaption) + app.component(CCarouselItem.name as string, CCarouselItem) }, } diff --git a/packages/coreui-vue/src/components/close-button/index.ts b/packages/coreui-vue/src/components/close-button/index.ts index 7c19c164..7445eb8f 100644 --- a/packages/coreui-vue/src/components/close-button/index.ts +++ b/packages/coreui-vue/src/components/close-button/index.ts @@ -3,7 +3,7 @@ import { CCloseButton } from './CCloseButton' const CCloseButtonPlugin = { install: (app: App): void => { - app.component(CCloseButton.name, CCloseButton) + app.component(CCloseButton.name as string, CCloseButton) }, } diff --git a/packages/coreui-vue/src/components/collapse/index.ts b/packages/coreui-vue/src/components/collapse/index.ts index c13fa246..2c6ccd7c 100644 --- a/packages/coreui-vue/src/components/collapse/index.ts +++ b/packages/coreui-vue/src/components/collapse/index.ts @@ -3,7 +3,7 @@ import { CCollapse } from './CCollapse' const CCollapsePlugin = { install: (app: App): void => { - app.component(CCollapse.name, CCollapse) + app.component(CCollapse.name as string, CCollapse) }, } diff --git a/packages/coreui-vue/src/components/conditional-teleport/CConditionalTeleport.ts b/packages/coreui-vue/src/components/conditional-teleport/CConditionalTeleport.ts index c380866a..9207fb9a 100644 --- a/packages/coreui-vue/src/components/conditional-teleport/CConditionalTeleport.ts +++ b/packages/coreui-vue/src/components/conditional-teleport/CConditionalTeleport.ts @@ -16,7 +16,7 @@ const CConditionalTeleport = defineComponent({ /** * An HTML element or function that returns a single element, with `document.body` as the default. * - * @since v5.0.0-rc.0 + * @since 5.0.0 */ container: { type: [Object, String] as PropType HTMLElement) | string>, diff --git a/packages/coreui-vue/src/components/conditional-teleport/index.ts b/packages/coreui-vue/src/components/conditional-teleport/index.ts index 249949b4..538edf16 100644 --- a/packages/coreui-vue/src/components/conditional-teleport/index.ts +++ b/packages/coreui-vue/src/components/conditional-teleport/index.ts @@ -3,7 +3,7 @@ import { CConditionalTeleport } from './CConditionalTeleport' const CConditionalTeleportPlugin = { install: (app: App): void => { - app.component(CConditionalTeleport.name, CConditionalTeleport) + app.component(CConditionalTeleport.name as string, CConditionalTeleport) }, } diff --git a/packages/coreui-vue/src/components/dropdown/CDropdown.ts b/packages/coreui-vue/src/components/dropdown/CDropdown.ts index bf4dc8f3..679e5d6f 100644 --- a/packages/coreui-vue/src/components/dropdown/CDropdown.ts +++ b/packages/coreui-vue/src/components/dropdown/CDropdown.ts @@ -3,10 +3,10 @@ import type { Placement } from '@popperjs/core' import { usePopper } from '../../composables' import type { Triggers } from '../../types' -import { isRTL } from '../../utils' +import { getNextActiveElement, isRTL } from '../../utils' import type { Alignments } from './types' -import { getNextActiveElement, getPlacement } from './utils' +import { getPlacement } from './utils' const CDropdown = defineComponent({ name: 'CDropdown', @@ -62,7 +62,7 @@ const CDropdown = defineComponent({ /** * Appends the vue dropdown menu to a specific element. You can pass an HTML element or function that returns a single element. By default `document.body`. * - * @since v5.0.0-rc.0 + * @since 5.0.0 */ container: { type: [Object, String] as PropType HTMLElement) | string>, @@ -115,7 +115,7 @@ const CDropdown = defineComponent({ /** * Generates dropdown menu using Teleport. * - * @since v5.0.0-rc.0 + * @since 5.0.0 */ teleport: { type: Boolean, @@ -228,6 +228,7 @@ const CDropdown = defineComponent({ ) { event.preventDefault() const target = event.target as HTMLElement + // eslint-disable-next-line unicorn/prefer-spread const items: HTMLElement[] = Array.from( dropdownMenuRef.value.querySelectorAll('.dropdown-item:not(.disabled):not(:disabled)'), ) diff --git a/packages/coreui-vue/src/components/dropdown/CDropdownHeader.ts b/packages/coreui-vue/src/components/dropdown/CDropdownHeader.ts index 0ee6018e..ea630404 100644 --- a/packages/coreui-vue/src/components/dropdown/CDropdownHeader.ts +++ b/packages/coreui-vue/src/components/dropdown/CDropdownHeader.ts @@ -6,7 +6,7 @@ const CDropdownHeader = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'h6', }, @@ -14,7 +14,7 @@ const CDropdownHeader = defineComponent({ setup(props, { slots }) { return () => h( - props.component, + props.as, { class: 'dropdown-header', }, diff --git a/packages/coreui-vue/src/components/dropdown/CDropdownItem.ts b/packages/coreui-vue/src/components/dropdown/CDropdownItem.ts index fdd01393..49b1ea68 100644 --- a/packages/coreui-vue/src/components/dropdown/CDropdownItem.ts +++ b/packages/coreui-vue/src/components/dropdown/CDropdownItem.ts @@ -12,7 +12,7 @@ const CDropdownItem = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'a', }, @@ -32,7 +32,7 @@ const CDropdownItem = defineComponent({ { class: 'dropdown-item', active: props.active, - component: props.component, + as: props.as, disabled: props.disabled, href: props.href, }, diff --git a/packages/coreui-vue/src/components/dropdown/CDropdownMenu.ts b/packages/coreui-vue/src/components/dropdown/CDropdownMenu.ts index 02fefabf..29791bac 100644 --- a/packages/coreui-vue/src/components/dropdown/CDropdownMenu.ts +++ b/packages/coreui-vue/src/components/dropdown/CDropdownMenu.ts @@ -12,7 +12,7 @@ const CDropdownMenu = defineComponent({ * * @values 'div', 'ul' */ - component: { + as: { type: String, default: 'div', }, @@ -34,7 +34,7 @@ const CDropdownMenu = defineComponent({ { default: () => h( - props.component, + props.as, { ...attrs, class: [ @@ -49,7 +49,7 @@ const CDropdownMenu = defineComponent({ ...(dark && { 'data-coreui-theme': 'dark' }), ref: dropdownMenuRef, }, - props.component === 'ul' + props.as === 'ul' ? slots.default && slots.default().map((vnode) => h('li', {}, vnode)) : slots.default && slots.default(), ), diff --git a/packages/coreui-vue/src/components/dropdown/CDropdownToggle.ts b/packages/coreui-vue/src/components/dropdown/CDropdownToggle.ts index 136a60b8..17fd034b 100644 --- a/packages/coreui-vue/src/components/dropdown/CDropdownToggle.ts +++ b/packages/coreui-vue/src/components/dropdown/CDropdownToggle.ts @@ -18,6 +18,13 @@ import type { Triggers } from '../../types' const CDropdownToggle = defineComponent({ name: 'CDropdownToggle', props: { + /** + * Component used for the root node. Either a string to use a HTML element or a component. + */ + as: { + type: String, + default: 'button', + }, /** * Sets the color context of the component to one of CoreUI’s themed colors. * @@ -31,13 +38,6 @@ const CDropdownToggle = defineComponent({ type: Boolean, default: true, }, - /** - * Component used for the root node. Either a string to use a HTML element or a component. - */ - component: { - type: String, - default: 'button', - }, /** * Create a custom toggler which accepts any content. */ @@ -49,7 +49,7 @@ const CDropdownToggle = defineComponent({ /** * If a dropdown `variant` is set to `nav-item` then render the toggler as a link instead of a button. * - * @since v5.0.0-rc.0 + * @since 5.0.0 */ navLink: { type: Boolean, @@ -162,35 +162,35 @@ const CDropdownToggle = defineComponent({ }), ) : dropdownVariant === 'nav-item' && props.navLink - ? h( - 'a', - { - href: '#', - ...togglerProps.value, - role: 'button', - ref: dropdownToggleRef, - }, - { default: () => slots.default && slots.default() }, - ) - : h( - CButton, - { - ...togglerProps.value, - color: props.color, - component: props.component, - disabled: props.disabled, - shape: props.shape, - size: props.size, - variant: props.variant, - ref: (el) => { - togglerRef.value = el + ? h( + 'a', + { + href: '#', + ...togglerProps.value, + role: 'button', + ref: dropdownToggleRef, }, - }, - () => - props.split - ? h('span', { class: 'visually-hidden' }, 'Toggle Dropdown') - : slots.default && slots.default(), - ) + { default: () => slots.default && slots.default() }, + ) + : h( + CButton, + { + ...togglerProps.value, + as: props.as, + color: props.color, + disabled: props.disabled, + shape: props.shape, + size: props.size, + variant: props.variant, + ref: (el) => { + togglerRef.value = el + }, + }, + () => + props.split + ? h('span', { class: 'visually-hidden' }, 'Toggle Dropdown') + : slots.default && slots.default(), + ) }, }) diff --git a/packages/coreui-vue/src/components/dropdown/__tests__/CDropdownHeader.spec.ts b/packages/coreui-vue/src/components/dropdown/__tests__/CDropdownHeader.spec.ts index 6ed72559..2401c7e8 100644 --- a/packages/coreui-vue/src/components/dropdown/__tests__/CDropdownHeader.spec.ts +++ b/packages/coreui-vue/src/components/dropdown/__tests__/CDropdownHeader.spec.ts @@ -12,7 +12,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: 'h2', + as: 'h2', }, slots: { default: 'Default slot', diff --git a/packages/coreui-vue/src/components/dropdown/__tests__/CDropdownItem.spec.ts b/packages/coreui-vue/src/components/dropdown/__tests__/CDropdownItem.spec.ts index bf153451..de5d2b11 100644 --- a/packages/coreui-vue/src/components/dropdown/__tests__/CDropdownItem.spec.ts +++ b/packages/coreui-vue/src/components/dropdown/__tests__/CDropdownItem.spec.ts @@ -13,7 +13,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { active: true, - component: 'div', + as: 'div', disabled: true, href: '/bazinga', }, diff --git a/packages/coreui-vue/src/components/dropdown/__tests__/CDropdownMenu.spec.ts b/packages/coreui-vue/src/components/dropdown/__tests__/CDropdownMenu.spec.ts index 265388de..23c7d3d7 100644 --- a/packages/coreui-vue/src/components/dropdown/__tests__/CDropdownMenu.spec.ts +++ b/packages/coreui-vue/src/components/dropdown/__tests__/CDropdownMenu.spec.ts @@ -32,7 +32,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: 'ul', + as: 'ul', }, slots: { default: 'Default slot', diff --git a/packages/coreui-vue/src/components/dropdown/index.ts b/packages/coreui-vue/src/components/dropdown/index.ts index 3bec8afd..aabcfeb8 100644 --- a/packages/coreui-vue/src/components/dropdown/index.ts +++ b/packages/coreui-vue/src/components/dropdown/index.ts @@ -8,12 +8,12 @@ import { CDropdownToggle } from './CDropdownToggle' const CDropdownPlugin = { install: (app: App): void => { - app.component(CDropdown.name, CDropdown) - app.component(CDropdownItem.name, CDropdownItem) - app.component(CDropdownHeader.name, CDropdownHeader) - app.component(CDropdownDivider.name, CDropdownDivider) - app.component(CDropdownMenu.name, CDropdownMenu) - app.component(CDropdownToggle.name, CDropdownToggle) + app.component(CDropdown.name as string, CDropdown) + app.component(CDropdownItem.name as string, CDropdownItem) + app.component(CDropdownHeader.name as string, CDropdownHeader) + app.component(CDropdownDivider.name as string, CDropdownDivider) + app.component(CDropdownMenu.name as string, CDropdownMenu) + app.component(CDropdownToggle.name as string, CDropdownToggle) }, } diff --git a/packages/coreui-vue/src/components/dropdown/utils.ts b/packages/coreui-vue/src/components/dropdown/utils.ts index edddb0db..c9659636 100644 --- a/packages/coreui-vue/src/components/dropdown/utils.ts +++ b/packages/coreui-vue/src/components/dropdown/utils.ts @@ -19,28 +19,6 @@ export const getAlignmentClassNames = (alignment: Alignments) => { return classNames } -export const getNextActiveElement = ( - list: HTMLElement[], - activeElement: HTMLElement, - shouldGetNext: boolean, - isCycleAllowed: boolean, -) => { - const listLength = list.length - let index = list.indexOf(activeElement) - - if (index === -1) { - return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0] - } - - index += shouldGetNext ? 1 : -1 - - if (isCycleAllowed) { - index = (index + listLength) % listLength - } - - return list[Math.max(0, Math.min(index, listLength - 1))] -} - export const getPlacement = ( placement: Placement, direction: string | undefined, diff --git a/packages/coreui-vue/src/components/footer/CFooter.ts b/packages/coreui-vue/src/components/footer/CFooter.ts index 81de0158..3f0d5fd2 100644 --- a/packages/coreui-vue/src/components/footer/CFooter.ts +++ b/packages/coreui-vue/src/components/footer/CFooter.ts @@ -3,6 +3,13 @@ import { defineComponent, h } from 'vue' const CFooter = defineComponent({ name: 'CFooter', props: { + /** + * Component used for the root node. Either a string to use a HTML element or a component. + */ + as: { + type: String, + default: 'div', + }, /** * Place footer in non-static positions. * @@ -18,7 +25,7 @@ const CFooter = defineComponent({ setup(props, { slots }) { return () => h( - 'div', + props.as, { class: ['footer', { [`footer-${props.position}`]: props.position }] }, slots.default && slots.default(), ) diff --git a/packages/coreui-vue/src/components/footer/__tests__/CFooter.spec.ts b/packages/coreui-vue/src/components/footer/__tests__/CFooter.spec.ts index 4a5ba536..35b914ca 100644 --- a/packages/coreui-vue/src/components/footer/__tests__/CFooter.spec.ts +++ b/packages/coreui-vue/src/components/footer/__tests__/CFooter.spec.ts @@ -19,6 +19,15 @@ const customWrapper = mount(Component, { }, }) +const customWrapperTwo = mount(Component, { + propsData: { + as: 'footer', + }, + slots: { + default: 'Default slot', + }, +}) + describe(`Loads and display ${ComponentName} component`, () => { it('has a name', () => { expect(Component.name).toMatch(ComponentName) @@ -42,3 +51,13 @@ describe(`Customize ${ComponentName} component`, () => { expect(customWrapper.classes('footer-fixed')).toBe(true) }) }) + +describe(`Customize (number two) ${ComponentName} component`, () => { + it('renders correctly', () => { + expect(customWrapperTwo.html()).toMatchSnapshot() + }) + + it('tag name is custom', () => { + expect(customWrapperTwo.element.tagName).toBe('FOOTER') + }) +}) diff --git a/packages/coreui-vue/src/components/footer/index.ts b/packages/coreui-vue/src/components/footer/index.ts index 3f64755a..8bd4edd4 100644 --- a/packages/coreui-vue/src/components/footer/index.ts +++ b/packages/coreui-vue/src/components/footer/index.ts @@ -3,7 +3,7 @@ import { CFooter } from './CFooter' const CFooterPlugin = { install: (app: App): void => { - app.component(CFooter.name, CFooter) + app.component(CFooter.name as string, CFooter) }, } diff --git a/packages/coreui-vue/src/components/form/CFormCheck.ts b/packages/coreui-vue/src/components/form/CFormCheck.ts index c7ed6edc..fbdd26fd 100644 --- a/packages/coreui-vue/src/components/form/CFormCheck.ts +++ b/packages/coreui-vue/src/components/form/CFormCheck.ts @@ -189,7 +189,7 @@ const CFormCheck = defineComponent({ const formControl = () => { return h('input', { ...attrs, - ...((props.modelValue || props.value) && { checked: isChecked.value }), + ...(props.modelValue && { checked: isChecked.value }), class: inputClassName, id: props.id, indeterminate: props.indeterminate, @@ -204,7 +204,7 @@ const CFormCheck = defineComponent({ ? h( CButton, { - component: 'label', + as: 'label', ...props.button, ...(props.id && { for: props.id }), }, @@ -236,26 +236,26 @@ const CFormCheck = defineComponent({ props.button ? [formControl(), (slots.label || props.label) && formLabel(), formValidation()] : props.label - ? props.hitArea - ? [ - h( - CFormLabel, + ? props.hitArea + ? [ + h( + CFormLabel, + { + customClassName: className, + ...(props.id && { for: props.id }), + }, + [formControl(), props.label], + ), + formValidation(), + ] + : h( + 'div', { - customClassName: className, - ...(props.id && { for: props.id }), + class: className, }, - [formControl(), props.label], - ), - formValidation(), - ] - : h( - 'div', - { - class: className, - }, - [formControl(), props.label && formLabel(), formValidation()], - ) - : formControl() + [formControl(), props.label && formLabel(), formValidation()], + ) + : formControl() }, }) diff --git a/packages/coreui-vue/src/components/form/CFormControlWrapper.ts b/packages/coreui-vue/src/components/form/CFormControlWrapper.ts index e4aee290..0ed3d195 100644 --- a/packages/coreui-vue/src/components/form/CFormControlWrapper.ts +++ b/packages/coreui-vue/src/components/form/CFormControlWrapper.ts @@ -4,11 +4,27 @@ import { CFormFloating } from './CFormFloating' import { CFormLabel } from './CFormLabel' import { CFormText } from './CFormText' +import type { ComponentProps } from '../../utils/ComponentProps' + +interface CFormControlWrapperProps extends ComponentProps { + floatingClassName?: string + floatingLabel?: string + id?: string + label?: string + text?: string +} + const CFormControlWrapper = defineComponent({ name: 'CFormControlWrapper', inheritAttrs: false, props: { ...CFormControlValidation.props, + /** + * A string of all className you want applied to the floating label wrapper. + * + * @since 5.5.0 + */ + floatingClassName: String, /** * Provide valuable, actionable valid feedback when using standard HTML form validation which applied two CSS pseudo-classes, `:invalid` and `:valid`. * @@ -18,7 +34,9 @@ const CFormControlWrapper = defineComponent({ /** * @ignore */ - id: String, + id: { + type: String, + }, /** * Add a caption for a component. * @@ -32,7 +50,7 @@ const CFormControlWrapper = defineComponent({ */ text: String, }, - setup(props, { slots }) { + setup(props: CFormControlWrapperProps, { slots }) { const formControlValidation = () => h( CFormControlValidation, @@ -41,7 +59,6 @@ const CFormControlWrapper = defineComponent({ feedback: props.feedback, feedbackInvalid: props.feedbackInvalid, feedbackValid: props.feedbackValid, - floatingLabel: props.floatingLabel, invalid: props.invalid, tooltipFeedback: props.tooltipFeedback, valid: props.valid, @@ -59,29 +76,36 @@ const CFormControlWrapper = defineComponent({ return () => props.floatingLabel - ? h(CFormFloating, () => [ - slots.default && slots.default(), - h( - CFormLabel, - { - for: props.id, - }, - { - default: () => (slots.label && slots.label()) || props.label || props.floatingLabel, - }, - ), - (props.text || slots.text) && + ? h( + CFormFloating, + { + class: props.floatingClassName, + }, + () => [ + slots.default && slots.default(), h( - CFormText, + CFormLabel, { - id: props.describedby, + for: props.id, }, { - default: () => (slots.text && slots.text()) || props.text, + default: () => + (slots.label && slots.label()) || props.label || props.floatingLabel, }, ), - formControlValidation(), - ]) + (props.text || slots.text) && + h( + CFormText, + { + id: props.describedby, + }, + { + default: () => (slots.text && slots.text()) || props.text, + }, + ), + formControlValidation(), + ], + ) : [ (props.label || slots.label) && h( diff --git a/packages/coreui-vue/src/components/form/CFormFeedback.ts b/packages/coreui-vue/src/components/form/CFormFeedback.ts index 3a7d9a16..df4d496e 100644 --- a/packages/coreui-vue/src/components/form/CFormFeedback.ts +++ b/packages/coreui-vue/src/components/form/CFormFeedback.ts @@ -6,7 +6,7 @@ const CFormFeedback = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'div', }, @@ -26,7 +26,7 @@ const CFormFeedback = defineComponent({ setup(props, { slots }) { return () => h( - props.component, + props.as, { class: [ { diff --git a/packages/coreui-vue/src/components/form/CFormInput.ts b/packages/coreui-vue/src/components/form/CFormInput.ts index 68746233..8dbe4eca 100644 --- a/packages/coreui-vue/src/components/form/CFormInput.ts +++ b/packages/coreui-vue/src/components/form/CFormInput.ts @@ -130,7 +130,9 @@ const CFormInput = defineComponent({ h( CFormControlWrapper, { - describedby: attrs['aria-describedby'], + ...(typeof attrs['aria-describedby'] === 'string' && { + describedby: attrs['aria-describedby'], + }), feedback: props.feedback, feedbackInvalid: props.feedbackInvalid, feedbackValid: props.feedbackValid, diff --git a/packages/coreui-vue/src/components/form/CFormSelect.ts b/packages/coreui-vue/src/components/form/CFormSelect.ts index b3086b60..23bd7b1b 100644 --- a/packages/coreui-vue/src/components/form/CFormSelect.ts +++ b/packages/coreui-vue/src/components/form/CFormSelect.ts @@ -119,7 +119,9 @@ const CFormSelect = defineComponent({ h( CFormControlWrapper, { - describedby: attrs['aria-describedby'], + ...(typeof attrs['aria-describedby'] === 'string' && { + describedby: attrs['aria-describedby'], + }), feedback: props.feedback, feedbackInvalid: props.feedbackInvalid, feedbackValid: props.feedbackValid, diff --git a/packages/coreui-vue/src/components/form/CFormSwitch.ts b/packages/coreui-vue/src/components/form/CFormSwitch.ts index a1c54a1e..6b75cd36 100644 --- a/packages/coreui-vue/src/components/form/CFormSwitch.ts +++ b/packages/coreui-vue/src/components/form/CFormSwitch.ts @@ -94,6 +94,7 @@ const CFormSwitch = defineComponent({ 'is-invalid': props.invalid, 'is-valid': props.valid, }, + attrs.class, ], id: props.id, onChange: (event: InputEvent) => handleChange(event), diff --git a/packages/coreui-vue/src/components/form/CFormText.ts b/packages/coreui-vue/src/components/form/CFormText.ts index 13b47ada..5ed6b00c 100644 --- a/packages/coreui-vue/src/components/form/CFormText.ts +++ b/packages/coreui-vue/src/components/form/CFormText.ts @@ -6,13 +6,13 @@ const CFormText = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'div', }, }, setup(props, { slots }) { - return () => h(props.component, { class: 'form-text' }, slots.default && slots.default()) + return () => h(props.as, { class: 'form-text' }, slots.default && slots.default()) }, }) diff --git a/packages/coreui-vue/src/components/form/CFormTextarea.ts b/packages/coreui-vue/src/components/form/CFormTextarea.ts index 21fb3596..42a8a732 100644 --- a/packages/coreui-vue/src/components/form/CFormTextarea.ts +++ b/packages/coreui-vue/src/components/form/CFormTextarea.ts @@ -106,7 +106,9 @@ const CFormTextarea = defineComponent({ h( CFormControlWrapper, { - describedby: attrs['aria-describedby'], + ...(typeof attrs['aria-describedby'] === 'string' && { + describedby: attrs['aria-describedby'], + }), feedback: props.feedback, feedbackInvalid: props.feedbackInvalid, feedbackValid: props.feedbackValid, @@ -133,6 +135,7 @@ const CFormTextarea = defineComponent({ 'is-invalid': props.invalid, 'is-valid': props.valid, }, + attrs.class, ], onChange: (event: InputEvent) => handleChange(event), onInput: (event: InputEvent) => handleInput(event), diff --git a/packages/coreui-vue/src/components/form/CInputGroupText.ts b/packages/coreui-vue/src/components/form/CInputGroupText.ts index 92717357..d44172a7 100644 --- a/packages/coreui-vue/src/components/form/CInputGroupText.ts +++ b/packages/coreui-vue/src/components/form/CInputGroupText.ts @@ -6,13 +6,13 @@ const CInputGroupText = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'span', }, }, setup(props, { slots }) { - return () => h(props.component, { class: 'input-group-text' }, slots.default && slots.default()) + return () => h(props.as, { class: 'input-group-text' }, slots.default && slots.default()) }, }) diff --git a/packages/coreui-vue/src/components/form/__tests__/CFormFeedback.spec.ts b/packages/coreui-vue/src/components/form/__tests__/CFormFeedback.spec.ts index 86349ea2..c1351c6c 100644 --- a/packages/coreui-vue/src/components/form/__tests__/CFormFeedback.spec.ts +++ b/packages/coreui-vue/src/components/form/__tests__/CFormFeedback.spec.ts @@ -12,7 +12,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: 'h2', + as: 'h2', invalid: true, tooltip: true, valid: true, @@ -24,7 +24,7 @@ const customWrapper = mount(Component, { const customWrapperTwo = mount(Component, { propsData: { - component: 'h2', + as: 'h2', invalid: true, tooltip: false, valid: true, diff --git a/packages/coreui-vue/src/components/form/__tests__/CFormText.spec.ts b/packages/coreui-vue/src/components/form/__tests__/CFormText.spec.ts index 224ad547..16a5de47 100644 --- a/packages/coreui-vue/src/components/form/__tests__/CFormText.spec.ts +++ b/packages/coreui-vue/src/components/form/__tests__/CFormText.spec.ts @@ -12,7 +12,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: 'p', + as 'p', }, slots: { default: 'Default slot', diff --git a/packages/coreui-vue/src/components/form/__tests__/CInputGroupText.spec.ts b/packages/coreui-vue/src/components/form/__tests__/CInputGroupText.spec.ts index 2ba2af6d..48a39878 100644 --- a/packages/coreui-vue/src/components/form/__tests__/CInputGroupText.spec.ts +++ b/packages/coreui-vue/src/components/form/__tests__/CInputGroupText.spec.ts @@ -12,7 +12,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: 'div', + as: 'div', }, slots: { default: 'Default slot', diff --git a/packages/coreui-vue/src/components/form/index.ts b/packages/coreui-vue/src/components/form/index.ts index 583df8d7..6fa6fb5c 100644 --- a/packages/coreui-vue/src/components/form/index.ts +++ b/packages/coreui-vue/src/components/form/index.ts @@ -1,7 +1,6 @@ import { App } from 'vue' import { CForm } from './CForm' import { CFormCheck } from './CFormCheck' -// import { CFormControl } from './CFormControl' import { CFormFeedback } from './CFormFeedback' import { CFormFloating } from './CFormFloating' import { CFormInput } from './CFormInput' @@ -16,20 +15,19 @@ import { CInputGroupText } from './CInputGroupText' const CFormPlugin = { install: (app: App): void => { - app.component(CForm.name, CForm) - app.component(CFormCheck.name, CFormCheck) - // app.component(CFormControl.name, CFormControl) - app.component(CFormFeedback.name, CFormFeedback) - app.component(CFormFloating.name, CFormFloating) - app.component(CFormInput.name, CFormInput) - app.component(CFormLabel.name, CFormLabel) - app.component(CFormRange.name, CFormRange) - app.component(CFormSelect.name, CFormSelect) - app.component(CFormSwitch.name, CFormSwitch) - app.component(CFormText.name, CFormText) - app.component(CFormTextarea.name, CFormTextarea) - app.component(CInputGroup.name, CInputGroup) - app.component(CInputGroupText.name, CInputGroupText) + app.component(CForm.name as string, CForm) + app.component(CFormCheck.name as string, CFormCheck) + app.component(CFormFeedback.name as string, CFormFeedback) + app.component(CFormFloating.name as string, CFormFloating) + app.component(CFormInput.name as string, CFormInput) + app.component(CFormLabel.name as string, CFormLabel) + app.component(CFormRange.name as string, CFormRange) + app.component(CFormSelect.name as string, CFormSelect) + app.component(CFormSwitch.name as string, CFormSwitch) + app.component(CFormText.name as string, CFormText) + app.component(CFormTextarea.name as string, CFormTextarea) + app.component(CInputGroup.name as string, CInputGroup) + app.component(CInputGroupText.name as string, CInputGroupText) }, } @@ -37,7 +35,6 @@ export { CFormPlugin, CForm, CFormCheck, - // CFormControl, CFormFeedback, CFormFloating, CFormInput, diff --git a/packages/coreui-vue/src/components/grid/index.ts b/packages/coreui-vue/src/components/grid/index.ts index 1af418a2..785efaac 100644 --- a/packages/coreui-vue/src/components/grid/index.ts +++ b/packages/coreui-vue/src/components/grid/index.ts @@ -5,9 +5,9 @@ import { CRow } from './CRow' const CGridPlugin = { install: (app: App): void => { - app.component(CCol.name, CCol) - app.component(CContainer.name, CContainer) - app.component(CRow.name, CRow) + app.component(CCol.name as string, CCol) + app.component(CContainer.name as string, CContainer) + app.component(CRow.name as string, CRow) }, } export { CGridPlugin, CCol, CContainer, CRow } diff --git a/packages/coreui-vue/src/components/header/CHeader.ts b/packages/coreui-vue/src/components/header/CHeader.ts index 32ab0bf5..d1c23b45 100644 --- a/packages/coreui-vue/src/components/header/CHeader.ts +++ b/packages/coreui-vue/src/components/header/CHeader.ts @@ -3,6 +3,13 @@ import { defineComponent, h } from 'vue' const CHeader = defineComponent({ name: 'CHeader', props: { + /** + * Component used for the root node. Either a string to use a HTML element or a component. + */ + as: { + type: String, + default: 'div', + }, /** * Defines optional container wrapping children elements. * @@ -31,7 +38,7 @@ const CHeader = defineComponent({ setup(props, { slots }) { return () => h( - 'div', + props.as, { class: ['header', { [`header-${props.position}`]: props.position }] }, props.container ? h( diff --git a/packages/coreui-vue/src/components/header/CHeaderBrand.ts b/packages/coreui-vue/src/components/header/CHeaderBrand.ts index 97ee9581..3f94fb86 100644 --- a/packages/coreui-vue/src/components/header/CHeaderBrand.ts +++ b/packages/coreui-vue/src/components/header/CHeaderBrand.ts @@ -6,13 +6,13 @@ const CHeaderBrand = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'a', }, }, setup(props, { slots }) { - return () => h(props.component, { class: 'header-brand' }, slots.default && slots.default()) + return () => h(props.as, { class: 'header-brand' }, slots.default && slots.default()) }, }) diff --git a/packages/coreui-vue/src/components/header/CHeaderNav.ts b/packages/coreui-vue/src/components/header/CHeaderNav.ts index 1fa1e7c7..e34277e0 100644 --- a/packages/coreui-vue/src/components/header/CHeaderNav.ts +++ b/packages/coreui-vue/src/components/header/CHeaderNav.ts @@ -6,7 +6,7 @@ const CHeaderNav = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'ul', }, @@ -14,7 +14,7 @@ const CHeaderNav = defineComponent({ setup(props, { slots }) { return () => h( - props.component, + props.as, { class: 'header-nav', role: 'navigation', diff --git a/packages/coreui-vue/src/components/header/__tests__/CHeader.spec.ts b/packages/coreui-vue/src/components/header/__tests__/CHeader.spec.ts index 2bb30916..09ec0065 100644 --- a/packages/coreui-vue/src/components/header/__tests__/CHeader.spec.ts +++ b/packages/coreui-vue/src/components/header/__tests__/CHeader.spec.ts @@ -20,6 +20,15 @@ const customWrapper = mount(Component, { }, }) +const customWrapperTwo = mount(Component, { + propsData: { + as: 'header', + }, + slots: { + default: 'Default slot', + }, +}) + describe(`Loads and display ${ComponentName} component`, () => { it('has a name', () => { expect(Component.name).toMatch(ComponentName) @@ -44,3 +53,13 @@ describe(`Customize ${ComponentName} component`, () => { expect(customWrapper.find('.container-lg').classes('container-lg')).toBe(true) }) }) + + +describe(`Customize (number two) ${ComponentName} component`, () => { + it('renders correctly', () => { + expect(customWrapperTwo.html()).toMatchSnapshot() + }) + it('tag name is custom', () => { + expect(customWrapperTwo.element.tagName).toBe('HEADER') + }) +}) \ No newline at end of file diff --git a/packages/coreui-vue/src/components/header/__tests__/CHeaderBrand.spec.ts b/packages/coreui-vue/src/components/header/__tests__/CHeaderBrand.spec.ts index 10df823a..391e1e0d 100644 --- a/packages/coreui-vue/src/components/header/__tests__/CHeaderBrand.spec.ts +++ b/packages/coreui-vue/src/components/header/__tests__/CHeaderBrand.spec.ts @@ -12,7 +12,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: 'div', + as: 'div', }, slots: { default: 'Default slot', diff --git a/packages/coreui-vue/src/components/header/__tests__/CHeaderNav.spec.ts b/packages/coreui-vue/src/components/header/__tests__/CHeaderNav.spec.ts index 13867aaa..6564189f 100644 --- a/packages/coreui-vue/src/components/header/__tests__/CHeaderNav.spec.ts +++ b/packages/coreui-vue/src/components/header/__tests__/CHeaderNav.spec.ts @@ -12,7 +12,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: 'div', + as: 'div', }, slots: { default: 'Default slot', diff --git a/packages/coreui-vue/src/components/header/index.ts b/packages/coreui-vue/src/components/header/index.ts index 3b8c7ddd..85c25975 100644 --- a/packages/coreui-vue/src/components/header/index.ts +++ b/packages/coreui-vue/src/components/header/index.ts @@ -8,12 +8,12 @@ import { CHeaderToggler } from './CHeaderToggler' const CHeaderPlugin = { install: (app: App): void => { - app.component(CHeader.name, CHeader) - app.component(CHeaderBrand.name, CHeaderBrand) - app.component(CHeaderDivider.name, CHeaderDivider) - app.component(CHeaderNav.name, CHeaderNav) - app.component(CHeaderText.name, CHeaderText) - app.component(CHeaderToggler.name, CHeaderToggler) + app.component(CHeader.name as string, CHeader) + app.component(CHeaderBrand.name as string, CHeaderBrand) + app.component(CHeaderDivider.name as string, CHeaderDivider) + app.component(CHeaderNav.name as string, CHeaderNav) + app.component(CHeaderText.name as string, CHeaderText) + app.component(CHeaderToggler.name as string, CHeaderToggler) }, } diff --git a/packages/coreui-vue/src/components/image/index.ts b/packages/coreui-vue/src/components/image/index.ts index 072142f4..cd4c69ae 100644 --- a/packages/coreui-vue/src/components/image/index.ts +++ b/packages/coreui-vue/src/components/image/index.ts @@ -3,7 +3,7 @@ import { CImage } from './CImage' const CImagePlugin = { install: (app: App): void => { - app.component(CImage.name, CImage) + app.component(CImage.name as string, CImage) }, } diff --git a/packages/coreui-vue/src/components/link/CLink.ts b/packages/coreui-vue/src/components/link/CLink.ts index d2627605..78369306 100644 --- a/packages/coreui-vue/src/components/link/CLink.ts +++ b/packages/coreui-vue/src/components/link/CLink.ts @@ -10,7 +10,7 @@ const CLink = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'a', }, @@ -39,12 +39,12 @@ const CLink = defineComponent({ } return () => h( - props.component, + props.as, { class: [{ active: props.active, disabled: props.disabled }], ...(props.active && { 'aria-current': 'page' }), - ...(props.component === 'a' && props.disabled && { 'aria-disabled': true, tabIndex: -1 }), - ...((props.component === 'a' || props.component === 'button') && { + ...(props.as === 'a' && props.disabled && { 'aria-disabled': true, tabIndex: -1 }), + ...((props.as === 'a' || props.as === 'button') && { onClick: handleClick, }), href: props.href, diff --git a/packages/coreui-vue/src/components/link/__tests__/CLink.spec.ts b/packages/coreui-vue/src/components/link/__tests__/CLink.spec.ts index f3c8c7dd..59a21a9c 100644 --- a/packages/coreui-vue/src/components/link/__tests__/CLink.spec.ts +++ b/packages/coreui-vue/src/components/link/__tests__/CLink.spec.ts @@ -15,7 +15,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { active: true, - component: 'div', + as: 'div', disabled: true, href: '/bazinga', }, diff --git a/packages/coreui-vue/src/components/link/index.ts b/packages/coreui-vue/src/components/link/index.ts index 4e45e441..04be72dd 100644 --- a/packages/coreui-vue/src/components/link/index.ts +++ b/packages/coreui-vue/src/components/link/index.ts @@ -3,7 +3,7 @@ import { CLink } from './CLink' const CCLinkPlugin = { install: (app: App): void => { - app.component(CLink.name, CLink) + app.component(CLink.name as string, CLink) }, } diff --git a/packages/coreui-vue/src/components/list-group/CListGroup.ts b/packages/coreui-vue/src/components/list-group/CListGroup.ts index 57003be4..9a565fe0 100644 --- a/packages/coreui-vue/src/components/list-group/CListGroup.ts +++ b/packages/coreui-vue/src/components/list-group/CListGroup.ts @@ -6,7 +6,7 @@ const CListGroup = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'ul', }, @@ -36,7 +36,7 @@ const CListGroup = defineComponent({ setup(props, { slots }) { return () => h( - props.component, + props.as, { class: [ 'list-group', diff --git a/packages/coreui-vue/src/components/list-group/CListGroupItem.ts b/packages/coreui-vue/src/components/list-group/CListGroupItem.ts index 0342f1fe..204df42f 100644 --- a/packages/coreui-vue/src/components/list-group/CListGroupItem.ts +++ b/packages/coreui-vue/src/components/list-group/CListGroupItem.ts @@ -9,6 +9,13 @@ const CListGroupItem = defineComponent({ * Toggle the active state for the component. */ active: Boolean, + /** + * Component used for the root node. Either a string to use a HTML element or a component. + */ + as: { + type: String, + default: 'li', + }, /** * Sets the color context of the component to one of CoreUI’s themed colors. * @@ -19,29 +26,22 @@ const CListGroupItem = defineComponent({ * Toggle the disabled state for the component. */ disabled: Boolean, - /** - * Component used for the root node. Either a string to use a HTML element or a component. - */ - component: { - type: String, - default: 'li', - }, }, setup(props, { slots }) { return () => h( - props.component, + props.as, { class: [ 'list-group-item', { [`list-group-item-${props.color}`]: props.color, - 'list-group-item-action': props.component === 'a' || props.component === 'button', + 'list-group-item-action': props.as === 'a' || props.as === 'button', [`active`]: props.active, [`disabled`]: props.disabled, }, ], - ...((props.component === 'a' || props.component === 'button') && { + ...((props.as === 'a' || props.as === 'button') && { active: props.active, disabled: props.disabled, }), diff --git a/packages/coreui-vue/src/components/list-group/__tests__/CListGroup.spec.ts b/packages/coreui-vue/src/components/list-group/__tests__/CListGroup.spec.ts index 092d62c2..f80906c1 100644 --- a/packages/coreui-vue/src/components/list-group/__tests__/CListGroup.spec.ts +++ b/packages/coreui-vue/src/components/list-group/__tests__/CListGroup.spec.ts @@ -12,7 +12,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: 'div', + as: 'div', flush: true, layout: 'horizontal-lg', }, diff --git a/packages/coreui-vue/src/components/list-group/__tests__/CListGroupItem.spec.ts b/packages/coreui-vue/src/components/list-group/__tests__/CListGroupItem.spec.ts index 41e9def2..763880b5 100644 --- a/packages/coreui-vue/src/components/list-group/__tests__/CListGroupItem.spec.ts +++ b/packages/coreui-vue/src/components/list-group/__tests__/CListGroupItem.spec.ts @@ -13,9 +13,9 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { active: true, + as: 'div', color: 'warning', disabled: true, - component: 'div', }, slots: { default: 'Default slot', @@ -24,8 +24,8 @@ const customWrapper = mount(Component, { const customWrapperTwo = mount(Component, { propsData: { - component: 'button', active: true, + as: 'button', disabled: true, }, slots: { diff --git a/packages/coreui-vue/src/components/list-group/index.ts b/packages/coreui-vue/src/components/list-group/index.ts index 3aaafb5b..d969a028 100644 --- a/packages/coreui-vue/src/components/list-group/index.ts +++ b/packages/coreui-vue/src/components/list-group/index.ts @@ -4,8 +4,8 @@ import { CListGroupItem } from './CListGroupItem' const CListGroupPlugin = { install: (app: App): void => { - app.component(CListGroup.name, CListGroup) - app.component(CListGroupItem.name, CListGroupItem) + app.component(CListGroup.name as string, CListGroup) + app.component(CListGroupItem.name as string, CListGroupItem) }, } diff --git a/packages/coreui-vue/src/components/modal/CModal.ts b/packages/coreui-vue/src/components/modal/CModal.ts index af05a0ca..e2e1947d 100644 --- a/packages/coreui-vue/src/components/modal/CModal.ts +++ b/packages/coreui-vue/src/components/modal/CModal.ts @@ -1,6 +1,7 @@ import { defineComponent, h, + PropType, provide, ref, RendererElement, @@ -11,6 +12,7 @@ import { } from 'vue' import { CBackdrop } from '../backdrop/CBackdrop' +import { CConditionalTeleport } from '../conditional-teleport' import { executeAfterTransition } from '../../utils/transition' @@ -47,6 +49,15 @@ const CModal = defineComponent({ return false }, }, + /** + * Appends the vue popover to a specific element. You can pass an HTML element or function that returns a single element. By default `document.body`. + * + * @since 5.3.0 + */ + container: { + type: [Object, String] as PropType HTMLElement) | string>, + default: 'body', + }, /** * A string of all className you want applied to the modal content component. */ @@ -54,7 +65,7 @@ const CModal = defineComponent({ /** * Puts the focus on the modal when shown. * - * @since v5.0.0-rc.0 + * @since 5.0.0 */ focus: { type: Boolean, @@ -99,6 +110,15 @@ const CModal = defineComponent({ return ['sm', 'lg', 'xl'].includes(value) }, }, + /** + * Generates modal using Teleport. + * + * @since 5.3.0 + */ + teleport: { + type: Boolean, + default: false, + }, /** * Remove animation to create modal that simply appear rather than fade in to view. */ @@ -264,27 +284,37 @@ const CModal = defineComponent({ ), ) - return () => [ + return () => h( - Transition, + CConditionalTeleport, { - css: false, - onEnter: (el, done) => handleEnter(el, done), - onAfterEnter: () => handleAfterEnter(), - onLeave: (el, done) => handleLeave(el, done), - onAfterLeave: (el) => handleAfterLeave(el), + container: props.container, + teleport: props.teleport, }, - () => - props.unmountOnClose - ? visible.value && modal() - : withDirectives(modal(), [[vShow, visible.value]]), - ), - props.backdrop && - h(CBackdrop, { - class: 'modal-backdrop', - visible: visible.value, - }), - ] + { + default: () => [ + h( + Transition, + { + css: false, + onEnter: (el, done) => handleEnter(el, done), + onAfterEnter: () => handleAfterEnter(), + onLeave: (el, done) => handleLeave(el, done), + onAfterLeave: (el) => handleAfterLeave(el), + }, + () => + props.unmountOnClose + ? visible.value && modal() + : withDirectives(modal(), [[vShow, visible.value]]), + ), + props.backdrop && + h(CBackdrop, { + class: 'modal-backdrop', + visible: visible.value, + }), + ], + }, + ) }, }) diff --git a/packages/coreui-vue/src/components/modal/CModalTitle.ts b/packages/coreui-vue/src/components/modal/CModalTitle.ts index ea970c09..a845f68c 100644 --- a/packages/coreui-vue/src/components/modal/CModalTitle.ts +++ b/packages/coreui-vue/src/components/modal/CModalTitle.ts @@ -6,13 +6,13 @@ const CModalTitle = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'h5', }, }, setup(props, { slots }) { - return () => h(props.component, { class: 'modal-title' }, slots.default && slots.default()) + return () => h(props.as, { class: 'modal-title' }, slots.default && slots.default()) }, }) diff --git a/packages/coreui-vue/src/components/modal/__tests__/CModalTitle.spec.ts b/packages/coreui-vue/src/components/modal/__tests__/CModalTitle.spec.ts index 28df672b..387f355d 100644 --- a/packages/coreui-vue/src/components/modal/__tests__/CModalTitle.spec.ts +++ b/packages/coreui-vue/src/components/modal/__tests__/CModalTitle.spec.ts @@ -12,7 +12,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: 'div', + as: 'div', }, slots: { default: 'Default slot', diff --git a/packages/coreui-vue/src/components/modal/index.ts b/packages/coreui-vue/src/components/modal/index.ts index 7e1c8e6b..4ebad336 100644 --- a/packages/coreui-vue/src/components/modal/index.ts +++ b/packages/coreui-vue/src/components/modal/index.ts @@ -7,11 +7,11 @@ import { CModalTitle } from './CModalTitle' const CModalPlugin = { install: (app: App): void => { - app.component(CModal.name, CModal) - app.component(CModalBody.name, CModalBody) - app.component(CModalFooter.name, CModalFooter) - app.component(CModalHeader.name, CModalHeader) - app.component(CModalTitle.name, CModalTitle) + app.component(CModal.name as string, CModal) + app.component(CModalBody.name as string, CModalBody) + app.component(CModalFooter.name as string, CModalFooter) + app.component(CModalHeader.name as string, CModalHeader) + app.component(CModalTitle.name as string, CModalTitle) }, } diff --git a/packages/coreui-vue/src/components/nav/CNav.ts b/packages/coreui-vue/src/components/nav/CNav.ts index ebde0a25..6b5b7384 100644 --- a/packages/coreui-vue/src/components/nav/CNav.ts +++ b/packages/coreui-vue/src/components/nav/CNav.ts @@ -6,7 +6,7 @@ const CNav = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'ul', }, @@ -24,22 +24,23 @@ const CNav = defineComponent({ /** * Set the nav variant to tabs or pills. * - * @values 'pills', 'tabs', 'underline', 'underline-border' + * @values 'enclosed', 'enclosed-pills', 'pills', 'tabs', 'underline', 'underline-border' */ variant: { type: String, validator: (value: string) => { - return ['pills', 'tabs', 'underline', 'underline-border'].includes(value) + return ['enclosed', 'enclosed-pills', 'pills', 'tabs', 'underline', 'underline-border'].includes(value) }, }, }, setup(props, { slots }) { return () => h( - props.component, + props.as, { class: [ 'nav', + props.variant === 'enclosed-pills' && 'nav-enclosed', { [`nav-${props.layout}`]: props.layout, [`nav-${props.variant}`]: props.variant, diff --git a/packages/coreui-vue/src/components/nav/CNavGroup.ts b/packages/coreui-vue/src/components/nav/CNavGroup.ts index a0e8815f..7c45bd7c 100644 --- a/packages/coreui-vue/src/components/nav/CNavGroup.ts +++ b/packages/coreui-vue/src/components/nav/CNavGroup.ts @@ -5,6 +5,13 @@ import { executeAfterTransition } from '../../utils/transition' const CNavGroup = defineComponent({ name: 'CNavGroup', props: { + /** + * Component used for the root node. Either a string to use a HTML element or a component. + */ + as: { + type: String, + default: 'li', + }, /** * Make nav group more compact by cutting all `padding` in half. */ @@ -34,7 +41,10 @@ const CNavGroup = defineComponent({ onMounted(() => { visible.value = props.visible - props.visible && navGroupRef.value.classList.add('show') + if (props.visible) { + navGroupRef.value.classList.add('show') + } + emit('visible-change', visible.value) }) @@ -53,7 +63,8 @@ const CNavGroup = defineComponent({ emit('visible-change', visible.value) }) - const handleTogglerClick = () => { + const handleTogglerClick = (event: Event) => { + event.preventDefault() visible.value = !visible.value emit('visible-change', visible.value) } @@ -93,7 +104,7 @@ const CNavGroup = defineComponent({ return () => h( - 'li', + props.as, { class: 'nav-group', ref: navGroupRef, @@ -104,6 +115,7 @@ const CNavGroup = defineComponent({ 'a', { class: ['nav-link', 'nav-group-toggle'], + href: '#', onClick: handleTogglerClick, }, slots.togglerContent && slots.togglerContent(), @@ -123,7 +135,7 @@ const CNavGroup = defineComponent({ default: () => visible.value && h( - 'ul', + props.as === 'div' ? 'div' : 'ul', { class: [ 'nav-group-items', diff --git a/packages/coreui-vue/src/components/nav/CNavItem.ts b/packages/coreui-vue/src/components/nav/CNavItem.ts index 19462bda..e660c01a 100644 --- a/packages/coreui-vue/src/components/nav/CNavItem.ts +++ b/packages/coreui-vue/src/components/nav/CNavItem.ts @@ -1,25 +1,54 @@ import { defineComponent, h } from 'vue' - import { CNavLink } from './CNavLink' +import type { ComponentProps } from '../../utils/ComponentProps' + +interface CNavItemProps extends ComponentProps { + as: string + class: string +} + const CNavItem = defineComponent({ name: 'CNavItem', + inheritAttrs: false, props: { - ...CNavLink.props, + /** + * Toggle the active state for the component. + */ + active: Boolean, + /** + * Component used for the root node. Either a string to use a HTML element or a component. + */ + as: { + type: String, + default: 'li', + }, + /** + * A string of all className you want applied to the component. + */ + class: String, + /** + * Toggle the disabled state for the component. + */ + disabled: Boolean, + /** + * @ignore + */ + href: String, }, - setup(props, { slots }) { + setup(props: CNavItemProps, { attrs, slots }) { return () => h( - 'li', + props.as, { - class: 'nav-item', + class: ['nav-item', props.class], }, props.href ? h( CNavLink, { + ...attrs, active: props.active, - component: props.component, disabled: props.disabled, href: props.href, }, diff --git a/packages/coreui-vue/src/components/nav/CNavLink.ts b/packages/coreui-vue/src/components/nav/CNavLink.ts index db5c183d..63c2af94 100644 --- a/packages/coreui-vue/src/components/nav/CNavLink.ts +++ b/packages/coreui-vue/src/components/nav/CNavLink.ts @@ -12,7 +12,7 @@ const CNavLink = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'a', }, @@ -30,9 +30,9 @@ const CNavLink = defineComponent({ h( CLink, { - class: 'nav-link', + as: props.as, active: props.active, - component: props.component, + class: 'nav-link', disabled: props.disabled, href: props.href, }, diff --git a/packages/coreui-vue/src/components/nav/CNavTitle.ts b/packages/coreui-vue/src/components/nav/CNavTitle.ts index c1a22227..6a808a42 100644 --- a/packages/coreui-vue/src/components/nav/CNavTitle.ts +++ b/packages/coreui-vue/src/components/nav/CNavTitle.ts @@ -2,8 +2,17 @@ import { defineComponent, h } from 'vue' const CNavTitle = defineComponent({ name: 'CNavTitle', - setup(_, { slots }) { - return () => h('li', { class: 'nav-title' }, slots.default && slots.default()) + props: { + /** + * Component used for the root node. Either a string to use a HTML element or a component. + */ + as: { + type: String, + default: 'li', + }, + }, + setup(props, { slots }) { + return () => h(props.as, { class: 'nav-title' }, slots.default && slots.default()) }, }) diff --git a/packages/coreui-vue/src/components/nav/__tests__/CNav.spec.ts b/packages/coreui-vue/src/components/nav/__tests__/CNav.spec.ts index 59ea597d..7b94d89b 100644 --- a/packages/coreui-vue/src/components/nav/__tests__/CNav.spec.ts +++ b/packages/coreui-vue/src/components/nav/__tests__/CNav.spec.ts @@ -12,7 +12,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: 'div', + as: 'div', layout: 'fill', variant: 'pills', }, diff --git a/packages/coreui-vue/src/components/nav/__tests__/CNavItem.spec.ts b/packages/coreui-vue/src/components/nav/__tests__/CNavItem.spec.ts index f7bee164..fc24c1c2 100644 --- a/packages/coreui-vue/src/components/nav/__tests__/CNavItem.spec.ts +++ b/packages/coreui-vue/src/components/nav/__tests__/CNavItem.spec.ts @@ -14,7 +14,7 @@ const customWrapper = mount(Component, { propsData: { href: '/bazinga', active: true, - component: 'div', + as: 'div', disabled: true, }, slots: { diff --git a/packages/coreui-vue/src/components/nav/__tests__/CNavLink.spec.ts b/packages/coreui-vue/src/components/nav/__tests__/CNavLink.spec.ts index 9b334d44..0220a67c 100644 --- a/packages/coreui-vue/src/components/nav/__tests__/CNavLink.spec.ts +++ b/packages/coreui-vue/src/components/nav/__tests__/CNavLink.spec.ts @@ -14,7 +14,7 @@ const customWrapper = mount(Component, { propsData: { href: '/bazinga', active: true, - component: 'div', + as: 'div', disabled: true, }, slots: { diff --git a/packages/coreui-vue/src/components/nav/index.ts b/packages/coreui-vue/src/components/nav/index.ts index d8796b1e..d38a6cfd 100644 --- a/packages/coreui-vue/src/components/nav/index.ts +++ b/packages/coreui-vue/src/components/nav/index.ts @@ -8,12 +8,12 @@ import { CNavTitle } from './CNavTitle' const CNavPlugin = { install: (app: App): void => { - app.component(CNav.name, CNav) - app.component(CNavGroup.name, CNavGroup) - app.component(CNavGroupItems.name, CNavGroupItems) - app.component(CNavItem.name, CNavItem) - app.component(CNavLink.name, CNavLink) - app.component(CNavTitle.name, CNavTitle) + app.component(CNav.name as string, CNav) + app.component(CNavGroup.name as string, CNavGroup) + app.component(CNavGroupItems.name as string, CNavGroupItems) + app.component(CNavItem.name as string, CNavItem) + app.component(CNavLink.name as string, CNavLink) + app.component(CNavTitle.name as string, CNavTitle) }, } diff --git a/packages/coreui-vue/src/components/navbar/CNavbar.ts b/packages/coreui-vue/src/components/navbar/CNavbar.ts index 37dceb40..ec6f4991 100644 --- a/packages/coreui-vue/src/components/navbar/CNavbar.ts +++ b/packages/coreui-vue/src/components/navbar/CNavbar.ts @@ -5,6 +5,13 @@ import { Color } from '../../props' const CNavbar = defineComponent({ name: 'CNavbar', props: { + /** + * Component used for the root node. Either a string to use a HTML element or a component. + */ + as: { + type: String, + default: 'nav', + }, /** * Sets the color context of the component to one of CoreUI’s themed colors. * @@ -22,13 +29,6 @@ const CNavbar = defineComponent({ return ['dark', 'light'].includes(value) }, }, - /** - * Component used for the root node. Either a string to use a HTML element or a component. - */ - component: { - type: String, - default: 'nav', - }, /** * Defines optional container wrapping children elements. * @@ -68,7 +68,7 @@ const CNavbar = defineComponent({ setup(props, { slots }) { return () => h( - props.component, + props.as, { class: [ 'navbar', diff --git a/packages/coreui-vue/src/components/navbar/CNavbarBrand.ts b/packages/coreui-vue/src/components/navbar/CNavbarBrand.ts index bd8e5211..25a50b93 100644 --- a/packages/coreui-vue/src/components/navbar/CNavbarBrand.ts +++ b/packages/coreui-vue/src/components/navbar/CNavbarBrand.ts @@ -7,7 +7,7 @@ const CNavbarBrand = defineComponent({ * Component used for the root node. Either a string to use a HTML element or a component. * */ - component: { + as: { type: String, default: 'a', }, @@ -19,7 +19,7 @@ const CNavbarBrand = defineComponent({ setup(props, { slots }) { return () => h( - props.component ?? (props.href ? 'a' : 'span'), + props.as ?? (props.href ? 'a' : 'span'), { class: 'navbar-brand', href: props.href, diff --git a/packages/coreui-vue/src/components/navbar/CNavbarNav.ts b/packages/coreui-vue/src/components/navbar/CNavbarNav.ts index 7a36931f..338882e1 100644 --- a/packages/coreui-vue/src/components/navbar/CNavbarNav.ts +++ b/packages/coreui-vue/src/components/navbar/CNavbarNav.ts @@ -6,7 +6,7 @@ const CNavbarNav = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'ul', }, @@ -14,7 +14,7 @@ const CNavbarNav = defineComponent({ setup(props, { slots }) { return () => h( - props.component, + props.as, { class: 'navbar-nav', role: 'navigation', diff --git a/packages/coreui-vue/src/components/navbar/__tests__/CNavbar.spec.ts b/packages/coreui-vue/src/components/navbar/__tests__/CNavbar.spec.ts index aa081f28..6c533fb4 100644 --- a/packages/coreui-vue/src/components/navbar/__tests__/CNavbar.spec.ts +++ b/packages/coreui-vue/src/components/navbar/__tests__/CNavbar.spec.ts @@ -12,9 +12,9 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { + as: 'div', color: 'warning', colorScheme: 'light', - component: 'div', container: 'lg', expand: 'xl', placement: 'fixed-bottom', diff --git a/packages/coreui-vue/src/components/navbar/__tests__/CNavbarBrand.spec.ts b/packages/coreui-vue/src/components/navbar/__tests__/CNavbarBrand.spec.ts index f4b1c5d1..8f9b1b7e 100644 --- a/packages/coreui-vue/src/components/navbar/__tests__/CNavbarBrand.spec.ts +++ b/packages/coreui-vue/src/components/navbar/__tests__/CNavbarBrand.spec.ts @@ -12,7 +12,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: 'div', + as: 'div', href: '/bazinga', }, slots: { diff --git a/packages/coreui-vue/src/components/navbar/__tests__/CNavbarNav.spec.ts b/packages/coreui-vue/src/components/navbar/__tests__/CNavbarNav.spec.ts index 74db8a76..3c51ea8c 100644 --- a/packages/coreui-vue/src/components/navbar/__tests__/CNavbarNav.spec.ts +++ b/packages/coreui-vue/src/components/navbar/__tests__/CNavbarNav.spec.ts @@ -12,7 +12,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: 'div', + as: 'div', }, slots: { default: 'Default slot', diff --git a/packages/coreui-vue/src/components/navbar/index.ts b/packages/coreui-vue/src/components/navbar/index.ts index b857b0f9..c466c871 100644 --- a/packages/coreui-vue/src/components/navbar/index.ts +++ b/packages/coreui-vue/src/components/navbar/index.ts @@ -7,11 +7,11 @@ import { CNavbarToggler } from './CNavbarToggler' const CNavbarPlugin = { install: (app: App): void => { - app.component(CNavbar.name, CNavbar) - app.component(CNavbarBrand.name, CNavbarBrand) - app.component(CNavbarNav.name, CNavbarNav) - app.component(CNavbarText.name, CNavbarText) - app.component(CNavbarToggler.name, CNavbarToggler) + app.component(CNavbar.name as string, CNavbar) + app.component(CNavbarBrand.name as string, CNavbarBrand) + app.component(CNavbarNav.name as string, CNavbarNav) + app.component(CNavbarText.name as string, CNavbarText) + app.component(CNavbarToggler.name as string, CNavbarToggler) }, } diff --git a/packages/coreui-vue/src/components/offcanvas/COffcanvas.ts b/packages/coreui-vue/src/components/offcanvas/COffcanvas.ts index aeef53aa..5377fa72 100644 --- a/packages/coreui-vue/src/components/offcanvas/COffcanvas.ts +++ b/packages/coreui-vue/src/components/offcanvas/COffcanvas.ts @@ -7,6 +7,7 @@ import { executeAfterTransition } from '../../utils/transition' const COffcanvas = defineComponent({ name: 'COffcanvas', + inheritAttrs: false, props: { /** * Apply a backdrop on body while offcanvas is open. @@ -94,7 +95,7 @@ const COffcanvas = defineComponent({ */ 'show', ], - setup(props, { slots, emit }) { + setup(props, { attrs, emit, slots }) { const offcanvasRef = ref() const visible = ref(props.visible) @@ -175,6 +176,7 @@ const COffcanvas = defineComponent({ h( 'div', { + ...attrs, class: [ { [`offcanvas${ @@ -182,6 +184,7 @@ const COffcanvas = defineComponent({ }`]: props.responsive, [`offcanvas-${props.placement}`]: props.placement, }, + attrs.class, ], onKeydown: (event: KeyboardEvent) => handleKeyDown(event), ref: offcanvasRef, diff --git a/packages/coreui-vue/src/components/offcanvas/COffcanvasTitle.ts b/packages/coreui-vue/src/components/offcanvas/COffcanvasTitle.ts index 0ea9f496..6a1cc18e 100644 --- a/packages/coreui-vue/src/components/offcanvas/COffcanvasTitle.ts +++ b/packages/coreui-vue/src/components/offcanvas/COffcanvasTitle.ts @@ -6,13 +6,13 @@ const COffcanvasTitle = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'h5', }, }, setup(props, { slots }) { - return () => h(props.component, { class: 'offcanvas-title' }, slots.default && slots.default()) + return () => h(props.as, { class: 'offcanvas-title' }, slots.default && slots.default()) }, }) diff --git a/packages/coreui-vue/src/components/offcanvas/__tests__/COffcanvasTitle.spec.ts b/packages/coreui-vue/src/components/offcanvas/__tests__/COffcanvasTitle.spec.ts index c41d76eb..7e27792f 100644 --- a/packages/coreui-vue/src/components/offcanvas/__tests__/COffcanvasTitle.spec.ts +++ b/packages/coreui-vue/src/components/offcanvas/__tests__/COffcanvasTitle.spec.ts @@ -12,7 +12,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: 'div', + as: 'div', }, slots: { default: 'Default slot', diff --git a/packages/coreui-vue/src/components/offcanvas/index.ts b/packages/coreui-vue/src/components/offcanvas/index.ts index cad1d0d3..28572037 100644 --- a/packages/coreui-vue/src/components/offcanvas/index.ts +++ b/packages/coreui-vue/src/components/offcanvas/index.ts @@ -6,10 +6,10 @@ import { COffcanvasTitle } from './COffcanvasTitle' const COffcanvasPlugin = { install: (app: App): void => { - app.component(COffcanvas.name, COffcanvas) - app.component(COffcanvasBody.name, COffcanvasBody) - app.component(COffcanvasHeader.name, COffcanvasHeader) - app.component(COffcanvasTitle.name, COffcanvasTitle) + app.component(COffcanvas.name as string, COffcanvas) + app.component(COffcanvasBody.name as string, COffcanvasBody) + app.component(COffcanvasHeader.name as string, COffcanvasHeader) + app.component(COffcanvasTitle.name as string, COffcanvasTitle) }, } diff --git a/packages/coreui-vue/src/components/pagination/CPaginationItem.ts b/packages/coreui-vue/src/components/pagination/CPaginationItem.ts index 92475524..c4085a9a 100644 --- a/packages/coreui-vue/src/components/pagination/CPaginationItem.ts +++ b/packages/coreui-vue/src/components/pagination/CPaginationItem.ts @@ -12,7 +12,7 @@ const CPaginationItem = defineComponent({ /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: String, + as: String, /** * Toggle the disabled state for the component. */ @@ -24,7 +24,7 @@ const CPaginationItem = defineComponent({ }, setup(props, { slots }) { return () => { - const component = props.component ?? (props.active ? 'span' : 'a') + const component = props.as ?? (props.active ? 'span' : 'a') return h( 'li', { @@ -41,8 +41,8 @@ const CPaginationItem = defineComponent({ ? h( CLink, { + as: component, class: ['page-link'], - component: component, href: props.href, }, { diff --git a/packages/coreui-vue/src/components/pagination/index.ts b/packages/coreui-vue/src/components/pagination/index.ts index 307d0534..8c7d5e7c 100644 --- a/packages/coreui-vue/src/components/pagination/index.ts +++ b/packages/coreui-vue/src/components/pagination/index.ts @@ -4,8 +4,8 @@ import { CPaginationItem } from './CPaginationItem' const CPaginationPlugin = { install: (app: App): void => { - app.component(CPagination.name, CPagination) - app.component(CPaginationItem.name, CPaginationItem) + app.component(CPagination.name as string, CPagination) + app.component(CPaginationItem.name as string, CPaginationItem) }, } diff --git a/packages/coreui-vue/src/components/placeholder/CPlaceholder.ts b/packages/coreui-vue/src/components/placeholder/CPlaceholder.ts index 99b69a20..9217bf91 100644 --- a/packages/coreui-vue/src/components/placeholder/CPlaceholder.ts +++ b/packages/coreui-vue/src/components/placeholder/CPlaceholder.ts @@ -25,19 +25,19 @@ export const CPlaceholder = defineComponent({ return ['glow', 'wave'].includes(value) }, }, - /** - * Sets the color context of the component to one of CoreUI’s themed colors. - * - * @values 'primary', 'secondary', 'success', 'danger', 'warning', 'info', 'dark', 'light' - */ - color: Color, /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: { + as: { type: String, default: 'span', }, + /** + * Sets the color context of the component to one of CoreUI’s themed colors. + * + * @values 'primary', 'secondary', 'success', 'danger', 'warning', 'info', 'dark', 'light' + */ + color: Color, /** * Size the component extra small, small, or large. * @@ -93,7 +93,7 @@ export const CPlaceholder = defineComponent({ return () => h( - props.component, + props.as, { class: [ props.animation ? `placeholder-${props.animation}` : 'placeholder', diff --git a/packages/coreui-vue/src/components/placeholder/index.ts b/packages/coreui-vue/src/components/placeholder/index.ts index 39369fc1..87038824 100644 --- a/packages/coreui-vue/src/components/placeholder/index.ts +++ b/packages/coreui-vue/src/components/placeholder/index.ts @@ -3,7 +3,7 @@ import { CPlaceholder } from './CPlaceholder' const CPlaceholderPlugin = { install: (app: App): void => { - app.component(CPlaceholder.name, CPlaceholder) + app.component(CPlaceholder.name as string, CPlaceholder) }, } diff --git a/packages/coreui-vue/src/components/popover/CPopover.ts b/packages/coreui-vue/src/components/popover/CPopover.ts index 9c0aa6c1..300f876a 100644 --- a/packages/coreui-vue/src/components/popover/CPopover.ts +++ b/packages/coreui-vue/src/components/popover/CPopover.ts @@ -1,4 +1,4 @@ -import { defineComponent, h, PropType, ref, RendererElement, Transition } from 'vue' +import { defineComponent, h, PropType, ref, RendererElement, Transition, useId } from 'vue' import type { Placement } from '@popperjs/core' import { CConditionalTeleport } from '../conditional-teleport' @@ -23,7 +23,7 @@ const CPopover = defineComponent({ /** * Appends the vue popover to a specific element. You can pass an HTML element or function that returns a single element. By default `document.body`. * - * @since v5.0.0-rc.0 + * @since 5.0.0 */ container: { type: [Object, String] as PropType HTMLElement) | string>, @@ -118,7 +118,9 @@ const CPopover = defineComponent({ const togglerRef = ref() const popoverRef = ref() const visible = ref(props.visible) + const { initPopper, destroyPopper } = usePopper() + const uniqueId = `popover-${useId()}` const delay = typeof props.delay === 'number' ? { show: props.delay, hide: props.delay } : props.delay @@ -206,6 +208,7 @@ const CPopover = defineComponent({ }, attrs.class, ], + id: uniqueId, ref: popoverRef, role: 'tooltip', }, @@ -234,6 +237,7 @@ const CPopover = defineComponent({ ), slots.toggler && slots.toggler({ + id: visible.value ? uniqueId : null, on: { click: (event: Event) => props.trigger.includes('click') && toggleVisible(event, !visible.value), diff --git a/packages/coreui-vue/src/components/popover/index.ts b/packages/coreui-vue/src/components/popover/index.ts index 58eaee32..261dae00 100644 --- a/packages/coreui-vue/src/components/popover/index.ts +++ b/packages/coreui-vue/src/components/popover/index.ts @@ -3,7 +3,7 @@ import { CPopover } from './CPopover' const CPopoverPlugin = { install: (app: App): void => { - app.component(CPopover.name, CPopover) + app.component(CPopover.name as string, CPopover) }, } diff --git a/packages/coreui-vue/src/components/progress/CProgress.ts b/packages/coreui-vue/src/components/progress/CProgress.ts index 82613fd0..03c39ed3 100644 --- a/packages/coreui-vue/src/components/progress/CProgress.ts +++ b/packages/coreui-vue/src/components/progress/CProgress.ts @@ -23,7 +23,7 @@ const CProgress = defineComponent({ /** * A string of all className you want applied to the component. * - * @since 5.0.0-rc.0 + * @since 5.0.0 */ progressBarClassName: String, /** diff --git a/packages/coreui-vue/src/components/progress/index.ts b/packages/coreui-vue/src/components/progress/index.ts index 3d16c4f2..ae6fabfc 100644 --- a/packages/coreui-vue/src/components/progress/index.ts +++ b/packages/coreui-vue/src/components/progress/index.ts @@ -5,9 +5,9 @@ import { CProgressStacked } from './CProgressStacked' const CProgressPlugin = { install: (app: App): void => { - app.component(CProgress.name, CProgress) - app.component(CProgressBar.name, CProgressBar) - app.component(CProgressStacked.name, CProgressStacked) + app.component(CProgress.name as string, CProgress) + app.component(CProgressBar.name as string, CProgressBar) + app.component(CProgressStacked.name as string, CProgressStacked) }, } diff --git a/packages/coreui-vue/src/components/sidebar/CSidebarBrand.ts b/packages/coreui-vue/src/components/sidebar/CSidebarBrand.ts index b6327134..d58b5803 100644 --- a/packages/coreui-vue/src/components/sidebar/CSidebarBrand.ts +++ b/packages/coreui-vue/src/components/sidebar/CSidebarBrand.ts @@ -2,8 +2,27 @@ import { defineComponent, h } from 'vue' const CSidebarBrand = defineComponent({ name: 'CSidebarBrand', - setup(_, { slots }) { - return () => h('div', { class: 'sidebar-brand' }, slots.default && slots.default()) + props: { + /** + * Component used for the root node. Either a string to use a HTML element or a component. + * + */ + as: { + type: String, + default: 'div', + }, + /** + * The href attribute specifies the URL of the page the link goes to. + */ + href: String, + }, + setup(props, { slots }) { + return () => + h( + props.as ?? (props.href ? 'a' : 'div'), + { class: 'sidebar-brand', href: props.href }, + slots.default && slots.default(), + ) }, }) diff --git a/packages/coreui-vue/src/components/sidebar/CSidebarNav.ts b/packages/coreui-vue/src/components/sidebar/CSidebarNav.ts index 2b645744..daea06af 100644 --- a/packages/coreui-vue/src/components/sidebar/CSidebarNav.ts +++ b/packages/coreui-vue/src/components/sidebar/CSidebarNav.ts @@ -1,8 +1,17 @@ -import { defineComponent, h, ref } from 'vue' +import { Component, defineComponent, h, ref, PropType } from 'vue' const CSidebarNav = defineComponent({ name: 'CSidebarNav', - setup(_, { slots }) { + props: { + /** + * Component used for the root node. Either a string to use a HTML element or a component. + */ + as: { + type: [Object, String] as PropType, + default: 'ul', + }, + }, + setup(props, { slots }) { const visibleGroup = ref() const handleVisibleChange = (visible: boolean, index: number) => { @@ -19,21 +28,24 @@ const CSidebarNav = defineComponent({ return () => h( - 'ul', + props.as, { class: 'sidebar-nav', }, - slots.default && - slots.default().map((vnode, index) => { - // @ts-expect-error name is defined in component - if (vnode.type.name === 'CNavGroup') { - return h(vnode, { - onVisibleChange: (visible: boolean) => handleVisibleChange(visible, index + 1), - ...(visibleGroup.value && { visible: isVisible(index + 1) }), - }) - } - return vnode - }), + { + default: () => + slots.default && + slots.default().map((vnode, index) => { + // @ts-expect-error name is defined in component + if (vnode.type.name === 'CNavGroup') { + return h(vnode, { + onVisibleChange: (visible: boolean) => handleVisibleChange(visible, index + 1), + ...(visibleGroup.value && { visible: isVisible(index + 1) }), + }) + } + return vnode + }), + }, ) }, }) diff --git a/packages/coreui-vue/src/components/sidebar/index.ts b/packages/coreui-vue/src/components/sidebar/index.ts index ec0e1826..11dd0095 100644 --- a/packages/coreui-vue/src/components/sidebar/index.ts +++ b/packages/coreui-vue/src/components/sidebar/index.ts @@ -8,12 +8,12 @@ import { CSidebarToggler } from './CSidebarToggler' const CSidebarPlugin = { install: (app: App): void => { - app.component(CSidebar.name, CSidebar) - app.component(CSidebarBrand.name, CSidebarBrand) - app.component(CSidebarFooter.name, CSidebarFooter) - app.component(CSidebarHeader.name, CSidebarHeader) - app.component(CSidebarNav.name, CSidebarNav) - app.component(CSidebarToggler.name, CSidebarToggler) + app.component(CSidebar.name as string, CSidebar) + app.component(CSidebarBrand.name as string, CSidebarBrand) + app.component(CSidebarFooter.name as string, CSidebarFooter) + app.component(CSidebarHeader.name as string, CSidebarHeader) + app.component(CSidebarNav.name as string, CSidebarNav) + app.component(CSidebarToggler.name as string, CSidebarToggler) }, } diff --git a/packages/coreui-vue/src/components/spinner/CSpinner.ts b/packages/coreui-vue/src/components/spinner/CSpinner.ts index f773d422..1e4f0f26 100644 --- a/packages/coreui-vue/src/components/spinner/CSpinner.ts +++ b/packages/coreui-vue/src/components/spinner/CSpinner.ts @@ -3,6 +3,13 @@ import { defineComponent, h } from 'vue' const CSpinner = defineComponent({ name: 'CSpinner', props: { + /** + * Component used for the root node. Either a string to use a HTML element or a component. + */ + as: { + type: String, + default: 'div', + }, /** * Sets the color context of the component to one of CoreUI’s themed colors. * @@ -23,13 +30,6 @@ const CSpinner = defineComponent({ ].includes(value) }, }, - /** - * Component used for the root node. Either a string to use a HTML element or a component. - */ - component: { - type: String, - default: 'div', - }, /** * Size the component small. * @@ -64,7 +64,7 @@ const CSpinner = defineComponent({ setup(props) { return () => h( - props.component, + props.as, { class: [ `spinner-${props.variant}`, diff --git a/packages/coreui-vue/src/components/spinner/__tests__/CSpinner.spec.ts b/packages/coreui-vue/src/components/spinner/__tests__/CSpinner.spec.ts index 0f1ec0a9..f007df07 100644 --- a/packages/coreui-vue/src/components/spinner/__tests__/CSpinner.spec.ts +++ b/packages/coreui-vue/src/components/spinner/__tests__/CSpinner.spec.ts @@ -12,8 +12,8 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { + as: 'h4', color: 'warning', - component: 'h4', size: 'sm', variant: 'grow', visuallyHiddenLabel: 'visuallyHiddenLabel', diff --git a/packages/coreui-vue/src/components/spinner/index.ts b/packages/coreui-vue/src/components/spinner/index.ts index f1b76dda..8bcffb6d 100644 --- a/packages/coreui-vue/src/components/spinner/index.ts +++ b/packages/coreui-vue/src/components/spinner/index.ts @@ -3,7 +3,7 @@ import { CSpinner } from './CSpinner' const CSpinnerPlugin = { install: (app: App): void => { - app.component(CSpinner.name, CSpinner) + app.component(CSpinner.name as string, CSpinner) }, } diff --git a/packages/coreui-vue/src/components/table/index.ts b/packages/coreui-vue/src/components/table/index.ts index 9bdba307..96b44727 100644 --- a/packages/coreui-vue/src/components/table/index.ts +++ b/packages/coreui-vue/src/components/table/index.ts @@ -10,14 +10,14 @@ import { CTableRow } from './CTableRow' const CTablePlugin = { install: (app: App): void => { - app.component(CTable.name, CTable) - app.component(CTableBody.name, CTableBody) - app.component(CTableCaption.name, CTableCaption) - app.component(CTableDataCell.name, CTableDataCell) - app.component(CTableFoot.name, CTableFoot) - app.component(CTableHead.name, CTableHead) - app.component(CTableHeaderCell.name, CTableHeaderCell) - app.component(CTableRow.name, CTableRow) + app.component(CTable.name as string, CTable) + app.component(CTableBody.name as string, CTableBody) + app.component(CTableCaption.name as string, CTableCaption) + app.component(CTableDataCell.name as string, CTableDataCell) + app.component(CTableFoot.name as string, CTableFoot) + app.component(CTableHead.name as string, CTableHead) + app.component(CTableHeaderCell.name as string, CTableHeaderCell) + app.component(CTableRow.name as string, CTableRow) }, } diff --git a/packages/coreui-vue/src/components/tabs/CTab.ts b/packages/coreui-vue/src/components/tabs/CTab.ts new file mode 100644 index 00000000..5eb4c513 --- /dev/null +++ b/packages/coreui-vue/src/components/tabs/CTab.ts @@ -0,0 +1,52 @@ +import { defineComponent, h, inject, Ref } from 'vue' + +const CTab = defineComponent({ + name: 'CTab', + props: { + /** + * Toggle the disabled state for the component. + * + * @since 5.4.0 + */ + disabled: Boolean, + /** + * Item key. + */ + itemKey: { + type: [Number, String], + required: true, + }, + }, + setup(props, { slots }) { + const activeItemKey = inject('activeItemKey') as Ref + const id = inject('id') + const setActiveItemKey = inject('setActiveItemKey') as (key: number | string) => void + + const isActive = () => props.itemKey === activeItemKey.value + + return () => + h( + 'button', + { + class: [ + 'nav-link', + { + active: isActive(), + }, + ], + id: `${props.itemKey}-tab-${id}`, + role: 'tab', + tabindex: isActive() ? 0 : -1, + type: 'button', + 'aria-controls': `${props.itemKey}-tab-panel-${id}`, + 'aria-selected': isActive(), + disabled: props.disabled, + onClick: () => setActiveItemKey(props.itemKey), + onFocus: () => setActiveItemKey(props.itemKey), + }, + slots.default && slots.default(), + ) + }, +}) + +export { CTab } diff --git a/packages/coreui-vue/src/components/tabs/CTabList.ts b/packages/coreui-vue/src/components/tabs/CTabList.ts new file mode 100644 index 00000000..d73bb17d --- /dev/null +++ b/packages/coreui-vue/src/components/tabs/CTabList.ts @@ -0,0 +1,90 @@ +import { defineComponent, h, ref } from 'vue' +import { getNextActiveElement } from '../../utils' + +const CTabList = defineComponent({ + name: 'CTabList', + props: { + /** + * Specify a layout type for component. + * + * @values 'fill', 'justified' + */ + layout: { + type: String, + validator: (value: string) => { + return ['fill', 'justified'].includes(value) + }, + }, + /** + * Set the nav variant to tabs or pills. + * + * @values 'enclosed', 'enclosed-pills', 'pills', 'tabs', 'underline', 'underline-border' + */ + variant: { + type: String, + validator: (value: string) => { + return ['enclosed', 'enclosed-pills', 'pills', 'tabs', 'underline', 'underline-border'].includes(value) + }, + }, + }, + setup(props, { slots }) { + const tabListRef = ref() + + const handleKeydown = (event: KeyboardEvent) => { + if ( + tabListRef.value && + (event.key === 'ArrowDown' || + event.key === 'ArrowUp' || + event.key === 'ArrowLeft' || + event.key === 'ArrowRight' || + event.key === 'Home' || + event.key === 'End') + ) { + event.preventDefault() + const target = event.target as HTMLElement + // eslint-disable-next-line unicorn/prefer-spread + const items: HTMLElement[] = Array.from( + tabListRef.value.querySelectorAll('.nav-link:not(.disabled):not(:disabled)'), + ) + + let nextActiveElement + + if (event.key === 'Home' || event.key === 'End') { + nextActiveElement = event.key === 'End' ? items.at(-1) : items[0] + } else { + nextActiveElement = getNextActiveElement( + items, + target, + event.key === 'ArrowDown' || event.key === 'ArrowRight', + true, + ) + } + + if (nextActiveElement) { + nextActiveElement.focus({ preventScroll: true }) + } + } + } + + return () => + h( + 'div', + { + class: [ + 'nav', + props.variant === 'enclosed-pills' && 'nav-enclosed', + { + [`nav-${props.layout}`]: props.layout, + [`nav-${props.variant}`]: props.variant, + }, + ], + role: 'tablist', + onKeydown: (event) => handleKeydown(event), + ref: tabListRef, + }, + slots.default && slots.default(), + ) + }, +}) + +export { CTabList } diff --git a/packages/coreui-vue/src/components/tabs/CTabPane.ts b/packages/coreui-vue/src/components/tabs/CTabPane.ts index 9ff0050f..7614c346 100644 --- a/packages/coreui-vue/src/components/tabs/CTabPane.ts +++ b/packages/coreui-vue/src/components/tabs/CTabPane.ts @@ -5,6 +5,15 @@ import { executeAfterTransition } from '../../utils/transition' const CTabPane = defineComponent({ name: 'CTabPane', props: { + /** + * Enable fade in and fade out transition. + * + * @since 5.1.0 + */ + transition: { + type: Boolean, + default: true, + }, /** * Toggle the visibility of component. */ @@ -57,9 +66,9 @@ const CTabPane = defineComponent({ { class: [ 'tab-pane', - 'fade', { active: props.visible, + fade: props.transition, show: firstRender.value && props.visible, }, ], diff --git a/packages/coreui-vue/src/components/tabs/CTabPanel.ts b/packages/coreui-vue/src/components/tabs/CTabPanel.ts new file mode 100644 index 00000000..3182e74f --- /dev/null +++ b/packages/coreui-vue/src/components/tabs/CTabPanel.ts @@ -0,0 +1,128 @@ +import { + defineComponent, + h, + inject, + ref, + Ref, + RendererElement, + Transition, + vShow, + watch, + withDirectives, +} from 'vue' + +import { executeAfterTransition } from '../../utils/transition' + +const CTabPanel = defineComponent({ + name: 'CTabPanel', + props: { + /** + * Item key. + */ + itemKey: { + type: [Number, String], + required: true, + }, + /** + * Enable fade in and fade out transition. + */ + transition: { + type: Boolean, + default: true, + }, + /** + * Toggle the visibility of component. + */ + visible: { + type: Boolean, + default: false, + }, + }, + emits: [ + /** + * Callback fired when the component requests to be hidden. + */ + 'hide', + /** + * Callback fired when the component requests to be shown. + */ + 'show', + ], + setup(props, { slots, emit }) { + const activeItemKey = inject('activeItemKey') as Ref + const id = inject('id') + const tabPaneRef = ref() + const firstRender = ref(true) + const visible = ref() + + watch( + () => props.visible, + () => { + visible.value = props.visible + }, + { + immediate: true, + }, + ) + + watch( + activeItemKey, + () => { + visible.value = Boolean(activeItemKey.value === props.itemKey) + }, + { + immediate: true, + }, + ) + + const handleEnter = (el: RendererElement, done: () => void) => { + firstRender.value = false + emit('show') + setTimeout(() => { + executeAfterTransition(() => done(), el as HTMLElement) + el.classList.add('show') + }, 1) + } + + const handleLeave = (el: RendererElement, done: () => void) => { + firstRender.value = false + emit('hide') + el.classList.remove('show') + executeAfterTransition(() => done(), el as HTMLElement) + } + + return () => + h( + Transition, + { + onEnter: (el, done) => handleEnter(el, done), + onLeave: (el, done) => handleLeave(el, done), + }, + () => + withDirectives( + h( + 'div', + { + class: [ + 'tab-pane', + { + active: visible.value, + fade: props.transition, + show: firstRender.value && visible.value, + }, + ], + id: `${props.itemKey}-tab-panel-${id}`, + role: 'tabpanel', + 'aria-labelledby': `${props.itemKey}-tab-${id}`, + tabindex: 0, + ref: tabPaneRef, + }, + slots.default && slots.default(), + ), + [[vShow, visible.value]], + ), + ) + }, +}) + +export { CTabPanel } diff --git a/packages/coreui-vue/src/components/tabs/CTabs.ts b/packages/coreui-vue/src/components/tabs/CTabs.ts new file mode 100644 index 00000000..b61908c2 --- /dev/null +++ b/packages/coreui-vue/src/components/tabs/CTabs.ts @@ -0,0 +1,43 @@ +import { defineComponent, h, provide, ref, useId, watch } from 'vue' + +const CTabs = defineComponent({ + name: 'CTabs', + props: { + /** + * The active item key. + */ + activeItemKey: { + type: [Number, String], + required: true, + } + }, + emits: [ + /** + * The callback is fired when the active tab changes. + */ + 'change', + ], + setup(props, { slots, emit }) { + const activeItemKey = ref(props.activeItemKey) + const uniqueId = useId() + const setActiveItemKey = (key: string | number) => { + activeItemKey.value = key + } + + watch( + () => props.activeItemKey, + (value) => { + activeItemKey.value = value + emit('change', value) + }, + ) + + provide('activeItemKey', activeItemKey) + provide('id', uniqueId) + provide('setActiveItemKey', setActiveItemKey) + + return () => h('div', { class: 'tabs' }, slots.default && slots.default()) + }, +}) + +export { CTabs } diff --git a/packages/coreui-vue/src/components/tabs/index.ts b/packages/coreui-vue/src/components/tabs/index.ts index 47f3846a..7c848b03 100644 --- a/packages/coreui-vue/src/components/tabs/index.ts +++ b/packages/coreui-vue/src/components/tabs/index.ts @@ -1,12 +1,20 @@ import { App } from 'vue' +import { CTab } from './CTab' import { CTabContent } from './CTabContent' +import { CTabList } from './CTabList' import { CTabPane } from './CTabPane' +import { CTabPanel } from './CTabPanel' +import { CTabs } from './CTabs' const CTabsPlugin = { install: (app: App): void => { - app.component(CTabContent.name, CTabContent) - app.component(CTabPane.name, CTabPane) + app.component(CTab.name as string, CTab) + app.component(CTabContent.name as string, CTabContent) + app.component(CTabList.name as string, CTabList) + app.component(CTabPane.name as string, CTabPane) + app.component(CTabPanel.name as string, CTabPanel) + app.component(CTabs.name as string, CTabs) }, } -export { CTabsPlugin, CTabContent, CTabPane } +export { CTabsPlugin, CTab, CTabContent, CTabList, CTabPane, CTabPanel, CTabs } diff --git a/packages/coreui-vue/src/components/toast/CToastClose.ts b/packages/coreui-vue/src/components/toast/CToastClose.ts index f0aedd8d..a190e323 100644 --- a/packages/coreui-vue/src/components/toast/CToastClose.ts +++ b/packages/coreui-vue/src/components/toast/CToastClose.ts @@ -1,13 +1,19 @@ -import { defineComponent, h, inject } from 'vue' +import { defineComponent, h, inject, resolveComponent } from 'vue' import { CCloseButton } from '../close-button/CCloseButton' +import type { ComponentProps } from '../../utils/ComponentProps' + +interface CCloseButtonProps extends ComponentProps { + as?: string +} + const CToastClose = defineComponent({ name: 'CToastClose', props: { /** * Component used for the root node. Either a string to use a HTML element or a component. */ - component: String, + as: String, ...CCloseButton.props, }, emits: [ @@ -16,7 +22,7 @@ const CToastClose = defineComponent({ */ 'close', ], - setup(props, { slots, emit }) { + setup(props: CCloseButtonProps, { slots, emit }) { // eslint-disable-next-line no-unused-vars const updateVisible = inject('updateVisible') as (visible: boolean) => void const handleClose = () => { @@ -24,9 +30,9 @@ const CToastClose = defineComponent({ updateVisible(false) } return () => - props.component + props.as ? h( - props.component, + resolveComponent(props.as), { onClick: () => { handleClose() diff --git a/packages/coreui-vue/src/components/toast/CToastHeader.ts b/packages/coreui-vue/src/components/toast/CToastHeader.ts index 74fbc651..3f822da0 100644 --- a/packages/coreui-vue/src/components/toast/CToastHeader.ts +++ b/packages/coreui-vue/src/components/toast/CToastHeader.ts @@ -9,20 +9,11 @@ const CToastHeader = defineComponent({ */ closeButton: Boolean, }, - emits: [ - /** - * Event called after clicking the close button. - */ - 'close', - ], - setup(props, { slots, emit }) { + setup(props, { slots }) { return () => h('div', { class: 'toast-header' }, [ slots.default && slots.default(), - props.closeButton && - h(CToastClose, { - onClose: () => emit('close'), - }), + props.closeButton && h(CToastClose), ]) }, }) diff --git a/packages/coreui-vue/src/components/toast/__tests__/CToastClose.spec.ts b/packages/coreui-vue/src/components/toast/__tests__/CToastClose.spec.ts index eee1ba77..b12e21a1 100644 --- a/packages/coreui-vue/src/components/toast/__tests__/CToastClose.spec.ts +++ b/packages/coreui-vue/src/components/toast/__tests__/CToastClose.spec.ts @@ -21,7 +21,7 @@ const defaultWrapper = mount(Component, { const customWrapper = mount(Component, { propsData: { - component: CButton, + as: CButton, }, slots: { default: 'Default slot', diff --git a/packages/coreui-vue/src/components/toast/index.ts b/packages/coreui-vue/src/components/toast/index.ts index 4e2ac38d..bf297589 100644 --- a/packages/coreui-vue/src/components/toast/index.ts +++ b/packages/coreui-vue/src/components/toast/index.ts @@ -7,11 +7,11 @@ import { CToastHeader } from './CToastHeader' const CToastPlugin = { install: (app: App): void => { - app.component(CToast.name, CToast) - app.component(CToastBody.name, CToastBody) - app.component(CToastClose.name, CToastClose) - app.component(CToaster.name, CToaster) - app.component(CToastHeader.name, CToastHeader) + app.component(CToast.name as string, CToast) + app.component(CToastBody.name as string, CToastBody) + app.component(CToastClose.name as string, CToastClose) + app.component(CToaster.name as string, CToaster) + app.component(CToastHeader.name as string, CToastHeader) }, } diff --git a/packages/coreui-vue/src/components/tooltip/CTooltip.ts b/packages/coreui-vue/src/components/tooltip/CTooltip.ts index 7e55964d..6fe15d0e 100644 --- a/packages/coreui-vue/src/components/tooltip/CTooltip.ts +++ b/packages/coreui-vue/src/components/tooltip/CTooltip.ts @@ -1,4 +1,4 @@ -import { defineComponent, h, PropType, ref, RendererElement, Transition } from 'vue' +import { defineComponent, h, PropType, ref, RendererElement, Transition, useId } from 'vue' import type { Placement } from '@popperjs/core' import { CConditionalTeleport } from '../conditional-teleport' @@ -23,7 +23,7 @@ const CTooltip = defineComponent({ /** * Appends the vue tooltip to a specific element. You can pass an HTML element or function that returns a single element. By default `document.body`. * - * @since v5.0.0-rc.0 + * @since 5.0.0 */ container: { type: [Object, String] as PropType HTMLElement) | string>, @@ -114,7 +114,9 @@ const CTooltip = defineComponent({ const togglerRef = ref() const tooltipRef = ref() const visible = ref(props.visible) + const { initPopper, destroyPopper } = usePopper() + const uniqueId = `tooltip-${useId()}` const delay = typeof props.delay === 'number' ? { show: props.delay, hide: props.delay } : props.delay @@ -202,6 +204,7 @@ const CTooltip = defineComponent({ }, attrs.class, ], + id: uniqueId, ref: tooltipRef, role: 'tooltip', }, @@ -222,6 +225,7 @@ const CTooltip = defineComponent({ ), slots.toggler && slots.toggler({ + id: visible.value ? uniqueId : null, on: { click: (event: Event) => props.trigger.includes('click') && toggleVisible(event, !visible.value), diff --git a/packages/coreui-vue/src/components/tooltip/index.ts b/packages/coreui-vue/src/components/tooltip/index.ts index 88b3e802..cde4a9a3 100644 --- a/packages/coreui-vue/src/components/tooltip/index.ts +++ b/packages/coreui-vue/src/components/tooltip/index.ts @@ -3,7 +3,7 @@ import { CTooltip } from './CTooltip' const CTooltipPlugin = { install: (app: App): void => { - app.component(CTooltip.name, CTooltip) + app.component(CTooltip.name as string, CTooltip) }, } diff --git a/packages/coreui-vue/src/components/widgets/index.ts b/packages/coreui-vue/src/components/widgets/index.ts index 2e6cdb18..38a6f825 100644 --- a/packages/coreui-vue/src/components/widgets/index.ts +++ b/packages/coreui-vue/src/components/widgets/index.ts @@ -8,12 +8,12 @@ import { CWidgetStatsF } from './CWidgetStatsF' const CWidgetsStatsPlugin = { install: (app: App): void => { - app.component(CWidgetStatsA.name, CWidgetStatsA) - app.component(CWidgetStatsB.name, CWidgetStatsB) - app.component(CWidgetStatsC.name, CWidgetStatsC) - app.component(CWidgetStatsD.name, CWidgetStatsD) - app.component(CWidgetStatsE.name, CWidgetStatsE) - app.component(CWidgetStatsF.name, CWidgetStatsF) + app.component(CWidgetStatsA.name as string, CWidgetStatsA) + app.component(CWidgetStatsB.name as string, CWidgetStatsB) + app.component(CWidgetStatsC.name as string, CWidgetStatsC) + app.component(CWidgetStatsD.name as string, CWidgetStatsD) + app.component(CWidgetStatsE.name as string, CWidgetStatsE) + app.component(CWidgetStatsF.name as string, CWidgetStatsF) }, } diff --git a/packages/coreui-vue/src/composables/index.ts b/packages/coreui-vue/src/composables/index.ts index 4cee4c15..4d78d30e 100644 --- a/packages/coreui-vue/src/composables/index.ts +++ b/packages/coreui-vue/src/composables/index.ts @@ -1,4 +1,5 @@ import { useColorModes } from './useColorModes' import { usePopper } from './usePopper' +import { useUniqueId } from './useUniqueId' -export { useColorModes, usePopper } +export { useColorModes, usePopper, useUniqueId } diff --git a/packages/coreui-vue/src/composables/useUniqueId.ts b/packages/coreui-vue/src/composables/useUniqueId.ts new file mode 100644 index 00000000..e6f724a9 --- /dev/null +++ b/packages/coreui-vue/src/composables/useUniqueId.ts @@ -0,0 +1,18 @@ +import { ref } from 'vue' + +export const useUniqueId = (prefix: string = '') => { + const ids = ref([]) + + const getUID = () => { + do { + prefix += Math.floor(Math.random() * 1_000_000) + } while (ids.value.includes(prefix)) + + ids.value.push(prefix) + return prefix + } + + return { + getUID, + } +} diff --git a/packages/coreui-vue/src/directives/v-c-popover.ts b/packages/coreui-vue/src/directives/v-c-popover.ts index 49d6d0f8..5e846244 100644 --- a/packages/coreui-vue/src/directives/v-c-popover.ts +++ b/packages/coreui-vue/src/directives/v-c-popover.ts @@ -1,7 +1,8 @@ import { DirectiveBinding } from 'vue' import { createPopper } from '@popperjs/core' +import type { Options } from '@popperjs/core' -import { getUID } from '../utils' +import { useUniqueId } from '../composables' const createPopoverElement = (id: string, header: string, content: string): HTMLDivElement => { const popover = document.createElement('div') @@ -14,8 +15,13 @@ const createPopoverElement = (id: string, header: string, content: string): HTML return popover } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const addPopoverElement = (popover: HTMLDivElement, el: HTMLElement, popperOptions: any) => { +const addPopoverElement = ( + el: HTMLElement, + popover: HTMLDivElement, + popperOptions: Partial, + uID: string, +) => { + el.setAttribute('aria-describedby', uID) document.body.appendChild(popover) createPopper(el, popover, popperOptions) setTimeout(() => { @@ -23,27 +29,33 @@ const addPopoverElement = (popover: HTMLDivElement, el: HTMLElement, popperOptio }, 1) } -const removePopoverElement = (popover: HTMLDivElement) => { +const removePopoverElement = (el: HTMLElement, popover: HTMLDivElement) => { + el.removeAttribute('aria-describedby') popover.classList.remove('show') setTimeout(() => { popover.remove() }, 300) } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const togglePopoverElement = (popover: HTMLDivElement, el: HTMLElement, popperOptions: any) => { +const togglePopoverElement = ( + el: HTMLElement, + popover: HTMLDivElement, + popperOptions: Partial, + uID: string, +) => { const popperElement = document.getElementById(popover.id) if (popperElement && popperElement.classList.contains('show')) { - removePopoverElement(popover) + removePopoverElement(el, popover) return } - addPopoverElement(popover, el, popperOptions) + addPopoverElement(el, popover, popperOptions, uID) } export default { name: 'c-popover', uid: '', mounted(el: HTMLElement, binding: DirectiveBinding): void { + const { getUID } = useUniqueId('popover') const value = binding.value const content = typeof value === 'string' ? value : value.content ?? '' const header = value.header ?? '' @@ -65,30 +77,30 @@ export default { ], } - const popoverUID = getUID('popover') - binding.arg = popoverUID - const popover = createPopoverElement(popoverUID, header, content) + const uID = getUID() + binding.arg = uID + const popover = createPopoverElement(uID, header, content) trigger.includes('click') && el.addEventListener('click', () => { - togglePopoverElement(popover, el, popperOptions) + togglePopoverElement(el, popover, popperOptions, uID) }) if (trigger.includes('focus')) { el.addEventListener('focus', () => { - addPopoverElement(popover, el, popperOptions) + addPopoverElement(el, popover, popperOptions, uID) }) el.addEventListener('blur', () => { - removePopoverElement(popover) + removePopoverElement(el, popover) }) } if (trigger.includes('hover')) { el.addEventListener('mouseenter', () => { - addPopoverElement(popover, el, popperOptions) + addPopoverElement(el, popover, popperOptions, uID) }) el.addEventListener('mouseleave', () => { - removePopoverElement(popover) + removePopoverElement(el, popover) }) } }, diff --git a/packages/coreui-vue/src/directives/v-c-tooltip.ts b/packages/coreui-vue/src/directives/v-c-tooltip.ts index 20b0c09d..bf888042 100644 --- a/packages/coreui-vue/src/directives/v-c-tooltip.ts +++ b/packages/coreui-vue/src/directives/v-c-tooltip.ts @@ -1,7 +1,8 @@ import { DirectiveBinding } from 'vue' import { createPopper } from '@popperjs/core' +import type { Options } from '@popperjs/core' -import { getUID } from '../utils' +import { useUniqueId } from '../composables' const createTooltipElement = (id: string, content: string): HTMLDivElement => { const tooltip = document.createElement('div') @@ -13,8 +14,13 @@ const createTooltipElement = (id: string, content: string): HTMLDivElement => { return tooltip } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const addTooltipElement = (tooltip: HTMLDivElement, el: HTMLElement, popperOptions: any) => { +const addTooltipElement = ( + el: HTMLElement, + tooltip: HTMLDivElement, + popperOptions: Partial, + uID: string, +) => { + el.setAttribute('aria-describedby', uID) document.body.appendChild(tooltip) createPopper(el, tooltip, popperOptions) setTimeout(() => { @@ -22,26 +28,32 @@ const addTooltipElement = (tooltip: HTMLDivElement, el: HTMLElement, popperOptio }, 1) } -const removeTooltipElement = (tooltip: HTMLDivElement) => { +const removeTooltipElement = (el: HTMLElement, tooltip: HTMLDivElement) => { + el.removeAttribute('aria-describedby') tooltip.classList.remove('show') setTimeout(() => { tooltip.remove() }, 300) } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const toggleTooltipElement = (tooltip: HTMLDivElement, el: HTMLElement, popperOptions: any) => { +const toggleTooltipElement = ( + el: HTMLElement, + tooltip: HTMLDivElement, + popperOptions: Partial, + uID: string, +) => { const popperElement = document.getElementById(tooltip.id) if (popperElement && popperElement.classList.contains('show')) { - removeTooltipElement(tooltip) + removeTooltipElement(el, tooltip) return } - addTooltipElement(tooltip, el, popperOptions) + addTooltipElement(el, tooltip, popperOptions, uID) } export default { name: 'c-tooltip', mounted(el: HTMLElement, binding: DirectiveBinding): void { + const { getUID } = useUniqueId('tooltip') const value = binding.value const content = typeof value === 'string' ? value : value.content ?? '' const trigger = value.trigger ?? 'hover' @@ -62,30 +74,30 @@ export default { ], } - const tooltipUID = getUID('tooltip') - binding.arg = tooltipUID - const tooltip = createTooltipElement(tooltipUID, content) + const uID = getUID() + binding.arg = uID + const tooltip = createTooltipElement(uID, content) trigger.includes('click') && el.addEventListener('click', () => { - toggleTooltipElement(tooltip, el, popperOptions) + toggleTooltipElement(el, tooltip, popperOptions, uID) }) if (trigger.includes('focus')) { el.addEventListener('focus', () => { - addTooltipElement(tooltip, el, popperOptions) + addTooltipElement(el, tooltip, popperOptions, uID) }) el.addEventListener('blur', () => { - removeTooltipElement(tooltip) + removeTooltipElement(el, tooltip) }) } if (trigger.includes('hover')) { el.addEventListener('mouseenter', () => { - addTooltipElement(tooltip, el, popperOptions) + addTooltipElement(el, tooltip, popperOptions, uID) }) el.addEventListener('mouseleave', () => { - removeTooltipElement(tooltip) + removeTooltipElement(el, tooltip) }) } }, diff --git a/packages/coreui-vue/src/utils/ComponentProps.ts b/packages/coreui-vue/src/utils/ComponentProps.ts new file mode 100644 index 00000000..5bd3991c --- /dev/null +++ b/packages/coreui-vue/src/utils/ComponentProps.ts @@ -0,0 +1,6 @@ +import { DefineComponent, ExtractPropTypes, ExtractPublicPropTypes } from 'vue' + +export type ComponentProps = + T extends DefineComponent, any, any> + ? ExtractPublicPropTypes + : never diff --git a/packages/coreui-vue/src/utils/getNextActiveElement.ts b/packages/coreui-vue/src/utils/getNextActiveElement.ts new file mode 100644 index 00000000..a80293ca --- /dev/null +++ b/packages/coreui-vue/src/utils/getNextActiveElement.ts @@ -0,0 +1,23 @@ +const getNextActiveElement = ( + list: HTMLElement[], + activeElement: HTMLElement, + shouldGetNext: boolean, + isCycleAllowed: boolean, +) => { + const listLength = list.length + let index = list.indexOf(activeElement) + + if (index === -1) { + return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0] + } + + index += shouldGetNext ? 1 : -1 + + if (isCycleAllowed) { + index = (index + listLength) % listLength + } + + return list[Math.max(0, Math.min(index, listLength - 1))] +} + +export default getNextActiveElement diff --git a/packages/coreui-vue/src/utils/index.ts b/packages/coreui-vue/src/utils/index.ts index 2bd39724..b478e8c2 100644 --- a/packages/coreui-vue/src/utils/index.ts +++ b/packages/coreui-vue/src/utils/index.ts @@ -1,6 +1,7 @@ +import getNextActiveElement from './getNextActiveElement' import getRTLPlacement from './getRTLPlacement' import getUID from './getUID' import isInViewport from './isInViewport' import isRTL from './isRTL' -export { getRTLPlacement, getUID, isInViewport, isRTL } +export { getNextActiveElement, getRTLPlacement, getUID, isInViewport, isRTL } diff --git a/packages/docs/.prettierrc b/packages/docs/.prettierrc new file mode 100644 index 00000000..53e4559d --- /dev/null +++ b/packages/docs/.prettierrc @@ -0,0 +1,8 @@ +{ + "semi": false, + "trailingComma": "es5", + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2 + +} diff --git a/packages/docs/.vuepress/client.ts b/packages/docs/.vuepress/client.ts index b410c0a2..0404f0fd 100644 --- a/packages/docs/.vuepress/client.ts +++ b/packages/docs/.vuepress/client.ts @@ -1,5 +1,6 @@ import { defineClientConfig } from '@vuepress/client' -import { CIcon } from '@coreui/icons-vue' + +import { CIcon, CIconSvg } from '@coreui/icons-vue' import CChartPlugin from '@coreui/vue-chartjs' import CoreuiVue from '@coreui/vue/src/' import '@coreui/coreui/scss/coreui.scss' @@ -70,6 +71,7 @@ export default defineClientConfig({ app.use(CoreuiVue) app.provide('icons', icons) app.component('CIcon', CIcon) + app.component('CIconSvg', CIconSvg) app.use(CChartPlugin), router.addRoute({ path: '', redirect: '/getting-started/introduction.html' }) }, diff --git a/packages/docs/.vuepress/config.ts b/packages/docs/.vuepress/config.ts index 4c55aee9..ea00ecf0 100644 --- a/packages/docs/.vuepress/config.ts +++ b/packages/docs/.vuepress/config.ts @@ -1,12 +1,14 @@ import { defineUserConfig } from 'vuepress' -import anchor from 'markdown-it-anchor' -import include_plugin from 'markdown-it-include' -import { defaultTheme } from './theme-coreui' - -import { containerPlugin } from '@vuepress/plugin-container' +import { viteBundler } from '@vuepress/bundler-vite' +import { activeHeaderLinksPlugin } from '@vuepress/plugin-active-header-links' +import { markdownContainerPlugin } from '@vuepress/plugin-markdown-container' +import { prismjsPlugin } from '@vuepress/plugin-prismjs' import { registerComponentsPlugin } from '@vuepress/plugin-register-components' import { tocPlugin } from '@vuepress/plugin-toc' import { getDirname, path } from '@vuepress/utils' +import anchor from 'markdown-it-anchor' +import include_plugin from 'markdown-it-include' +import { defaultTheme } from './src/node' const __dirname = getDirname(import.meta.url) @@ -15,18 +17,14 @@ export default defineUserConfig({ lang: 'en-US', title: 'Vue UI Components · CoreUI', description: 'UI Components Library for Vue.js (Vue 3)', - head: [ - ['link', { rel: 'icon', href: `/vue/docs/favicons/favicon-96x96.png` }], - ], + head: [['link', { rel: 'icon', href: `/vue/docs/favicons/favicon-96x96.png` }]], + bundler: viteBundler(), markdown: { anchor: { - permalink: anchor.permalink.ariaHidden({ + permalink: anchor.permalink.linkInsideHeader({ class: 'anchor-link', placement: 'after' - }) - }, - code: { - lineNumbers: false, + }), }, }, extendsMarkdown: (md) => { @@ -36,53 +34,52 @@ export default defineUserConfig({ }) }, plugins: [ - containerPlugin({ + activeHeaderLinksPlugin({ + headerLinkSelector: 'a.sidebar-item', + headerAnchorSelector: '.header-anchor', + // should greater than page transition duration + delay: 300, + }), + // backToTopPlugin(), + markdownContainerPlugin({ type: 'demo', - render: function (tokens, idx) { - if (tokens[idx].nesting === 1) { - return '
\n' - } else { - return '
\n' - } - }, + before: (): string => `
\n`, + after: (): string => '
\n', }), - containerPlugin({ - type: 'demo-rounded', - render: function (tokens, idx) { - if (tokens[idx].nesting === 1) { - return '
\n' - } else { - return '
\n' - } - }, + markdownContainerPlugin({ + type: 'demo-bg-secondary', + before: (): string => + `
\n`, + after: (): string => '
\n', }), - containerPlugin({ + markdownContainerPlugin({ type: 'demo-dark', - render: function (tokens, idx) { - if (tokens[idx].nesting === 1) { - return '
\n' - } else { - return '
\n' - } - }, + before: (): string => `
\n`, + after: (): string => '
\n', }), - containerPlugin({ - type: 'demo-bg-secondary', - render: function (tokens, idx) { - if (tokens[idx].nesting === 1) { - return '
\n' - } else { - return '
\n' - } - }, + markdownContainerPlugin({ + type: 'demo-rounded', + before: (): string => `
\n`, + after: (): string => '
\n', }), - tocPlugin({}), + prismjsPlugin(), registerComponentsPlugin({ components: { - Callout: path.resolve(__dirname, './theme-coreui/src/client/components/Callout.vue'), - ScssDocs: path.resolve(__dirname, './theme-coreui/src/client/components/ScssDocs.vue'), + Callout: path.resolve(__dirname, './src/client/components/Callout.vue'), + ScssDocs: path.resolve(__dirname, './src/client/components/ScssDocs.vue'), }, }), + tocPlugin({}), + { + name: 'extendsPage', + extendsPage: (page) => { + const frontmatter = page.frontmatter + + frontmatter.head = [ + ['link', { rel: 'canonical', href: `https://coreui.io/vue/docs${page.path}` }], + ] + }, + }, ], theme: defaultTheme({ sidebar: [ @@ -323,6 +320,10 @@ export default defineUserConfig({ text: 'Table', link: `/components/table.html`, }, + { + text: 'Tabs', + link: `/components/tabs.html`, + }, { text: 'Toast', link: `/components/toast.html`, diff --git a/packages/docs/.vuepress/theme-coreui/src/assets/brand/coreui-vue.svg b/packages/docs/.vuepress/src/assets/brand/coreui-vue.svg similarity index 100% rename from packages/docs/.vuepress/theme-coreui/src/assets/brand/coreui-vue.svg rename to packages/docs/.vuepress/src/assets/brand/coreui-vue.svg diff --git a/packages/docs/.vuepress/theme-coreui/src/client/components/Ads.vue b/packages/docs/.vuepress/src/client/components/Ads.vue similarity index 100% rename from packages/docs/.vuepress/theme-coreui/src/client/components/Ads.vue rename to packages/docs/.vuepress/src/client/components/Ads.vue diff --git a/packages/docs/.vuepress/src/client/components/Banner.vue b/packages/docs/.vuepress/src/client/components/Banner.vue new file mode 100644 index 00000000..ba150ac6 --- /dev/null +++ b/packages/docs/.vuepress/src/client/components/Banner.vue @@ -0,0 +1,48 @@ + + + diff --git a/packages/docs/.vuepress/src/client/components/Callout.vue b/packages/docs/.vuepress/src/client/components/Callout.vue new file mode 100644 index 00000000..da3b93d8 --- /dev/null +++ b/packages/docs/.vuepress/src/client/components/Callout.vue @@ -0,0 +1,20 @@ + + + diff --git a/packages/docs/.vuepress/theme-coreui/src/client/components/Footer.vue b/packages/docs/.vuepress/src/client/components/Footer.vue similarity index 84% rename from packages/docs/.vuepress/theme-coreui/src/client/components/Footer.vue rename to packages/docs/.vuepress/src/client/components/Footer.vue index ca00885f..d38e5749 100644 --- a/packages/docs/.vuepress/theme-coreui/src/client/components/Footer.vue +++ b/packages/docs/.vuepress/src/client/components/Footer.vue @@ -35,18 +35,4 @@

- - - + \ No newline at end of file diff --git a/packages/docs/.vuepress/theme-coreui/src/client/components/Header.vue b/packages/docs/.vuepress/src/client/components/Header.vue similarity index 87% rename from packages/docs/.vuepress/theme-coreui/src/client/components/Header.vue rename to packages/docs/.vuepress/src/client/components/Header.vue index 183935cf..17ee1090 100644 --- a/packages/docs/.vuepress/theme-coreui/src/client/components/Header.vue +++ b/packages/docs/.vuepress/src/client/components/Header.vue @@ -1,3 +1,12 @@ + - diff --git a/packages/docs/.vuepress/src/client/components/OtherFrameworks.vue b/packages/docs/.vuepress/src/client/components/OtherFrameworks.vue new file mode 100644 index 00000000..b58ceed6 --- /dev/null +++ b/packages/docs/.vuepress/src/client/components/OtherFrameworks.vue @@ -0,0 +1,45 @@ + + + \ No newline at end of file diff --git a/packages/docs/.vuepress/src/client/components/ScssDocs.vue b/packages/docs/.vuepress/src/client/components/ScssDocs.vue new file mode 100644 index 00000000..61a93fee --- /dev/null +++ b/packages/docs/.vuepress/src/client/components/ScssDocs.vue @@ -0,0 +1,44 @@ + + + diff --git a/packages/docs/.vuepress/theme-coreui/src/client/components/Sidebar.vue b/packages/docs/.vuepress/src/client/components/Sidebar.vue similarity index 94% rename from packages/docs/.vuepress/theme-coreui/src/client/components/Sidebar.vue rename to packages/docs/.vuepress/src/client/components/Sidebar.vue index 6ad736e2..49581af1 100755 --- a/packages/docs/.vuepress/theme-coreui/src/client/components/Sidebar.vue +++ b/packages/docs/.vuepress/src/client/components/Sidebar.vue @@ -1,3 +1,7 @@ + + - - diff --git a/packages/docs/.vuepress/theme-coreui/src/client/components/SidebarNav.ts b/packages/docs/.vuepress/src/client/components/SidebarNav.ts similarity index 90% rename from packages/docs/.vuepress/theme-coreui/src/client/components/SidebarNav.ts rename to packages/docs/.vuepress/src/client/components/SidebarNav.ts index 09184a1e..ba1b4a81 100755 --- a/packages/docs/.vuepress/theme-coreui/src/client/components/SidebarNav.ts +++ b/packages/docs/.vuepress/src/client/components/SidebarNav.ts @@ -1,14 +1,15 @@ import { defineComponent, h, computed, onMounted, ref } from 'vue' -import type { VNode } from 'vue' -import { RouterLink, useRoute } from 'vue-router' -import type { RouteLocationNormalizedLoaded } from 'vue-router' -import type { ResolvedSidebarItem } from '../../shared' - +import { useRoute } from 'vuepress/client' +import { RouterLink} from 'vue-router' +import { useSidebarItems } from '../composables' import { withBase } from '@vuepress/client' - import { CBadge, CNavGroup, CNavItem, CSidebarNav } from '@coreui/vue/src/' import { CIcon } from '@coreui/icons-vue' +import type { VNode } from 'vue' +import type { RouteLocationNormalizedLoaded } from 'vue-router' +import type { ResolvedSidebarItem } from '../../shared' + const normalizePath = (path: string): string => decodeURI(path) .replace(/#.*$/, '') @@ -43,13 +44,8 @@ const isActiveItem = (route: RouteLocationNormalizedLoaded, item: ResolvedSideba const SidebarNav = defineComponent({ name: 'SidebarNav', - props: { - items: { - type: Array, - required: true, - }, - }, - setup(props) { + setup() { + const sidebarItems = useSidebarItems() const route = useRoute() const firstRender = ref(true) @@ -57,7 +53,7 @@ const SidebarNav = defineComponent({ firstRender.value = false }) - const renderItem = (item: ResolvedSidebarItem): VNode => { + const renderItem = (item: any): VNode => { if (item.children && !item.link.includes('.html')) { const visible = computed(() => item.children.some((child) => isActiveItem(route, child))) @@ -122,7 +118,7 @@ const SidebarNav = defineComponent({ CSidebarNav, {}, { - default: () => props.items.map((item: any) => renderItem(item)), + default: () => sidebarItems.value.map((item: any) => renderItem(item)), } ) }, diff --git a/packages/docs/.vuepress/src/client/components/Toc.vue b/packages/docs/.vuepress/src/client/components/Toc.vue new file mode 100644 index 00000000..074ca917 --- /dev/null +++ b/packages/docs/.vuepress/src/client/components/Toc.vue @@ -0,0 +1,37 @@ + + + diff --git a/packages/docs/.vuepress/theme-coreui/src/client/components/other_frameworks.json b/packages/docs/.vuepress/src/client/components/other_frameworks.json similarity index 66% rename from packages/docs/.vuepress/theme-coreui/src/client/components/other_frameworks.json rename to packages/docs/.vuepress/src/client/components/other_frameworks.json index 32046bbd..b9297930 100644 --- a/packages/docs/.vuepress/theme-coreui/src/client/components/other_frameworks.json +++ b/packages/docs/.vuepress/src/client/components/other_frameworks.json @@ -1,240 +1,240 @@ { "accordion": { - "angular": "https://coreui.io/angular/docs/components/accordion", - "bootstrap": "https://coreui.io/docs/components/accordion/", + "angular": "https://coreui.io/angular/docs/components/accordion/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/accordion/", "react": "https://coreui.io/react/docs/components/accordion/", "vue": "https://coreui.io/vue/docs/components/accordion.html" }, "alert": { - "angular": "https://coreui.io/angular/docs/components/alert", - "bootstrap": "https://coreui.io/docs/components/alerts/", + "angular": "https://coreui.io/angular/docs/components/alert/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/alerts/", "react": "https://coreui.io/react/docs/components/alert/", "vue": "https://coreui.io/vue/docs/components/alert.html" }, "avatar": { - "angular": "https://coreui.io/angular/docs/components/avatar", - "bootstrap": "https://coreui.io/docs/components/avatar/", + "angular": "https://coreui.io/angular/docs/components/avatar/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/avatar/", "react": "https://coreui.io/react/docs/components/avatar/", "vue": "https://coreui.io/vue/docs/components/avatar.html" }, "badge": { - "angular": "https://coreui.io/angular/docs/components/badge", - "bootstrap": "https://coreui.io/docs/components/badge/", + "angular": "https://coreui.io/angular/docs/components/badge/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/badge/", "react": "https://coreui.io/react/docs/components/badge/", "vue": "https://coreui.io/vue/docs/components/badge.html" }, "breadcrumb": { - "angular": "https://coreui.io/angular/docs/components/breadcrumb", - "bootstrap": "https://coreui.io/docs/components/breadcrumb/", + "angular": "https://coreui.io/angular/docs/components/breadcrumb/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/breadcrumb/", "react": "https://coreui.io/react/docs/components/breadcrumb/", "vue": "https://coreui.io/vue/docs/components/breadcrumb.html" }, "button": { - "angular": "https://coreui.io/angular/docs/components/button", - "bootstrap": "https://coreui.io/docs/components/buttons/", + "angular": "https://coreui.io/angular/docs/components/button/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/buttons/", "react": "https://coreui.io/react/docs/components/button/", "vue": "https://coreui.io/vue/docs/components/button.html" }, "button-group": { - "angular": "https://coreui.io/angular/docs/components/button-group", - "bootstrap": "https://coreui.io/docs/components/button-group/", + "angular": "https://coreui.io/angular/docs/components/button-group/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/button-group/", "react": "https://coreui.io/react/docs/components/button-group/", "vue": "https://coreui.io/vue/docs/components/button-group.html" }, "callout": { - "angular": "https://coreui.io/angular/docs/components/callout", - "bootstrap": "https://coreui.io/docs/components/callout/", + "angular": "https://coreui.io/angular/docs/components/callout/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/callout/", "react": "https://coreui.io/react/docs/components/callout/", "vue": "https://coreui.io/vue/docs/components/callout.html" }, "card": { - "angular": "https://coreui.io/angular/docs/components/card", - "bootstrap": "https://coreui.io/docs/components/card/", + "angular": "https://coreui.io/angular/docs/components/card/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/card/", "react": "https://coreui.io/react/docs/components/card/", "vue": "https://coreui.io/vue/docs/components/card.html" }, "carousel": { - "angular": "https://coreui.io/angular/docs/components/carousel", - "bootstrap": "https://coreui.io/docs/components/carousel/", + "angular": "https://coreui.io/angular/docs/components/carousel/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/carousel/", "react": "https://coreui.io/react/docs/components/carousel/", "vue": "https://coreui.io/vue/docs/components/carousel.html" }, "checkbox": { - "angular": "https://coreui.io/angular/docs/forms/checks-radios", - "bootstrap": "https://coreui.io/docs/forms/checks-radios/", + "angular": "https://coreui.io/angular/docs/forms/checks-radios/", + "bootstrap": "https://coreui.io/bootstrap/docs/forms/checks-radios/", "react": "https://coreui.io/react/docs/forms/checkbox/", "vue": "https://coreui.io/vue/docs/forms/checkbox.html" }, "close-button": { - "angular": "https://coreui.io/angular/docs/components/close-button", - "bootstrap": "https://coreui.io/docs/components/close-button/", + "angular": "https://coreui.io/angular/docs/components/close-button/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/close-button/", "react": "https://coreui.io/react/docs/components/close-button/", "vue": "https://coreui.io/vue/docs/components/close-button.html" }, "collapse": { - "angular": "https://coreui.io/angular/docs/components/collapse", - "bootstrap": "https://coreui.io/docs/components/collapse/", + "angular": "https://coreui.io/angular/docs/components/collapse/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/collapse/", "react": "https://coreui.io/react/docs/components/collapse/", "vue": "https://coreui.io/vue/docs/components/collapse.html" }, "dropdown": { - "angular": "https://coreui.io/angular/docs/components/dropdown", - "bootstrap": "https://coreui.io/docs/components/dropdowns/", + "angular": "https://coreui.io/angular/docs/components/dropdown/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/dropdowns/", "react": "https://coreui.io/react/docs/components/dropdown/", "vue": "https://coreui.io/vue/docs/components/dropdown.html" }, "footer": { - "angular": "https://coreui.io/angular/docs/components/footer", - "bootstrap": "https://coreui.io/docs/components/footer/", + "angular": "https://coreui.io/angular/docs/components/footer/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/footer/", "react": "https://coreui.io/react/docs/components/footer/", "vue": "https://coreui.io/vue/docs/components/footer.html" }, "header": { - "angular": "https://coreui.io/angular/docs/components/header", - "bootstrap": "https://coreui.io/docs/components/header/", + "angular": "https://coreui.io/angular/docs/components/header/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/header/", "react": "https://coreui.io/react/docs/components/header/", "vue": "https://coreui.io/vue/docs/components/header.html" }, "icon": { - "angular": "https://coreui.io/angular/docs/components/icon", - "bootstrap": "https://coreui.io/docs/components/icon/", + "angular": "https://coreui.io/angular/docs/components/icon/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/icon/", "react": "https://coreui.io/react/docs/components/icon/", "vue": "https://coreui.io/vue/docs/components/icon.html" }, "image": { - "angular": "https://coreui.io/angular/docs/components/image", - "bootstrap": "https://coreui.io/docs/content/images/", + "angular": "https://coreui.io/angular/docs/components/image/", + "bootstrap": "https://coreui.io/bootstrap/docs/content/images/", "react": "https://coreui.io/react/docs/components/image/", "vue": "https://coreui.io/vue/docs/components/image.html" }, "input": { - "angular": "https://coreui.io/angular/docs/forms/input", - "bootstrap": "https://coreui.io/docs/forms/form-control/", + "angular": "https://coreui.io/angular/docs/forms/input/", + "bootstrap": "https://coreui.io/bootstrap/docs/forms/form-control/", "react": "https://coreui.io/react/docs/forms/input/", "vue": "https://coreui.io/vue/docs/forms/input.html" }, "input-group": { - "angular": "https://coreui.io/angular/docs/forms/input-group", - "bootstrap": "https://coreui.io/docs/forms/input-group/", + "angular": "https://coreui.io/angular/docs/forms/input-group/", + "bootstrap": "https://coreui.io/bootstrap/docs/forms/input-group/", "react": "https://coreui.io/react/docs/forms/input-group/", "vue": "https://coreui.io/vue/docs/forms/input-group.html" }, "floating-labels": { - "angular": "https://coreui.io/angular/docs/forms/floating-labels", - "bootstrap": "https://coreui.io/docs/forms/floating-labels/", + "angular": "https://coreui.io/angular/docs/forms/floating-labels/", + "bootstrap": "https://coreui.io/bootstrap/docs/forms/floating-labels/", "react": "https://coreui.io/react/docs/forms/floating-labels/", "vue": "https://coreui.io/vue/docs/forms/floating-labels.html" }, "list-group": { - "angular": "https://coreui.io/angular/docs/components/list-group", - "bootstrap": "https://coreui.io/docs/components/list-group/", + "angular": "https://coreui.io/angular/docs/components/list-group/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/list-group/", "react": "https://coreui.io/react/docs/components/list-group/", "vue": "https://coreui.io/vue/docs/components/list-group.html" }, "modal": { - "angular": "https://coreui.io/angular/docs/components/modal", - "bootstrap": "https://coreui.io/docs/components/modal/", + "angular": "https://coreui.io/angular/docs/components/modal/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/modal/", "react": "https://coreui.io/react/docs/components/modal/", "vue": "https://coreui.io/vue/docs/components/modal.html" }, "navbar": { - "bootstrap": "https://coreui.io/docs/components/navbar/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/navbar/", "react": "https://coreui.io/react/docs/components/navbar/", "vue": "https://coreui.io/vue/docs/components/navbar.html" }, "navs-tabs": { - "angular": "https://coreui.io/angular/docs/components/nav", - "bootstrap": "https://coreui.io/docs/components/navs-tabs/", + "angular": "https://coreui.io/angular/docs/components/nav/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/navs-tabs/", "react": "https://coreui.io/react/docs/components/navs-tabs/", "vue": "https://coreui.io/vue/docs/components/navs-tabs.html" }, "offcanvas": { - "angular": "https://coreui.io/angular/docs/components/offcanvas", - "bootstrap": "https://coreui.io/docs/components/offcanvas/", + "angular": "https://coreui.io/angular/docs/components/offcanvas/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/offcanvas/", "react": "https://coreui.io/react/docs/components/offcanvas/", "vue": "https://coreui.io/vue/docs/components/offcanvas.html" }, "pagination": { - "angular": "https://coreui.io/angular/docs/components/pagination", - "bootstrap": "https://coreui.io/docs/components/pagination/", + "angular": "https://coreui.io/angular/docs/components/pagination/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/pagination/", "react": "https://coreui.io/react/docs/components/pagination/", "vue": "https://coreui.io/vue/docs/components/pagination.html" }, "placeholder": { - "angular": "https://coreui.io/angular/docs/components/placeholder", - "bootstrap": "https://coreui.io/docs/components/placeholders/", + "angular": "https://coreui.io/angular/docs/components/placeholder/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/placeholders/", "react": "https://coreui.io/react/docs/components/placeholder/", "vue": "https://coreui.io/vue/docs/components/placeholder.html" }, "popover": { - "angular": "https://coreui.io/angular/docs/components/popover", - "bootstrap": "https://coreui.io/docs/components/popovers/", + "angular": "https://coreui.io/angular/docs/components/popover/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/popovers/", "react": "https://coreui.io/react/docs/components/popover/", "vue": "https://coreui.io/vue/docs/components/popover.html" }, "progress": { - "angular": "https://coreui.io/angular/docs/components/progress", - "bootstrap": "https://coreui.io/docs/components/progress/", + "angular": "https://coreui.io/angular/docs/components/progress/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/progress/", "react": "https://coreui.io/react/docs/components/progress/", "vue": "https://coreui.io/vue/docs/components/progress.html" }, "radio": { - "angular": "https://coreui.io/angular/docs/forms/checks-radios", - "bootstrap": "https://coreui.io/docs/forms/checks-radios/", + "angular": "https://coreui.io/angular/docs/forms/checks-radios/", + "bootstrap": "https://coreui.io/bootstrap/docs/forms/checks-radios/", "react": "https://coreui.io/react/docs/forms/radio/", "vue": "https://coreui.io/vue/docs/forms/radio.html" }, "range": { - "angular": "https://coreui.io/angular/docs/forms/range", - "bootstrap": "https://coreui.io/docs/forms/range/", + "angular": "https://coreui.io/angular/docs/forms/range/", + "bootstrap": "https://coreui.io/bootstrap/docs/forms/range/", "react": "https://coreui.io/react/docs/forms/range/", "vue": "https://coreui.io/vue/docs/forms/range.html" }, "select": { - "angular": "https://coreui.io/angular/docs/forms/select", - "bootstrap": "https://coreui.io/docs/forms/select/", + "angular": "https://coreui.io/angular/docs/forms/select/", + "bootstrap": "https://coreui.io/bootstrap/docs/forms/select/", "react": "https://coreui.io/react/docs/forms/select/", "vue": "https://coreui.io/vue/docs/forms/select.html" }, "sidebar": { - "angular": "https://coreui.io/angular/docs/components/sidebar", - "bootstrap": "https://coreui.io/docs/components/sidebar/", + "angular": "https://coreui.io/angular/docs/components/sidebar/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/sidebar/", "react": "https://coreui.io/react/docs/components/sidebar/", "vue": "https://coreui.io/vue/docs/components/sidebar.html" }, "spinner": { - "angular": "https://coreui.io/angular/docs/components/spinner", - "bootstrap": "https://coreui.io/docs/components/spinners/", + "angular": "https://coreui.io/angular/docs/components/spinner/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/spinners/", "react": "https://coreui.io/react/docs/components/spinner/", "vue": "https://coreui.io/vue/docs/components/spinner.html" }, "switch": { - "angular": "https://coreui.io/angular/docs/forms/checks-radios", - "bootstrap": "https://coreui.io/docs/forms/checks-radios/", + "angular": "https://coreui.io/angular/docs/forms/checks-radios/", + "bootstrap": "https://coreui.io/bootstrap/docs/forms/checks-radios/", "react": "https://coreui.io/react/docs/forms/switch/", "vue": "https://coreui.io/vue/docs/forms/switch.html" }, "table": { - "angular": "https://coreui.io/angular/docs/components/table", - "bootstrap": "https://coreui.io/docs/content/tables/", + "angular": "https://coreui.io/angular/docs/components/table/", + "bootstrap": "https://coreui.io/bootstrap/docs/content/tables/", "react": "https://coreui.io/react/docs/components/table/", "vue": "https://coreui.io/vue/docs/components/table.html" }, "textarea": { - "angular": "https://coreui.io/angular/docs/forms/form-control", - "bootstrap": "https://coreui.io/docs/forms/form-control/", + "angular": "https://coreui.io/angular/docs/forms/form-control/", + "bootstrap": "https://coreui.io/bootstrap/docs/forms/form-control/", "react": "https://coreui.io/react/docs/forms/textarea/", "vue": "https://coreui.io/vue/docs/forms/textarea.html" }, "toast": { - "angular": "https://coreui.io/angular/docs/components/toast", - "bootstrap": "https://coreui.io/docs/components/toasts/", + "angular": "https://coreui.io/angular/docs/components/toast/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/toasts/", "react": "https://coreui.io/react/docs/components/toast/", "vue": "https://coreui.io/vue/docs/components/toast.html" }, "tooltip": { - "angular": "https://coreui.io/angular/docs/components/tooltip", - "bootstrap": "https://coreui.io/docs/components/tooltips/", + "angular": "https://coreui.io/angular/docs/components/tooltip/", + "bootstrap": "https://coreui.io/bootstrap/docs/components/tooltips/", "react": "https://coreui.io/react/docs/components/tooltip/", "vue": "https://coreui.io/vue/docs/components/tooltip.html" } diff --git a/packages/docs/.vuepress/theme-coreui/src/client/composables/index.ts b/packages/docs/.vuepress/src/client/composables/index.ts similarity index 100% rename from packages/docs/.vuepress/theme-coreui/src/client/composables/index.ts rename to packages/docs/.vuepress/src/client/composables/index.ts diff --git a/packages/docs/.vuepress/theme-coreui/src/client/composables/useNavLink.ts b/packages/docs/.vuepress/src/client/composables/useNavLink.ts similarity index 100% rename from packages/docs/.vuepress/theme-coreui/src/client/composables/useNavLink.ts rename to packages/docs/.vuepress/src/client/composables/useNavLink.ts diff --git a/packages/docs/.vuepress/theme-coreui/src/client/composables/useResolveRouteWithRedirect.ts b/packages/docs/.vuepress/src/client/composables/useResolveRouteWithRedirect.ts similarity index 100% rename from packages/docs/.vuepress/theme-coreui/src/client/composables/useResolveRouteWithRedirect.ts rename to packages/docs/.vuepress/src/client/composables/useResolveRouteWithRedirect.ts diff --git a/packages/docs/.vuepress/theme-coreui/src/client/composables/useScrollPromise.ts b/packages/docs/.vuepress/src/client/composables/useScrollPromise.ts similarity index 100% rename from packages/docs/.vuepress/theme-coreui/src/client/composables/useScrollPromise.ts rename to packages/docs/.vuepress/src/client/composables/useScrollPromise.ts diff --git a/packages/docs/.vuepress/theme-coreui/src/client/composables/useSidebarItems.ts b/packages/docs/.vuepress/src/client/composables/useSidebarItems.ts similarity index 91% rename from packages/docs/.vuepress/theme-coreui/src/client/composables/useSidebarItems.ts rename to packages/docs/.vuepress/src/client/composables/useSidebarItems.ts index 0cf89962..cc9aa7b6 100755 --- a/packages/docs/.vuepress/theme-coreui/src/client/composables/useSidebarItems.ts +++ b/packages/docs/.vuepress/src/client/composables/useSidebarItems.ts @@ -1,7 +1,6 @@ import { usePageData, usePageFrontmatter } from '@vuepress/client' import type { PageHeader } from '@vuepress/client' import { - isArray, isPlainObject, isString, resolveLocalePath, @@ -70,7 +69,7 @@ export const resolveSidebarItems = ( return resolveAutoSidebarItems(sidebarDepth) } - if (isArray(sidebarConfig)) { + if (Array.isArray(sidebarConfig)) { return resolveArraySidebarItems(sidebarConfig, sidebarDepth) } @@ -144,19 +143,20 @@ export const resolveArraySidebarItems = ( } } + // TODO: check if we need this // if the sidebar item is current page and children is not set // use headers of current page as children - if (childItem.link === route.path) { - // skip h1 header - const headers = - page.value.headers[0]?.level === 1 - ? page.value.headers[0].children - : page.value.headers - return { - ...childItem, - children: headersToSidebarItemChildren(headers, sidebarDepth), - } - } + // if (childItem.link === route.path) { + // // skip h1 header + // const headers = + // page.value.headers[0]?.level === 1 + // ? page.value.headers[0].children + // : page.value.headers + // return { + // ...childItem, + // children: headersToSidebarItemChildren(headers, sidebarDepth), + // } + // } return childItem } diff --git a/packages/docs/.vuepress/theme-coreui/src/client/composables/useThemeData.ts b/packages/docs/.vuepress/src/client/composables/useThemeData.ts similarity index 100% rename from packages/docs/.vuepress/theme-coreui/src/client/composables/useThemeData.ts rename to packages/docs/.vuepress/src/client/composables/useThemeData.ts diff --git a/packages/docs/.vuepress/theme-coreui/src/client/config.ts b/packages/docs/.vuepress/src/client/config.ts similarity index 100% rename from packages/docs/.vuepress/theme-coreui/src/client/config.ts rename to packages/docs/.vuepress/src/client/config.ts diff --git a/packages/docs/.vuepress/theme-coreui/src/client/index.ts b/packages/docs/.vuepress/src/client/index.ts similarity index 100% rename from packages/docs/.vuepress/theme-coreui/src/client/index.ts rename to packages/docs/.vuepress/src/client/index.ts diff --git a/packages/docs/.vuepress/src/client/layouts/404.vue b/packages/docs/.vuepress/src/client/layouts/404.vue new file mode 100755 index 00000000..b8315905 --- /dev/null +++ b/packages/docs/.vuepress/src/client/layouts/404.vue @@ -0,0 +1,25 @@ + + + diff --git a/packages/docs/.vuepress/src/client/layouts/Layout.vue b/packages/docs/.vuepress/src/client/layouts/Layout.vue new file mode 100755 index 00000000..75d6da70 --- /dev/null +++ b/packages/docs/.vuepress/src/client/layouts/Layout.vue @@ -0,0 +1,82 @@ + + + diff --git a/packages/docs/.vuepress/theme-coreui/src/client/layouts/Redirect.vue b/packages/docs/.vuepress/src/client/layouts/Redirect.vue similarity index 100% rename from packages/docs/.vuepress/theme-coreui/src/client/layouts/Redirect.vue rename to packages/docs/.vuepress/src/client/layouts/Redirect.vue diff --git a/packages/docs/.vuepress/theme-coreui/src/client/shim.d.ts b/packages/docs/.vuepress/src/client/shim.d.ts similarity index 100% rename from packages/docs/.vuepress/theme-coreui/src/client/shim.d.ts rename to packages/docs/.vuepress/src/client/shim.d.ts diff --git a/packages/docs/.vuepress/theme-coreui/src/client/styles/_ads.scss b/packages/docs/.vuepress/src/client/styles/_ads.scss similarity index 100% rename from packages/docs/.vuepress/theme-coreui/src/client/styles/_ads.scss rename to packages/docs/.vuepress/src/client/styles/_ads.scss diff --git a/packages/docs/.vuepress/theme-coreui/src/client/styles/_anchor.scss b/packages/docs/.vuepress/src/client/styles/_anchor.scss similarity index 100% rename from packages/docs/.vuepress/theme-coreui/src/client/styles/_anchor.scss rename to packages/docs/.vuepress/src/client/styles/_anchor.scss diff --git a/packages/docs/.vuepress/theme-coreui/src/client/styles/_callouts.scss b/packages/docs/.vuepress/src/client/styles/_callouts.scss similarity index 100% rename from packages/docs/.vuepress/theme-coreui/src/client/styles/_callouts.scss rename to packages/docs/.vuepress/src/client/styles/_callouts.scss diff --git a/packages/docs/.vuepress/theme-coreui/src/client/styles/_component-examples.scss b/packages/docs/.vuepress/src/client/styles/_component-examples.scss similarity index 93% rename from packages/docs/.vuepress/theme-coreui/src/client/styles/_component-examples.scss rename to packages/docs/.vuepress/src/client/styles/_component-examples.scss index 10462b61..2e725c8f 100644 --- a/packages/docs/.vuepress/theme-coreui/src/client/styles/_component-examples.scss +++ b/packages/docs/.vuepress/src/client/styles/_component-examples.scss @@ -2,6 +2,23 @@ // Docs examples // +.docs-code-tabs { + padding: 0 ($cd-gutter-x * .5); + margin: 0 ($cd-gutter-x * -.5); + + @include media-breakpoint-up(md) { + padding: 0; + margin: 0; + } +} + +.docs-code-tab-content { + .tab-pane div[class^="language-"] { + border-top: 0 !important; + @include border-top-radius(0 !important); + } +} + .docs-example-snippet { border: solid var(--cui-border-color); border-width: 1px 0; @@ -73,6 +90,16 @@ margin-left: .5rem; } + // Avatars + > .avatar + .avatar { + margin-left: .25rem; + } + + // Badges + > .badge + .badge { + margin-left: .25rem; + } + // Buttons > .btn, > .btn-group { @@ -369,19 +396,20 @@ div[class^="language-"], .highlight { position: relative; padding: .75rem ($cd-gutter-x * .5); - margin-bottom: 1rem; + margin: 0 ($cd-gutter-x * -.5) 1rem ($cd-gutter-x * -.5) ; border: 1px solid var(--cui-border-color); background-color: var(--cd-pre-bg); @include media-breakpoint-up(md) { padding: .75rem 1.25rem; + margin-right: 0; + margin-left: 0; @include border-radius(var(--cui-border-radius)); } pre { padding: .25rem 0 .875rem; margin-top: .8125rem; - margin-right: 1.875rem; margin-bottom: 0; overflow: overlay; white-space: pre; diff --git a/packages/docs/.vuepress/theme-coreui/src/client/styles/_footer.scss b/packages/docs/.vuepress/src/client/styles/_footer.scss similarity index 100% rename from packages/docs/.vuepress/theme-coreui/src/client/styles/_footer.scss rename to packages/docs/.vuepress/src/client/styles/_footer.scss diff --git a/packages/docs/.vuepress/src/client/styles/_layout.scss b/packages/docs/.vuepress/src/client/styles/_layout.scss new file mode 100644 index 00000000..2ee63995 --- /dev/null +++ b/packages/docs/.vuepress/src/client/styles/_layout.scss @@ -0,0 +1,54 @@ +.wrapper { + width: 100%; + @include ltr-rtl("padding-left", var(--cui-sidebar-occupy-start, 0)); + will-change: auto; + @include transition(padding .15s); + + > .container-lg { + --cui-gutter-x: 3rem; + } +} + +.docs-sidebar { + grid-area: sidebar; +} + +.docs-main { + grid-area: main; + + @include media-breakpoint-down(lg) { + max-width: 760px; + margin-inline: auto; + } + + @include media-breakpoint-up(md) { + display: grid; + grid-template-areas: + "intro" + "toc" + "content"; + grid-template-rows: auto auto 1fr; + gap: $grid-gutter-width; + } + + @include media-breakpoint-up(lg) { + grid-template-areas: + "intro toc" + "content toc"; + grid-template-rows: auto 1fr; + grid-template-columns: 4fr 1fr; + } +} + +.docs-intro { + grid-area: intro; +} + +.docs-toc { + grid-area: toc; +} + +.docs-content { + grid-area: content; + min-width: 1px; // Fix width when bd-content contains a `
` https://github.com/twbs/bootstrap/issues/25410
+}
diff --git a/packages/docs/.vuepress/theme-coreui/src/client/styles/_prism.scss b/packages/docs/.vuepress/src/client/styles/_prism.scss
similarity index 100%
rename from packages/docs/.vuepress/theme-coreui/src/client/styles/_prism.scss
rename to packages/docs/.vuepress/src/client/styles/_prism.scss
diff --git a/packages/docs/.vuepress/theme-coreui/src/client/styles/_scrolling.scss b/packages/docs/.vuepress/src/client/styles/_scrolling.scss
similarity index 100%
rename from packages/docs/.vuepress/theme-coreui/src/client/styles/_scrolling.scss
rename to packages/docs/.vuepress/src/client/styles/_scrolling.scss
diff --git a/packages/docs/.vuepress/theme-coreui/src/client/styles/_search.scss b/packages/docs/.vuepress/src/client/styles/_search.scss
similarity index 100%
rename from packages/docs/.vuepress/theme-coreui/src/client/styles/_search.scss
rename to packages/docs/.vuepress/src/client/styles/_search.scss
diff --git a/packages/docs/.vuepress/theme-coreui/src/client/styles/_sidebar.scss b/packages/docs/.vuepress/src/client/styles/_sidebar.scss
similarity index 100%
rename from packages/docs/.vuepress/theme-coreui/src/client/styles/_sidebar.scss
rename to packages/docs/.vuepress/src/client/styles/_sidebar.scss
diff --git a/packages/docs/.vuepress/theme-coreui/src/client/styles/_table-api.scss b/packages/docs/.vuepress/src/client/styles/_table-api.scss
similarity index 100%
rename from packages/docs/.vuepress/theme-coreui/src/client/styles/_table-api.scss
rename to packages/docs/.vuepress/src/client/styles/_table-api.scss
diff --git a/packages/docs/.vuepress/src/client/styles/_toc.scss b/packages/docs/.vuepress/src/client/styles/_toc.scss
new file mode 100644
index 00000000..8f6dc04d
--- /dev/null
+++ b/packages/docs/.vuepress/src/client/styles/_toc.scss
@@ -0,0 +1,87 @@
+// stylelint-disable selector-max-type
+
+.docs-toc {
+  @include media-breakpoint-up(lg) {
+    position: sticky;
+    top: 5rem;
+    right: 0;
+    z-index: 2;
+    height: subtract(100vh, 7rem);
+    overflow-y: auto;
+  }
+
+  nav {
+    @include font-size(.875rem);
+
+    ul {
+      padding-left: 0;
+      list-style: none;
+
+      ul {
+        padding-left: 1rem;
+        margin-top: .25rem;
+      }
+    }
+
+    li {
+      margin-bottom: .25rem;
+    }
+
+    a {
+      color: inherit;
+
+      &:not(:hover) {
+        text-decoration: none;
+      }
+
+      code {
+        font: inherit;
+      }
+    }
+  }
+}
+
+.docs-toc-toggle {
+  display: flex;
+  align-items: center;
+
+  @include media-breakpoint-down(sm) {
+    justify-content: space-between;
+    width: 100%;
+  }
+
+  @include media-breakpoint-down(md) {
+    color: var(--cui-body-color);
+    border: 1px solid var(--cui-border-color);
+    @include border-radius(var(--cui-border-radius));
+
+    &:hover,
+    &:focus,
+    &:active,
+    &[aria-expanded="true"] {
+      color: var(--cui-primary);
+      background-color: var(--cui-body-bg);
+      border-color: var(--cui-primary);
+    }
+
+    &:focus,
+    &[aria-expanded="true"] {
+      box-shadow: 0 0 0 3px rgba(var(--cui-primary-rgb), .25);
+    }
+  }
+}
+
+.docs-toc-collapse {
+  @include media-breakpoint-down(md) {
+    nav {
+      padding: 1.25rem 1.25rem 1.25rem 1rem;
+      background-color: var(--cui-tertiary-bg);
+      border: 1px solid var(--cui-border-color);
+      @include border-radius(var(--cui-border-radius));
+    }
+  }
+
+  @include media-breakpoint-up(md) {
+    display: block !important; // stylelint-disable-line declaration-no-important
+  }
+}
diff --git a/packages/docs/.vuepress/theme-coreui/src/client/styles/_variables.scss b/packages/docs/.vuepress/src/client/styles/_variables.scss
similarity index 100%
rename from packages/docs/.vuepress/theme-coreui/src/client/styles/_variables.scss
rename to packages/docs/.vuepress/src/client/styles/_variables.scss
diff --git a/packages/docs/.vuepress/theme-coreui/src/client/styles/custom-container.scss b/packages/docs/.vuepress/src/client/styles/custom-container.scss
similarity index 100%
rename from packages/docs/.vuepress/theme-coreui/src/client/styles/custom-container.scss
rename to packages/docs/.vuepress/src/client/styles/custom-container.scss
diff --git a/packages/docs/.vuepress/theme-coreui/src/client/styles/index.scss b/packages/docs/.vuepress/src/client/styles/index.scss
similarity index 100%
rename from packages/docs/.vuepress/theme-coreui/src/client/styles/index.scss
rename to packages/docs/.vuepress/src/client/styles/index.scss
diff --git a/packages/docs/.vuepress/src/node/defaultTheme.ts b/packages/docs/.vuepress/src/node/defaultTheme.ts
new file mode 100755
index 00000000..0fe8ca5f
--- /dev/null
+++ b/packages/docs/.vuepress/src/node/defaultTheme.ts
@@ -0,0 +1,43 @@
+import type { Page, Theme } from '@vuepress/core'
+
+import { themeDataPlugin } from '@vuepress/plugin-theme-data'
+import { fs, getDirname, path } from '@vuepress/utils'
+import type {
+  DefaultThemeLocaleOptions,
+  DefaultThemePageData,
+  DefaultThemePluginsOptions,
+} from '../shared'
+import { assignDefaultLocaleOptions } from './utils'
+
+const __dirname = getDirname(import.meta.url)
+
+export interface DefaultThemeOptions extends DefaultThemeLocaleOptions {
+  /**
+   * To avoid confusion with the root `plugins` option,
+   * we use `themePlugins`
+   */
+  themePlugins?: DefaultThemePluginsOptions
+}
+export const defaultTheme = ({
+  themePlugins = {},
+  ...localeOptions
+}: DefaultThemeOptions = {}): Theme => {
+  assignDefaultLocaleOptions(localeOptions)
+
+  return {
+    name: '@vuepress/coreui-docs-theme',
+
+    templateBuild: path.resolve(__dirname, '../templates/build.html'),
+
+    clientConfigFile: path.resolve(__dirname, '../client/config.ts'),
+
+    extendsPage: (page: Page>) => {
+      // save relative file path into page data to generate edit link
+      page.data.filePathRelative = page.filePathRelative
+      // save title into route meta to generate navbar and sidebar
+      page.routeMeta.title = page.title
+    },
+
+    plugins: [themeDataPlugin({ themeData: localeOptions })],
+  }
+}
diff --git a/packages/docs/.vuepress/theme-coreui/src/node/index.ts b/packages/docs/.vuepress/src/node/index.ts
similarity index 100%
rename from packages/docs/.vuepress/theme-coreui/src/node/index.ts
rename to packages/docs/.vuepress/src/node/index.ts
diff --git a/packages/docs/.vuepress/theme-coreui/src/node/utils/assignDefaultLocaleOptions.ts b/packages/docs/.vuepress/src/node/utils/assignDefaultLocaleOptions.ts
similarity index 100%
rename from packages/docs/.vuepress/theme-coreui/src/node/utils/assignDefaultLocaleOptions.ts
rename to packages/docs/.vuepress/src/node/utils/assignDefaultLocaleOptions.ts
diff --git a/packages/docs/.vuepress/theme-coreui/src/node/utils/index.ts b/packages/docs/.vuepress/src/node/utils/index.ts
similarity index 100%
rename from packages/docs/.vuepress/theme-coreui/src/node/utils/index.ts
rename to packages/docs/.vuepress/src/node/utils/index.ts
diff --git a/packages/docs/.vuepress/theme-coreui/src/shared/index.ts b/packages/docs/.vuepress/src/shared/index.ts
similarity index 100%
rename from packages/docs/.vuepress/theme-coreui/src/shared/index.ts
rename to packages/docs/.vuepress/src/shared/index.ts
diff --git a/packages/docs/.vuepress/theme-coreui/src/shared/nav.ts b/packages/docs/.vuepress/src/shared/nav.ts
similarity index 100%
rename from packages/docs/.vuepress/theme-coreui/src/shared/nav.ts
rename to packages/docs/.vuepress/src/shared/nav.ts
diff --git a/packages/docs/.vuepress/theme-coreui/src/shared/options.ts b/packages/docs/.vuepress/src/shared/options.ts
similarity index 100%
rename from packages/docs/.vuepress/theme-coreui/src/shared/options.ts
rename to packages/docs/.vuepress/src/shared/options.ts
diff --git a/packages/docs/.vuepress/theme-coreui/src/shared/page.ts b/packages/docs/.vuepress/src/shared/page.ts
similarity index 52%
rename from packages/docs/.vuepress/theme-coreui/src/shared/page.ts
rename to packages/docs/.vuepress/src/shared/page.ts
index 9feed30c..40007b84 100755
--- a/packages/docs/.vuepress/theme-coreui/src/shared/page.ts
+++ b/packages/docs/.vuepress/src/shared/page.ts
@@ -9,30 +9,13 @@ export interface DefaultThemePageFrontmatter {
   home?: boolean
   navbar?: boolean
   pageClass?: string
+  name?: string
+  preview_component?: boolean
+  pro_component?: boolean
+  other_frameworks?: string
 }
 
-export interface DefaultThemeHomePageFrontmatter
-  extends DefaultThemePageFrontmatter {
-  home: true
-  heroImage?: string
-  heroAlt?: string
-  heroText?: string | null
-  tagline?: string | null
-  actions?: {
-    text: string
-    link: string
-    type?: 'primary' | 'secondary'
-  }[]
-  features?: {
-    title: string
-    details: string
-  }[]
-  footer?: string
-  footerHtml?: boolean
-}
-
-export interface DefaultThemeNormalPageFrontmatter
-  extends DefaultThemePageFrontmatter {
+export interface DefaultThemeNormalPageFrontmatter extends DefaultThemePageFrontmatter {
   home?: false
   editLink?: boolean
   lastUpdated?: boolean
@@ -41,6 +24,4 @@ export interface DefaultThemeNormalPageFrontmatter
   sidebarDepth?: number
   prev?: string | NavLink
   next?: string | NavLink
-  pro_component: boolean
-  other_frameworks?: string
 }
diff --git a/packages/docs/.vuepress/theme-coreui/templates/build.html b/packages/docs/.vuepress/src/templates/build.html
similarity index 100%
rename from packages/docs/.vuepress/theme-coreui/templates/build.html
rename to packages/docs/.vuepress/src/templates/build.html
diff --git a/packages/docs/.vuepress/theme-coreui/package.json b/packages/docs/.vuepress/theme-coreui/package.json
deleted file mode 100755
index 39655fd9..00000000
--- a/packages/docs/.vuepress/theme-coreui/package.json
+++ /dev/null
@@ -1,52 +0,0 @@
-{
-  "name": "@vuepress/theme-coreui",
-  "version": "1.0.0",
-  "description": "CoreUI for Vue.js docs theme",
-  "keywords": [
-    "vuepress-theme",
-    "vuepress",
-    "theme",
-    "default"
-  ],
-  "homepage": "https://github.com/coreui",
-  "bugs": {
-    "url": "https://github.com/coreui/coreui-vue/issues"
-  },
-  "repository": {
-    "type": "git",
-    "url": "git+https://github.com/coreui/coreui-vue.git"
-  },
-  "license": "MIT",
-  "author": "Lukasz Holeczek",
-  "main": "src/node/index.ts",
-  "files": [
-    "lib"
-  ],
-  "scripts": {
-    "build": "tsc -b tsconfig.build.json",
-    "clean": "rimraf lib *.tsbuildinfo",
-    "copy": "cpx \"src/**/*.{d.ts,vue,scss}\" lib"
-  },
-  "dependencies": {
-    "@vuepress/client": "2.0.0-beta.21",
-    "@vuepress/core": "2.0.0-beta.22",
-    "@vuepress/plugin-active-header-links": "2.0.0-beta.22",
-    "@vuepress/plugin-back-to-top": "2.0.0-beta.22",
-    "@vuepress/plugin-container": "2.0.0-beta.22",
-    "@vuepress/plugin-git": "2.0.0-beta.22",
-    "@vuepress/plugin-medium-zoom": "2.0.0-beta.22",
-    "@vuepress/plugin-nprogress": "2.0.0-beta.22",
-    "@vuepress/plugin-palette": "2.0.0-beta.22",
-    "@vuepress/plugin-prismjs": "2.0.0-beta.22",
-    "@vuepress/plugin-theme-data": "2.0.0-beta.22",
-    "@vuepress/shared": "2.0.0-beta.21",
-    "@vuepress/utils": "2.0.0-beta.21",
-    "sass": "^1.35.1",
-    "sass-loader": "^12.1.0",
-    "vue": "^3.1.4",
-    "vue-router": "^4.0.10"
-  },
-  "publishConfig": {
-    "access": "public"
-  }
-}
diff --git a/packages/docs/.vuepress/theme-coreui/src/client/components/Callout.vue b/packages/docs/.vuepress/theme-coreui/src/client/components/Callout.vue
deleted file mode 100644
index f3f3e8f1..00000000
--- a/packages/docs/.vuepress/theme-coreui/src/client/components/Callout.vue
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
diff --git a/packages/docs/.vuepress/theme-coreui/src/client/components/Page.vue b/packages/docs/.vuepress/theme-coreui/src/client/components/Page.vue
deleted file mode 100755
index 4ae63055..00000000
--- a/packages/docs/.vuepress/theme-coreui/src/client/components/Page.vue
+++ /dev/null
@@ -1,111 +0,0 @@
-
-
-
diff --git a/packages/docs/.vuepress/theme-coreui/src/client/components/ScssDocs.vue b/packages/docs/.vuepress/theme-coreui/src/client/components/ScssDocs.vue
deleted file mode 100644
index ae622b73..00000000
--- a/packages/docs/.vuepress/theme-coreui/src/client/components/ScssDocs.vue
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
diff --git a/packages/docs/.vuepress/theme-coreui/src/client/layouts/404.vue b/packages/docs/.vuepress/theme-coreui/src/client/layouts/404.vue
deleted file mode 100755
index 1066e78c..00000000
--- a/packages/docs/.vuepress/theme-coreui/src/client/layouts/404.vue
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
diff --git a/packages/docs/.vuepress/theme-coreui/src/client/layouts/Layout.vue b/packages/docs/.vuepress/theme-coreui/src/client/layouts/Layout.vue
deleted file mode 100755
index 24fdb74b..00000000
--- a/packages/docs/.vuepress/theme-coreui/src/client/layouts/Layout.vue
+++ /dev/null
@@ -1,103 +0,0 @@
-
-
-
diff --git a/packages/docs/.vuepress/theme-coreui/src/client/styles/_layout.scss b/packages/docs/.vuepress/theme-coreui/src/client/styles/_layout.scss
deleted file mode 100644
index 284eae98..00000000
--- a/packages/docs/.vuepress/theme-coreui/src/client/styles/_layout.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-.wrapper {
-  width: 100%;
-  @include ltr-rtl("padding-left", var(--cui-sidebar-occupy-start, 0));
-  will-change: auto;
-  @include transition(padding .15s);
-}
\ No newline at end of file
diff --git a/packages/docs/.vuepress/theme-coreui/src/client/styles/_toc.scss b/packages/docs/.vuepress/theme-coreui/src/client/styles/_toc.scss
deleted file mode 100644
index fc1f0fee..00000000
--- a/packages/docs/.vuepress/theme-coreui/src/client/styles/_toc.scss
+++ /dev/null
@@ -1,40 +0,0 @@
-.docs-toc {
-  @include media-breakpoint-up(lg) {
-    position: sticky;
-    top: 5rem;
-    right: 0;
-    z-index: 2;
-    height: subtract(100vh, 7rem);
-    overflow-y: auto;
-  }
-
-  nav {
-    @include font-size(.875rem);
-
-    ul {
-      padding-left: 0;
-      list-style: none;
-
-      ul {
-        padding-left: 1rem;
-        margin-top: .25rem;
-      }
-    }
-
-    li {
-      margin-bottom: .25rem;
-    }
-
-    a {
-      color: inherit;
-
-      &:not(:hover) {
-        text-decoration: none;
-      }
-
-      code {
-        font: inherit;
-      }
-    }
-  }
-}
\ No newline at end of file
diff --git a/packages/docs/.vuepress/theme-coreui/src/node/defaultTheme.ts b/packages/docs/.vuepress/theme-coreui/src/node/defaultTheme.ts
deleted file mode 100755
index 0886fd22..00000000
--- a/packages/docs/.vuepress/theme-coreui/src/node/defaultTheme.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import type { Page, Theme } from '@vuepress/core'
-import { activeHeaderLinksPlugin } from '@vuepress/plugin-active-header-links'
-import { backToTopPlugin } from '@vuepress/plugin-back-to-top'
-import { prismjsPlugin } from '@vuepress/plugin-prismjs'
-import { themeDataPlugin } from '@vuepress/plugin-theme-data'
-import { fs, getDirname, path } from '@vuepress/utils'
-import type { DefaultThemeLocaleOptions, DefaultThemePluginsOptions } from '../shared'
-import { assignDefaultLocaleOptions } from './utils'
-
-const __dirname = getDirname(import.meta.url)
-
-export interface DefaultThemeOptions extends DefaultThemeLocaleOptions {
-  /**
-   * To avoid confusion with the root `plugins` option,
-   * we use `themePlugins`
-   */
-  themePlugins?: DefaultThemePluginsOptions
-}
-export const defaultTheme = ({
-  themePlugins = {},
-  ...localeOptions
-}: DefaultThemeOptions = {}): Theme => {
-  assignDefaultLocaleOptions(localeOptions)
-
-  return {
-    name: '@vuepress/coreui-docs-theme',
-
-    templateBuild: path.resolve(__dirname, '../../templates/build.html'),
-
-    alias: {
-      // use alias to make all components replaceable
-      ...Object.fromEntries(
-        fs
-          .readdirSync(path.resolve(__dirname, '../client/components'))
-          .filter((file) => file.endsWith('.vue'))
-          .map((file) => [`@theme/${file}`, path.resolve(__dirname, '../client/components', file)]),
-      ),
-    },
-
-    clientConfigFile: path.resolve(__dirname, '../client/config.ts'),
-
-    extendsPage: (page: Page>) => {
-      // save relative file path into page data to generate edit link
-      page.data.filePathRelative = page.filePathRelative
-      // save title into route meta to generate navbar and sidebar
-      page.routeMeta.title = page.title
-    },
-
-    // layouts: path.resolve(__dirname, '../client/layouts'),
-
-    // clientAppEnhanceFiles: path.resolve(__dirname, '../client/clientAppEnhance.ts'),
-
-    // clientAppSetupFiles: path.resolve(__dirname, '../client/clientAppSetup.ts'),
-
-    // // use the relative file path to generate edit link
-    // extendsPageData: ({ filePathRelative }) => ({ filePathRelative }),
-
-    plugins: [
-      // @vuepress/plugin-active-header-link
-      themePlugins.activeHeaderLinks !== false
-        ? activeHeaderLinksPlugin({
-            headerLinkSelector: 'a.sidebar-item',
-            headerAnchorSelector: '.header-anchor',
-            // should greater than page transition duration
-            delay: 300,
-          })
-        : [],
-
-      // @vuepress/plugin-back-to-top
-      themePlugins.backToTop !== false ? backToTopPlugin() : [],
-
-      // @vuepress/plugin-prismjs
-      themePlugins.prismjs !== false ? prismjsPlugin() : [],
-
-      // @vuepress/plugin-theme-data
-      themeDataPlugin({ themeData: localeOptions }),
-      // [
-      //   '@vuepress/active-header-links',
-      //   {
-      //     headerLinkSelector: 'a.sidebar-item',
-      //     headerAnchorSelector: '.anchor-link',
-      //   },
-      // ],
-      // ['@vuepress/back-to-top', themePlugins.backToTop !== false],
-      // ['@vuepress/prismjs', themePlugins.prismjs !== false],
-      // ['@vuepress/theme-data', { themeData: localeOptions }],
-    ],
-  }
-}
diff --git a/packages/docs/README.md b/packages/docs/README.md
deleted file mode 100644
index 54e0c48e..00000000
--- a/packages/docs/README.md
+++ /dev/null
@@ -1,80 +0,0 @@
----
-layout: Redirect
-lang: en-US
-title: Title of this page
-description: Description of this page
----
-### Components:
-
-[ CAccordion ](./4.0/components/accordion.md)
-
-[ CAlert ](./4.0/components/alert.md)
-
-[ CAvatar ](./4.0/components/avatar.md)
-
-[ CBackdrop ](./4.0/components/backdrop.md)
-
-[ CBadge ](./4.0/components/badge.md)
-
-[ CBreadcrumb ](./4.0/components/breadcrumb.md)
-
-[ CButton ](./4.0/components/button.md)
-
-[ CButtonGroup ](./4.0/components/button-group.md)
-
-[ CCallout ](./4.0/components/callout.md)
-
-[ CCard ](./4.0/components/card.md)
-
-[ CCollapse ](./4.0/components/collapse.md)
-
-[ CDropdown ](./4.0/components/dropdown.md)
-
-[ CDropdown ](./4.0/components/dropdown.md)
-
-[ CFooter ](./4.0/components/footer.md)
-
-[ CForm ](./4.0/components/form.md)
-
-[ CGrid ](./4.0/components/grid.md)
-
-[ CHeader ](./4.0/components/header.md)
-
-[ CLink ](./4.0/components/link.md)
-
-[ CListGroup ](./4.0/components/list-group.md)
-
-[ CLoadingButton ](./4.0/components/loading-button.md)
-
-[ CModal ](./4.0/components/modal.md)
-
-[ CMultiSelect ](./4.0/components/multi-select.md)
-
-[ CNav ](./4.0/components/nav.md)
-
-[ CNavBar ](./4.0/components/navbar.md)
-
-[ COffcanvas ](./4.0/components/offcanvas.md)
-
-[ CPagination ](./4.0/components/pagination.md)
-
-[ CPopover ](./4.0/components/popover.md)
-
-[ CProgress ](./4.0/components/progress.md)
-
-[ CSidebar ](./4.0/components/sidebar.md)
-
-[ CSpinner ](./4.0/components/spinner.md)
-
-[ CTable ](./4.0/components/table.md)
-
-[ CTabs ](./4.0/components/tabs.md)
-
-[ CToast ](./4.0/components/toast.md)
-
-### Directives:
-
-
-[ CTooltip ](./4.0/directives/tooltip.md)
-
-[ CPopover ](./4.0/directives/popover.md)
\ No newline at end of file
diff --git a/packages/docs/api/accordion/CAccordionItem.api.md b/packages/docs/api/accordion/CAccordionItem.api.md
index 753acd83..3e07d5aa 100644
--- a/packages/docs/api/accordion/CAccordionItem.api.md
+++ b/packages/docs/api/accordion/CAccordionItem.api.md
@@ -8,6 +8,7 @@ import CAccordionItem from '@coreui/vue/src/components/accordion/CAccordionItem'
 
 #### Props
 
-| Prop name    | Description   | Type           | Values | Default |
-| ------------ | ------------- | -------------- | ------ | ------- |
-| **item-key** | The item key. | number\|string | -      | -       |
+| Prop name    | Description                                                                                   | Type           | Values | Default |
+| ------------ | --------------------------------------------------------------------------------------------- | -------------- | ------ | ------- |
+| **id**       | The id global attribute defines an identifier (ID) that must be unique in the whole document. | string         | -      | -       |
+| **item-key** | The item key.                                                                                 | number\|string | -      | -       |
diff --git a/packages/docs/api/alert/CAlertHeading.api.md b/packages/docs/api/alert/CAlertHeading.api.md
index 30262600..da1acd2d 100644
--- a/packages/docs/api/alert/CAlertHeading.api.md
+++ b/packages/docs/api/alert/CAlertHeading.api.md
@@ -8,6 +8,6 @@ import CAlertHeading from '@coreui/vue/src/components/alert/CAlertHeading'
 
 #### Props
 
-| Prop name     | Description                                                                             | Type   | Values | Default |
-| ------------- | --------------------------------------------------------------------------------------- | ------ | ------ | ------- |
-| **component** | Component used for the root node. Either a string to use a HTML element or a component. | string | -      | 'h4'    |
+| Prop name | Description                                                                             | Type   | Values | Default |
+| --------- | --------------------------------------------------------------------------------------- | ------ | ------ | ------- |
+| **as**    | Component used for the root node. Either a string to use a HTML element or a component. | string | -      | 'h4'    |
diff --git a/packages/docs/api/badge/CBadge.api.md b/packages/docs/api/badge/CBadge.api.md
index 0c953392..4d763c88 100644
--- a/packages/docs/api/badge/CBadge.api.md
+++ b/packages/docs/api/badge/CBadge.api.md
@@ -8,11 +8,12 @@ import CBadge from '@coreui/vue/src/components/badge/CBadge'
 
 #### Props
 
-| Prop name      | Description                                                                             | Type   | Values                                                                                                                                                                                                                                                                                                                                                                      | Default |
-| -------------- | --------------------------------------------------------------------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
-| **color**      | Sets the color context of the component to one of CoreUI’s themed colors.               | string | `'primary'`, `'secondary'`, `'success'`, `'danger'`, `'warning'`, `'info'`, `'dark'`, `'light'`                                                                                                                                                                                                                                                                             | -       |
-| **component**  | Component used for the root node. Either a string to use a HTML element or a component. | string | -                                                                                                                                                                                                                                                                                                                                                                           | 'span'  |
-| **position**   | Position badge in one of the corners of a link or button.                               | string | `'top-start'`, `'top-end'`, `'bottom-end'`, `'bottom-start'`                                                                                                                                                                                                                                                                                                                | -       |
-| **shape**      | Select the shape of the component.                                                      | string | `'rounded'`, `'rounded-top'`, `'rounded-end'`, `'rounded-bottom'`, `'rounded-start'`, `'rounded-circle'`, `'rounded-pill'`, `'rounded-0'`, `'rounded-1'`, `'rounded-2'`, `'rounded-3'`                                                                                                                                                                                      | -       |
-| **size**       | Size the component small.                                                               | string | `'sm'`                                                                                                                                                                                                                                                                                                                                                                      | -       |
-| **text-color** | Sets the text color of the component to one of CoreUI’s themed colors.                  | string | `'primary'`, `'secondary'`, `'success'`, `'danger'`, `'warning'`, `'info'`, `'dark'`, `'light'`, `'primary-emphasis'`, `'secondary-emphasis'`, `'success-emphasis'`, `'danger-emphasis'`, `'warning-emphasis'`, `'info-emphasis'`, `'light-emphasis'`, `'body'`, `'body-emphasis'`, `'body-secondary'`, `'body-tertiary'`, `'black'`, `'black-50'`, `'white'`, `'white-50'` | -       |
+| Prop name                                                        | Description                                                                                                                                                               | Type   | Values                                                                                                                                                                                                                                                                                                                                                                      | Default |
+| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
+| **as**                                                           | Component used for the root node. Either a string to use a HTML element or a component.                                                                                   | string | -                                                                                                                                                                                                                                                                                                                                                                           | 'span'  |
+| **color**                                                        | Sets the color context of the component to one of CoreUI’s themed colors.                                                                                                 | string | `'primary'`, `'secondary'`, `'success'`, `'danger'`, `'warning'`, `'info'`, `'dark'`, `'light'`                                                                                                                                                                                                                                                                             | -       |
+| **position**                                                     | Position badge in one of the corners of a link or button.                                                                                                                 | string | `'top-start'`, `'top-end'`, `'bottom-end'`, `'bottom-start'`                                                                                                                                                                                                                                                                                                                | -       |
+| **shape**                                                        | Select the shape of the component.                                                                                                                                        | string | `'rounded'`, `'rounded-top'`, `'rounded-end'`, `'rounded-bottom'`, `'rounded-start'`, `'rounded-circle'`, `'rounded-pill'`, `'rounded-0'`, `'rounded-1'`, `'rounded-2'`, `'rounded-3'`                                                                                                                                                                                      | -       |
+| **size**                                                         | Size the component small.                                                                                                                                                 | string | `'sm'`                                                                                                                                                                                                                                                                                                                                                                      | -       |
+| **text-bg-color** 
5.0.0+
| Sets the component's color scheme to one of CoreUI's themed colors, ensuring the text color contrast adheres to the WCAG 4.5:1 contrast ratio standard for accessibility. | string | `'primary'`, `'secondary'`, `'success'`, `'danger'`, `'warning'`, `'info'`, `'dark'`, `'light'` | - | +| **text-color** | Sets the text color of the component to one of CoreUI’s themed colors. | string | `'primary'`, `'secondary'`, `'success'`, `'danger'`, `'warning'`, `'info'`, `'dark'`, `'light'`, `'primary-emphasis'`, `'secondary-emphasis'`, `'success-emphasis'`, `'danger-emphasis'`, `'warning-emphasis'`, `'info-emphasis'`, `'light-emphasis'`, `'body'`, `'body-emphasis'`, `'body-secondary'`, `'body-tertiary'`, `'black'`, `'black-50'`, `'white'`, `'white-50'` | - | diff --git a/packages/docs/api/button/CButton.api.md b/packages/docs/api/button/CButton.api.md index 8f8f11ee..47adf340 100644 --- a/packages/docs/api/button/CButton.api.md +++ b/packages/docs/api/button/CButton.api.md @@ -8,17 +8,17 @@ import CButton from '@coreui/vue/src/components/button/CButton' #### Props -| Prop name | Description | Type | Values | Default | -| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -| **active** | Toggle the active state for the component. | boolean | - | - | -| **color** | Sets the color context of the component to one of CoreUI’s themed colors. | string | `'primary'`, `'secondary'`, `'success'`, `'danger'`, `'warning'`, `'info'`, `'dark'`, `'light'` | - | -| **component** | Component used for the root node. Either a string to use a HTML element or a component. | string | - | 'button' | -| **disabled** | Toggle the disabled state for the component. | boolean | - | - | -| **href** | The href attribute specifies the URL of the page the link goes to. | string | - | - | -| **shape** | Select the shape of the component. | string | `'rounded'`, `'rounded-top'`, `'rounded-end'`, `'rounded-bottom'`, `'rounded-start'`, `'rounded-circle'`, `'rounded-pill'`, `'rounded-0'`, `'rounded-1'`, `'rounded-2'`, `'rounded-3'` | - | -| **size** | Size the component small or large. | string | `'sm'`, `'lg'` | - | -| **type** | Specifies the type of button. Always specify the type attribute for the `