From 1f424df63c2bf0e050a94aa3003a7e188bf7c088 Mon Sep 17 00:00:00 2001 From: mrholek Date: Sat, 25 May 2024 11:59:25 +0200 Subject: [PATCH 1/7] chore: update dependencies and devDependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @coreui/coreui ^5.0.0 → ^5.0.1 @mdx-js/mdx ^2.3.0 → ^3.0.1 @mdx-js/react ^2.3.0 → ^3.0.1 @rollup/plugin-commonjs ^25.0.7 → ^25.0.8 @testing-library/jest-dom ^6.4.2 → ^6.4.5 @testing-library/react ^14.2.2 → ^14.3.1 @types/react 18.2.73 → 18.3.3 @types/react-dom ^18.2.22 → ^18.3.0 @typescript-eslint/eslint-plugin ^7.4.0 → ^7.10.0 @typescript-eslint/parser ^7.4.0 → ^7.10.0 eslint-plugin-react-hooks ^4.6.0 → ^4.6.2 gatsby ^5.13.3 → ^5.13.5 gatsby-plugin-offline ^6.13.1 → ^6.13.2 lerna ^8.1.2 → ^8.1.3 react ^18.2.0 → ^18.3.1 react-dom ^18.2.0 → ^18.3.1 react-imask ^7.5.0 → ^7.6.1 rimraf ^5.0.5 → ^5.0.7 rollup ^4.13.1 → ^4.18.0 sass ^1.72.0 → ^1.77.2 ts-jest ^29.1.2 → ^29.1.3 typescript ^5.4.3 → ^5.4.5 --- package.json | 8 ++++---- packages/coreui-react/package.json | 22 +++++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 00387841..109914f1 100644 --- a/package.json +++ b/package.json @@ -22,15 +22,15 @@ "test:update": "npm-run-all charts:test:update icons:test:update lib:test:update" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^7.4.0", - "@typescript-eslint/parser": "^7.4.0", + "@typescript-eslint/eslint-plugin": "^7.10.0", + "@typescript-eslint/parser": "^7.10.0", "eslint": "8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-react": "^7.34.1", - "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-unicorn": "^51.0.1", - "lerna": "^8.1.2", + "lerna": "^8.1.3", "npm-run-all": "^4.1.5", "prettier": "^3.2.5" }, diff --git a/packages/coreui-react/package.json b/packages/coreui-react/package.json index 90f90185..e66f90bf 100644 --- a/packages/coreui-react/package.json +++ b/packages/coreui-react/package.json @@ -41,31 +41,31 @@ "test:update": "jest --coverage --updateSnapshot" }, "dependencies": { - "@coreui/coreui": "^5.0.0", + "@coreui/coreui": "^5.0.1", "@popperjs/core": "^2.11.8", "prop-types": "^15.8.1" }, "devDependencies": { - "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-commonjs": "^25.0.8", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-typescript": "^11.1.6", - "@testing-library/jest-dom": "^6.4.2", - "@testing-library/react": "^14.2.2", + "@testing-library/jest-dom": "^6.4.5", + "@testing-library/react": "^14.3.1", "@types/jest": "^29.5.12", - "@types/react": "18.2.73", - "@types/react-dom": "^18.2.22", + "@types/react": "18.3.3", + "@types/react-dom": "^18.3.0", "@types/react-transition-group": "^4.4.10", "classnames": "^2.5.1", "cross-env": "^7.0.3", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", "react-transition-group": "^4.4.5", - "rollup": "^4.13.1", - "ts-jest": "^29.1.2", + "rollup": "^4.18.0", + "ts-jest": "^29.1.3", "tslib": "^2.6.2", - "typescript": "^5.4.3" + "typescript": "^5.4.5" }, "peerDependencies": { "react": ">=17", From 5633f3c25e56c289c9c025af05f7a33e8704d59c Mon Sep 17 00:00:00 2001 From: mrholek Date: Sat, 25 May 2024 12:02:03 +0200 Subject: [PATCH 2/7] feat(CTabPane): add the `transition` property to enable control of fade animation on panels --- .../coreui-react/src/components/tabs/CTabPane.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/coreui-react/src/components/tabs/CTabPane.tsx b/packages/coreui-react/src/components/tabs/CTabPane.tsx index 7d66be7f..9cc2e15b 100644 --- a/packages/coreui-react/src/components/tabs/CTabPane.tsx +++ b/packages/coreui-react/src/components/tabs/CTabPane.tsx @@ -18,6 +18,12 @@ export interface CTabPaneProps extends HTMLAttributes { * Callback fired when the component requests to be shown. */ onShow?: () => void + /** + * Enable fade in and fade out transition. + * + * @since 5.1.0 + */ + transition?: boolean /** * Toggle the visibility of component. */ @@ -25,7 +31,7 @@ export interface CTabPaneProps extends HTMLAttributes { } export const CTabPane = forwardRef( - ({ children, className, onHide, onShow, visible, ...rest }, ref) => { + ({ children, className, onHide, onShow, transition = true, visible, ...rest }, ref) => { const tabPaneRef = useRef() const forkedRef = useForkedRef(ref, tabPaneRef) @@ -35,9 +41,9 @@ export const CTabPane = forwardRef(
Date: Sat, 25 May 2024 19:34:06 +0200 Subject: [PATCH 3/7] feat(CTabs): the initial release of the new react tabs component --- .../src/components/dropdown/CDropdown.tsx | 4 +- .../src/components/dropdown/utils.ts | 22 -- .../coreui-react/src/components/tabs/CTab.tsx | 56 +++ .../src/components/tabs/CTabList.tsx | 92 +++++ .../src/components/tabs/CTabPanel.tsx | 98 +++++ .../src/components/tabs/CTabs.tsx | 54 +++ .../coreui-react/src/components/tabs/index.ts | 6 +- .../src/utils/getNextActiveElement.ts | 23 ++ packages/coreui-react/src/utils/index.ts | 2 + packages/docs/content/api/CTab.api.mdx | 11 + packages/docs/content/api/CTabList.api.mdx | 12 + packages/docs/content/api/CTabPane.api.mdx | 1 + packages/docs/content/api/CTabPanel.api.mdx | 15 + packages/docs/content/api/CTabs.api.mdx | 12 + .../docs/content/api/CTabsContext.api.mdx | 10 + packages/docs/content/components/tabs.mdx | 347 ++++++++++++++++++ packages/docs/src/nav.tsx | 8 + 17 files changed, 748 insertions(+), 25 deletions(-) create mode 100644 packages/coreui-react/src/components/tabs/CTab.tsx create mode 100644 packages/coreui-react/src/components/tabs/CTabList.tsx create mode 100644 packages/coreui-react/src/components/tabs/CTabPanel.tsx create mode 100644 packages/coreui-react/src/components/tabs/CTabs.tsx create mode 100644 packages/coreui-react/src/utils/getNextActiveElement.ts create mode 100644 packages/docs/content/api/CTab.api.mdx create mode 100644 packages/docs/content/api/CTabList.api.mdx create mode 100644 packages/docs/content/api/CTabPanel.api.mdx create mode 100644 packages/docs/content/api/CTabs.api.mdx create mode 100644 packages/docs/content/api/CTabsContext.api.mdx create mode 100644 packages/docs/content/components/tabs.mdx diff --git a/packages/coreui-react/src/components/dropdown/CDropdown.tsx b/packages/coreui-react/src/components/dropdown/CDropdown.tsx index 303a793a..65101efc 100644 --- a/packages/coreui-react/src/components/dropdown/CDropdown.tsx +++ b/packages/coreui-react/src/components/dropdown/CDropdown.tsx @@ -15,10 +15,10 @@ import { PolymorphicRefForwardingComponent } from '../../helpers' import { useForkedRef, usePopper } from '../../hooks' import { placementPropType } from '../../props' import type { Placements } from '../../types' -import { isRTL } from '../../utils' +import { getNextActiveElement, isRTL } from '../../utils' import type { Alignments, Directions } from './types' -import { getNextActiveElement, getPlacement } from './utils' +import { getPlacement } from './utils' export interface CDropdownProps extends HTMLAttributes { /** diff --git a/packages/coreui-react/src/components/dropdown/utils.ts b/packages/coreui-react/src/components/dropdown/utils.ts index edddb0db..c9659636 100644 --- a/packages/coreui-react/src/components/dropdown/utils.ts +++ b/packages/coreui-react/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-react/src/components/tabs/CTab.tsx b/packages/coreui-react/src/components/tabs/CTab.tsx new file mode 100644 index 00000000..a2485297 --- /dev/null +++ b/packages/coreui-react/src/components/tabs/CTab.tsx @@ -0,0 +1,56 @@ +import React, { forwardRef, HTMLAttributes, useContext } from 'react' +import PropTypes from 'prop-types' +import classNames from 'classnames' + +import { TabsContext } from './CTabs' + +export interface CTabProps extends HTMLAttributes { + /** + * A string of all className you want applied to the base component. + */ + className?: string + /** + * Item key. + */ + itemKey?: number | string +} + +export const CTab = forwardRef( + ({ children, className, itemKey, ...rest }, ref) => { + const { _activeItemKey, setActiveKey, id } = useContext(TabsContext) + + const isActive = () => itemKey === _activeItemKey + + return ( + + ) + }, +) + +CTab.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + itemKey: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), +} + +CTab.displayName = 'CTab' diff --git a/packages/coreui-react/src/components/tabs/CTabList.tsx b/packages/coreui-react/src/components/tabs/CTabList.tsx new file mode 100644 index 00000000..7f70938c --- /dev/null +++ b/packages/coreui-react/src/components/tabs/CTabList.tsx @@ -0,0 +1,92 @@ +import React, { forwardRef, HTMLAttributes, KeyboardEvent, useRef } from 'react' +import PropTypes from 'prop-types' +import classNames from 'classnames' + +import { useForkedRef } from '../../hooks' +import { getNextActiveElement } from '../../utils' + +export interface CTabListProps extends HTMLAttributes { + /** + * A string of all className you want applied to the base component. + */ + className?: string + /** + * Specify a layout type for component. + */ + layout?: 'fill' | 'justified' + /** + * Set the nav variant to tabs or pills. + */ + variant?: 'pills' | 'tabs' | 'underline' | 'underline-border' +} + +export const CTabList = forwardRef( + ({ children, className, layout, variant, ...rest }, ref) => { + const tabsRef = useRef(null) + const forkedRef = useForkedRef(ref, tabsRef) + + const handleKeydown = (event: KeyboardEvent) => { + if ( + tabsRef.current !== null && + (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( + tabsRef.current.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 ( +
+ {children} +
+ ) + }, +) + +CTabList.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + layout: PropTypes.oneOf(['fill', 'justified']), + variant: PropTypes.oneOf(['pills', 'tabs', 'underline', 'underline-border']), +} + +CTabList.displayName = 'CTabList' diff --git a/packages/coreui-react/src/components/tabs/CTabPanel.tsx b/packages/coreui-react/src/components/tabs/CTabPanel.tsx new file mode 100644 index 00000000..6a6d703e --- /dev/null +++ b/packages/coreui-react/src/components/tabs/CTabPanel.tsx @@ -0,0 +1,98 @@ +import React, { HTMLAttributes, forwardRef, useContext, useEffect, useRef, useState } from 'react' +import PropTypes from 'prop-types' +import classNames from 'classnames' +import { Transition } from 'react-transition-group' + +import { TabsContext } from './CTabs' +import { useForkedRef } from '../../hooks' +import { getTransitionDurationFromElement } from '../../utils' + +export interface CTabPanelProps extends HTMLAttributes { + /** + * A string of all className you want applied to the base component. + */ + className?: string + /** + * Item key. + */ + itemKey?: number | string + /** + * Callback fired when the component requests to be hidden. + */ + onHide?: () => void + /** + * Callback fired when the component requests to be shown. + */ + onShow?: () => void + /** + * Enable fade in and fade out transition. + */ + transition?: boolean + /** + * Toggle the visibility of component. + */ + visible?: boolean +} + +export const CTabPanel = forwardRef( + ({ children, className, itemKey, onHide, onShow, transition = true, visible, ...rest }, ref) => { + const { _activeItemKey, id } = useContext(TabsContext) + + const tabPaneRef = useRef() + const forkedRef = useForkedRef(ref, tabPaneRef) + + const [_visible, setVisible] = useState(visible || _activeItemKey === itemKey) + + useEffect(() => { + visible !== undefined && setVisible(visible) + }, [visible]) + + useEffect(() => { + setVisible(_activeItemKey === itemKey) + }, [_activeItemKey]) + + return ( + + {(state) => ( +
+ {children} +
+ )} +
+ ) + }, +) + +CTabPanel.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + itemKey: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + onHide: PropTypes.func, + onShow: PropTypes.func, + transition: PropTypes.bool, + visible: PropTypes.bool, +} + +CTabPanel.displayName = 'CTabPanel' diff --git a/packages/coreui-react/src/components/tabs/CTabs.tsx b/packages/coreui-react/src/components/tabs/CTabs.tsx new file mode 100644 index 00000000..8740e109 --- /dev/null +++ b/packages/coreui-react/src/components/tabs/CTabs.tsx @@ -0,0 +1,54 @@ +import React, { createContext, forwardRef, HTMLAttributes, useEffect, useId, useState } from 'react' +import PropTypes from 'prop-types' +import classNames from 'classnames' + +export interface CTabsProps extends Omit, 'onChange'> { + /** + * The active item key. + */ + activeItemKey?: number | string + /** + * A string of all className you want applied to the base component. + */ + className?: string + /** + * The callback is fired when the active tab changes. + */ + onChange?: (value: number | string) => void +} + +export interface TabsContextProps { + _activeItemKey?: number | string + setActiveKey: React.Dispatch> + id?: string +} + +export const TabsContext = createContext({} as TabsContextProps) + +export const CTabs = forwardRef( + ({ children, activeItemKey, className, onChange }, ref) => { + const id = useId() + const [_activeItemKey, setActiveKey] = useState(activeItemKey) + + useEffect(() => { + _activeItemKey && onChange && onChange(_activeItemKey) + }, [_activeItemKey]) + + return ( + +
+ {children} +
+
+ ) + }, +) + +CTabs.propTypes = { + activeItemKey: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + children: PropTypes.node, + className: PropTypes.string, + onChange: PropTypes.func, +} + +CTabs.displayName = 'CTabs' diff --git a/packages/coreui-react/src/components/tabs/index.ts b/packages/coreui-react/src/components/tabs/index.ts index 1962780c..0d2c6e06 100644 --- a/packages/coreui-react/src/components/tabs/index.ts +++ b/packages/coreui-react/src/components/tabs/index.ts @@ -1,4 +1,8 @@ +import { CTab } from './CTab' import { CTabContent } from './CTabContent' import { CTabPane } from './CTabPane' +import { CTabPanel } from './CTabPanel' +import { CTabList } from './CTabList' +import { CTabs } from './CTabs' -export { CTabContent, CTabPane } +export { CTab, CTabContent, CTabList, CTabPane, CTabPanel, CTabs } diff --git a/packages/coreui-react/src/utils/getNextActiveElement.ts b/packages/coreui-react/src/utils/getNextActiveElement.ts new file mode 100644 index 00000000..a80293ca --- /dev/null +++ b/packages/coreui-react/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-react/src/utils/index.ts b/packages/coreui-react/src/utils/index.ts index 10dbd154..bcdb558e 100644 --- a/packages/coreui-react/src/utils/index.ts +++ b/packages/coreui-react/src/utils/index.ts @@ -1,4 +1,5 @@ import executeAfterTransition from './executeAfterTransition' +import getNextActiveElement from './getNextActiveElement' import getRTLPlacement from './getRTLPlacement' import getTransitionDurationFromElement from './getTransitionDurationFromElement' import isInViewport from './isInViewport' @@ -6,6 +7,7 @@ import isRTL from './isRTL' export { executeAfterTransition, + getNextActiveElement, getRTLPlacement, getTransitionDurationFromElement, isInViewport, diff --git a/packages/docs/content/api/CTab.api.mdx b/packages/docs/content/api/CTab.api.mdx new file mode 100644 index 00000000..f0ba1fe6 --- /dev/null +++ b/packages/docs/content/api/CTab.api.mdx @@ -0,0 +1,11 @@ + +```jsx +import { CTab } from '@coreui/react' +// or +import CTab from '@coreui/react/src/components/tabs/CTab' +``` + +| Property | Description | Type | Default | +| --- | --- | --- | --- | +| **className** | A string of all className you want applied to the base component. | `string` | - | +| **itemKey** | Item key. | `string` \| `number` | - | diff --git a/packages/docs/content/api/CTabList.api.mdx b/packages/docs/content/api/CTabList.api.mdx new file mode 100644 index 00000000..0625b962 --- /dev/null +++ b/packages/docs/content/api/CTabList.api.mdx @@ -0,0 +1,12 @@ + +```jsx +import { CTabList } from '@coreui/react' +// or +import CTabList from '@coreui/react/src/components/tabs/CTabList' +``` + +| Property | Description | Type | Default | +| --- | --- | --- | --- | +| **className** | A string of all className you want applied to the base component. | `string` | - | +| **layout** | Specify a layout type for component. | `'fill'` \| `'justified'` | - | +| **variant** | Set the nav variant to tabs or pills. | `'pills'` \| `'tabs'` \| `'underline'` \| `'underline-border'` | - | diff --git a/packages/docs/content/api/CTabPane.api.mdx b/packages/docs/content/api/CTabPane.api.mdx index 9b2755bc..e37cc210 100644 --- a/packages/docs/content/api/CTabPane.api.mdx +++ b/packages/docs/content/api/CTabPane.api.mdx @@ -10,4 +10,5 @@ import CTabPane from '@coreui/react/src/components/tabs/CTabPane' | **className** | A string of all className you want applied to the base component. | `string` | - | | **onHide** | Callback fired when the component requests to be hidden. | `() => void` | - | | **onShow** | Callback fired when the component requests to be shown. | `() => void` | - | +| **transition** **_5.1.0+_** | Enable fade in and fade out transition. | `boolean` | true | | **visible** | Toggle the visibility of component. | `boolean` | - | diff --git a/packages/docs/content/api/CTabPanel.api.mdx b/packages/docs/content/api/CTabPanel.api.mdx new file mode 100644 index 00000000..02745245 --- /dev/null +++ b/packages/docs/content/api/CTabPanel.api.mdx @@ -0,0 +1,15 @@ + +```jsx +import { CTabPanel } from '@coreui/react' +// or +import CTabPanel from '@coreui/react/src/components/tabs/CTabPanel' +``` + +| Property | Description | Type | Default | +| --- | --- | --- | --- | +| **className** | A string of all className you want applied to the base component. | `string` | - | +| **itemKey** | Item key. | `string` \| `number` | - | +| **onHide** | Callback fired when the component requests to be hidden. | `() => void` | - | +| **onShow** | Callback fired when the component requests to be shown. | `() => void` | - | +| **transition** | Enable fade in and fade out transition. | `boolean` | true | +| **visible** | Toggle the visibility of component. | `boolean` | - | diff --git a/packages/docs/content/api/CTabs.api.mdx b/packages/docs/content/api/CTabs.api.mdx new file mode 100644 index 00000000..3a31ef16 --- /dev/null +++ b/packages/docs/content/api/CTabs.api.mdx @@ -0,0 +1,12 @@ + +```jsx +import { CTabs } from '@coreui/react' +// or +import CTabs from '@coreui/react/src/components/tabs/CTabs' +``` + +| Property | Description | Type | Default | +| --- | --- | --- | --- | +| **activeItemKey** | The active item key. | `string` \| `number` | - | +| **className** | A string of all className you want applied to the base component. | `string` | - | +| **onChange** | The callback is fired when the active tab changes. | `(value: string` \| `number) => void` | - | diff --git a/packages/docs/content/api/CTabsContext.api.mdx b/packages/docs/content/api/CTabsContext.api.mdx new file mode 100644 index 00000000..fc799c7c --- /dev/null +++ b/packages/docs/content/api/CTabsContext.api.mdx @@ -0,0 +1,10 @@ + +```jsx +import { CTabsContext } from '@coreui/react' +// or +import CTabsContext from '@coreui/react/src/components/tabs/CTabsContext' +``` + +| Property | Description | Type | Default | +| --- | --- | --- | --- | +| **activeItemKey** | The active item key. | `string` \| `number` | - | diff --git a/packages/docs/content/components/tabs.mdx b/packages/docs/content/components/tabs.mdx new file mode 100644 index 00000000..03fd7a76 --- /dev/null +++ b/packages/docs/content/components/tabs.mdx @@ -0,0 +1,347 @@ +--- +title: React Tabs Components +name: Tabs +description: The CoreUI React Tabs component provides a flexible and accessible way to create tabbed navigation in your application. It supports various styles and configurations to meet different design requirements. +menu: Components +route: /components/tabs +since: 5.1.0 +--- + +import { useState } from 'react' + +import { + CTab, + CTabContent, + CTabList, + CTabPanel, + CTabs, +} from '@coreui/react/src/index' + +## Example + +The basic React tabs example uses the `variant="tabs"` props to generate a tabbed interface. + +```jsx preview + + + Home + Profile + Contact + Disabled + + + + Home tab content + + + Profile tab content + + + Contact tab content + + + Disabled tab content + + + +``` + +## Available styles + +Change the style of ``'s component with modifiers and utilities. Mix and match as needed, or build your own. + +### Unstyled + +If you don’t provide the `variant` prop, the component will default to a basic style. + +```jsx preview + + + Home + Profile + Contact + Disabled + + + + Home tab content + + + Profile tab content + + + Contact tab content + + + Disabled tab content + + + +``` + +### Pills + +Take that same code, but use `variant="pills"` instead: + +```jsx preview + + + Home + Profile + Contact + Disabled + + + + Home tab content + + + Profile tab content + + + Contact tab content + + + Disabled tab content + + + +``` + +### Underline + +Take that same code, but use `variant="underline"` instead: + +```jsx preview + + + Home + Profile + Contact + Disabled + + + + Home tab content + + + Profile tab content + + + Contact tab content + + + Disabled tab content + + + +``` + +### Underline border + +Take that same code, but use `variant="underline-border"` instead: + +```jsx preview + + + Home + Profile + Contact + Disabled + + + + Home tab content + + + Profile tab content + + + Contact tab content + + + Disabled tab content + + + +``` + +### Fill and justify + +Force your ``'s contents to extend the full available width one of two modifier classes. To proportionately fill all available space use `layout="fill"`. Notice that all horizontal space is occupied, but not every nav item has the same width. + +```jsx preview + + + Home + Profile tab with longer content + Contact + Disabled + + + + Home tab content + + + Profile tab content + + + Contact tab content + + + Disabled tab content + + + +``` + +For equal-width elements, use `layout="justified"`. All horizontal space will be occupied by nav links, but unlike the `layout="fill"` above, every nav item will be the same width. + +```jsx preview + + + Home + Profile + Contact + Disabled + + + + Home tab content + + + Profile tab content + + + Contact tab content + + + Disabled tab content + + + +``` + +## Accessibility + +Dynamic tabbed interfaces, as described in the [WAI ARIA Authoring Practices](https://www.w3.org/TR/wai-aria-practices/#tabpanel), require `role="tablist"`, `role="tab"`, `role="tabpanel"`, and additional `aria-` attributes in order to convey their structure, functionality and current state to users of assistive technologies (such as screen readers). + +### WAI-ARIA Roles + +- The element that serves as the container for the set of tabs has the role `tablist`. +- Each element that serves as a tab has the role `tab` and is contained within the element with the role `tablist`. +- Each element that contains the content panel for a tab has the role `tabpanel`. +- If the tab list has a visible label, the element with the role `tablist` has `aria-labelledby` set to a value that refers to the labeling element. Otherwise, the `tablist` element has a label provided by `aria-label`. +- Each element with the role `tab` has the property `aria-controls` referring to its associated `tabpanel` element. +- The active tab element has the state `aria-selected` set to `true`, and all other tab elements have it set to `false`. +- Each element with the role `tabpanel` has the property `aria-labelledby` referring to its associated `tab` element. + +Our React Tabs component automatically manages all `role="..."` and `aria-` attributes for accessibility. It also handles the selected state by adding `aria-selected="true"` to the active tab. Additionally, you have the flexibility to manually set these attributes, as shown in the example below: + +```jsx + + + Home + Profile + Contact + Disabled + + + + Home tab content + + + Profile tab content + + + Contact tab content + + + Disabled tab content + + + +``` + +This example demonstrates how to manually set `aria-selected`, `aria-controls`, and `aria-labelledby` attributes on your ``'s and ``'s. + +### Keyboard Interaction + +**When focus enters the tab list:** + +Tab: It places focus on the active `tab` element. + +**When focus is on a tab element:** + +Tab: Moves focus to the next element in the tab sequence, typically the tabpanel unless the first focusable element inside the tabpanel is found earlier. + +Left Arrow: Moves focus to the previous tab. If on the first tab, it wraps around to the last tab. + +Right Arrow: Moves focus to the next tab. If on the last tab, it wraps around to the first tab. + +Home: Moves focus to the first tab. + +End: Moves focus to the last tab. + +## Customizing + +### CSS variables + +React tabs use local CSS variables on `.nav`, `.nav-tabs`, `.nav-pills`, `.nav-underline` and `.nav-underline-border` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too. + +On the `.nav` base class: + + + +On the `.nav-tabs` modifier class: + + + +On the `.nav-pills` modifier class: + + + +On the `.nav-underline` modifier class: + + + +On the `.nav-underline-border` modifier class: + + + +#### How to use CSS variables + +```jsx +const vars = { + '--my-css-var': 10, + '--my-another-css-var': "red" +} +return ... +``` + +### SASS variables + + + +## API + +### CTab + +`markdown:CTab.api.mdx` + +### CTabContent + +`markdown:CTabContent.api.mdx` + +### CTabList + +`markdown:CTabList.api.mdx` + +### CTabPanel + +`markdown:CTabPanel.api.mdx` + +### CTabs + +`markdown:CTabs.api.mdx` + diff --git a/packages/docs/src/nav.tsx b/packages/docs/src/nav.tsx index 2b3a062e..6aebda1d 100644 --- a/packages/docs/src/nav.tsx +++ b/packages/docs/src/nav.tsx @@ -293,6 +293,14 @@ const nav = [ name: 'Table', to: '/components/table/', }, + { + name: 'Tabs', + to: '/components/tabs/', + badge: { + color: 'success', + text: 'New', + }, + }, { name: 'Toast', to: '/components/toast/', From 8dc985366bd318966e44dd2befad457a3598f284 Mon Sep 17 00:00:00 2001 From: mrholek Date: Sat, 25 May 2024 19:38:49 +0200 Subject: [PATCH 4/7] chore: update dependencies and devDependencies --- packages/docs/package.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/docs/package.json b/packages/docs/package.json index e8b587d9..ab551842 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -25,21 +25,21 @@ }, "dependencies": { "@coreui/chartjs": "^4.0.0", - "@coreui/coreui": "^5.0.0", + "@coreui/coreui": "^5.0.1", "@coreui/icons": "^3.0.1", "@coreui/icons-react": "^2.2.1", "@coreui/react-chartjs": "^3.0.0", "@coreui/utils": "^2.0.2", "@docsearch/css": "^3.6.0", - "@mdx-js/mdx": "^2.3.0", - "@mdx-js/react": "^2.3.0", + "@mdx-js/mdx": "^3.0.1", + "@mdx-js/react": "^3.0.1", "@types/react-helmet": "^6.1.11", - "gatsby": "^5.13.3", + "gatsby": "^5.13.5", "gatsby-plugin-google-tagmanager": "^5.13.1", "gatsby-plugin-image": "^3.13.1", "gatsby-plugin-manifest": "^5.13.1", "gatsby-plugin-mdx": "^5.13.1", - "gatsby-plugin-offline": "^6.13.1", + "gatsby-plugin-offline": "^6.13.2", "gatsby-plugin-react-helmet": "^6.13.1", "gatsby-plugin-sass": "^6.13.1", "gatsby-plugin-sharp": "^5.13.1", @@ -53,13 +53,13 @@ "prism-react-renderer": "^2.3.1", "prismjs": "^1.29.0", "prop-types": "^15.8.1", - "react": "^18.2.0", + "react": "^18.3.1", "react-docgen-typescript": "^2.2.2", - "react-dom": "^18.2.0", + "react-dom": "^18.3.1", "react-helmet": "^6.1.0", - "react-imask": "^7.5.0", - "rimraf": "^5.0.5", - "sass": "^1.72.0" + "react-imask": "^7.6.1", + "rimraf": "^5.0.7", + "sass": "^1.77.2" }, "devDependencies": { "npm-run-all": "^4.1.5" From 4617f9d8b1c39b1e559c0d6c20ec0e2f7d5038e2 Mon Sep 17 00:00:00 2001 From: mrholek Date: Sat, 25 May 2024 19:45:20 +0200 Subject: [PATCH 5/7] docs: improve layout, add tabs support to mdx files --- .../{gatsby-browser.js => gatsby-browser.tsx} | 0 .../{gatsby-config.js => gatsby-config.mjs} | 17 +- .../docs/{gatsby-node.js => gatsby-node.mjs} | 19 +- .../docs/{gatsby-ssr.js => gatsby-ssr.tsx} | 0 packages/docs/src/components/ScssDocs.tsx | 2 +- packages/docs/src/components/Toc.tsx | 68 +++-- .../docs/src/styles/_component-examples.scss | 15 +- packages/docs/src/styles/_layout.scss | 48 ++++ packages/docs/src/styles/_toc.scss | 47 +++- packages/docs/src/templates/DefaultLayout.tsx | 18 +- packages/docs/src/templates/DocsLayout.tsx | 91 ++++--- packages/remark-code-tabs/index.js | 244 ++++++++++++++++++ packages/remark-code-tabs/package.json | 19 ++ 13 files changed, 498 insertions(+), 90 deletions(-) rename packages/docs/{gatsby-browser.js => gatsby-browser.tsx} (100%) rename packages/docs/{gatsby-config.js => gatsby-config.mjs} (88%) rename packages/docs/{gatsby-node.js => gatsby-node.mjs} (73%) rename packages/docs/{gatsby-ssr.js => gatsby-ssr.tsx} (100%) create mode 100755 packages/remark-code-tabs/index.js create mode 100644 packages/remark-code-tabs/package.json diff --git a/packages/docs/gatsby-browser.js b/packages/docs/gatsby-browser.tsx similarity index 100% rename from packages/docs/gatsby-browser.js rename to packages/docs/gatsby-browser.tsx diff --git a/packages/docs/gatsby-config.js b/packages/docs/gatsby-config.mjs similarity index 88% rename from packages/docs/gatsby-config.js rename to packages/docs/gatsby-config.mjs index 5af7a215..93254870 100644 --- a/packages/docs/gatsby-config.js +++ b/packages/docs/gatsby-config.mjs @@ -1,4 +1,12 @@ -module.exports = { +import remarkGfm from 'remark-gfm' +import remarkCodeTabs from 'remark-code-tabs' + +import { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +const config = { siteMetadata: { title: `CoreUI for React.js`, titleTemplate: `%s · React UI Components · CoreUI `, @@ -52,10 +60,7 @@ module.exports = { resolve: `gatsby-plugin-mdx`, options: { mdxOptions: { - remarkPlugins: [ - // Add GitHub Flavored Markdown (GFM) support - require(`remark-gfm`), - ], + remarkPlugins: [remarkGfm, remarkCodeTabs], }, gatsbyRemarkPlugins: [ { @@ -99,3 +104,5 @@ module.exports = { }, ], } + +export default config diff --git a/packages/docs/gatsby-node.js b/packages/docs/gatsby-node.mjs similarity index 73% rename from packages/docs/gatsby-node.js rename to packages/docs/gatsby-node.mjs index a93ffd30..97f4a8e1 100644 --- a/packages/docs/gatsby-node.js +++ b/packages/docs/gatsby-node.mjs @@ -1,7 +1,12 @@ -const path = require('node:path') -const { createFilePath } = require('gatsby-source-filesystem') +import { resolve } from 'node:path' +import { createFilePath } from 'gatsby-source-filesystem' -exports.onCreateNode = async ({ node, loadNodeContent, actions: { createNodeField }, getNode }) => { +export const onCreateNode = async ({ + node, + loadNodeContent, + actions: { createNodeField }, + getNode, +}) => { if (node.internal.type === 'Mdx') { const slug = createFilePath({ node, getNode }) @@ -22,7 +27,11 @@ exports.onCreateNode = async ({ node, loadNodeContent, actions: { createNodeFiel } } -exports.createPages = async ({ graphql, actions: { createPage, createRedirect }, reporter }) => { +export const createPages = async ({ + graphql, + actions: { createPage, createRedirect }, + reporter, +}) => { const result = await graphql(` query { allMdx { @@ -50,7 +59,7 @@ exports.createPages = async ({ graphql, actions: { createPage, createRedirect }, posts.forEach((node) => { createPage({ path: node.fields.slug, - component: `${path.resolve(`./src/templates/MdxLayout.tsx`)}?__contentFilePath=${ + component: `${resolve(`./src/templates/MdxLayout.tsx`)}?__contentFilePath=${ node.internal.contentFilePath }`, context: { id: node.id }, diff --git a/packages/docs/gatsby-ssr.js b/packages/docs/gatsby-ssr.tsx similarity index 100% rename from packages/docs/gatsby-ssr.js rename to packages/docs/gatsby-ssr.tsx diff --git a/packages/docs/src/components/ScssDocs.tsx b/packages/docs/src/components/ScssDocs.tsx index 45d649db..53c36b9f 100644 --- a/packages/docs/src/components/ScssDocs.tsx +++ b/packages/docs/src/components/ScssDocs.tsx @@ -43,7 +43,7 @@ const ScssDocs = ({ file, capture }: ScssDocsProps) => { return ( code && ( -
+
= ({ items }) => { - const toc = (items: TocItem[]) => { - return ( - items && - items.map((item, index) => { - if (Array.isArray(item.items)) { - return ( -
  • - {item.title} -
      {toc(item.items)}
    -
  • - ) - } +const toc = (items: TocItem[]) => { + return ( + items && + items.map((item, index) => { + if (Array.isArray(item.items)) { return (
  • {item.title} +
      {toc(item.items)}
  • ) - }) - ) - } + } + return ( +
  • + {item.title} +
  • + ) + }) + ) +} +const Toc: FC = ({ items }) => { + const [visible, setVisible] = useState(false) return (
    - On this page - -
      {toc(items)}
    -
    + + On this page + + +
      {toc(items)}
    +
    +
    ) } diff --git a/packages/docs/src/styles/_component-examples.scss b/packages/docs/src/styles/_component-examples.scss index ed1b36f2..3fe0496c 100644 --- a/packages/docs/src/styles/_component-examples.scss +++ b/packages/docs/src/styles/_component-examples.scss @@ -2,6 +2,14 @@ // Docs examples // +.tab-content .tab-pane { + @include border-top-radius(0); + + .highlight { + @include border-top-radius(0); + } +} + .docs-example-snippet { border: solid var(--cui-border-color); border-width: 1px 0; @@ -373,19 +381,20 @@ .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; @@ -404,7 +413,7 @@ margin: 0 ($cd-gutter-x * -.5) $spacer; .highlight { - margin-bottom: 0; + margin: 0; } .docs-example ~ .highlight { diff --git a/packages/docs/src/styles/_layout.scss b/packages/docs/src/styles/_layout.scss index 38bfe8c4..2ee63995 100644 --- a/packages/docs/src/styles/_layout.scss +++ b/packages/docs/src/styles/_layout.scss @@ -3,4 +3,52 @@ @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/src/styles/_toc.scss b/packages/docs/src/styles/_toc.scss
    index 6178d193..8f6dc04d 100644
    --- a/packages/docs/src/styles/_toc.scss
    +++ b/packages/docs/src/styles/_toc.scss
    @@ -39,4 +39,49 @@
           }
         }
       }
    -}
    \ No newline at end of file
    +}
    +
    +.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/src/templates/DefaultLayout.tsx b/packages/docs/src/templates/DefaultLayout.tsx
    index 2065aa0e..83c5fd99 100644
    --- a/packages/docs/src/templates/DefaultLayout.tsx
    +++ b/packages/docs/src/templates/DefaultLayout.tsx
    @@ -29,17 +29,15 @@ const DefaultLayout: FC = ({ children, data, pageContext, pa
         >
           
           
    -      
    +
    -
    - {path === '/404/' ? ( - {children} - ) : ( - - {children} - - )} -
    + {path === '/404/' ? ( + {children} + ) : ( + + {children} + + )}