Skip to content

[pull] main from coreui:main #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
22 changes: 11 additions & 11 deletions packages/coreui-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions packages/coreui-react/src/components/dropdown/CDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLDivElement | HTMLLIElement> {
/**
Expand Down
22 changes: 0 additions & 22 deletions packages/coreui-react/src/components/dropdown/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
56 changes: 56 additions & 0 deletions packages/coreui-react/src/components/tabs/CTab.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLButtonElement> {
/**
* A string of all className you want applied to the base component.
*/
className?: string
/**
* Item key.
*/
itemKey?: number | string
}

export const CTab = forwardRef<HTMLButtonElement, CTabProps>(
({ children, className, itemKey, ...rest }, ref) => {
const { _activeItemKey, setActiveKey, id } = useContext(TabsContext)

const isActive = () => itemKey === _activeItemKey

return (
<button
className={classNames(
'nav-link',
{
active: isActive(),
},
className,
)}
id={`${id}${itemKey}-tab`}
onClick={() => setActiveKey(itemKey)}
onFocus={() => setActiveKey(itemKey)}
role="tab"
tabIndex={isActive() ? 0 : -1}
type="button"
aria-controls={`${id}${itemKey}-tab-pane`}
aria-selected={isActive()}
ref={ref}
{...rest}
>
{children}
</button>
)
},
)

CTab.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
itemKey: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
}

CTab.displayName = 'CTab'
92 changes: 92 additions & 0 deletions packages/coreui-react/src/components/tabs/CTabList.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement> {
/**
* 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<HTMLDivElement, CTabListProps>(
({ children, className, layout, variant, ...rest }, ref) => {
const tabsRef = useRef<HTMLDivElement | HTMLUListElement | HTMLOListElement>(null)
const forkedRef = useForkedRef(ref, tabsRef)

const handleKeydown = (event: KeyboardEvent<HTMLDivElement>) => {
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 (
<div
className={classNames(
'nav',
{
[`nav-${layout}`]: layout,
[`nav-${variant}`]: variant,
},
className,
)}
role="tablist"
onKeyDown={handleKeydown}
ref={forkedRef}
{...rest}
>
{children}
</div>
)
},
)

CTabList.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
layout: PropTypes.oneOf(['fill', 'justified']),
variant: PropTypes.oneOf(['pills', 'tabs', 'underline', 'underline-border']),
}

CTabList.displayName = 'CTabList'
11 changes: 9 additions & 2 deletions packages/coreui-react/src/components/tabs/CTabPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,20 @@ export interface CTabPaneProps extends HTMLAttributes<HTMLDivElement> {
* 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.
*/
visible?: boolean
}

export const CTabPane = forwardRef<HTMLDivElement, CTabPaneProps>(
({ children, className, onHide, onShow, visible, ...rest }, ref) => {
({ children, className, onHide, onShow, transition = true, visible, ...rest }, ref) => {
const tabPaneRef = useRef()
const forkedRef = useForkedRef(ref, tabPaneRef)

Expand All @@ -35,9 +41,9 @@ export const CTabPane = forwardRef<HTMLDivElement, CTabPaneProps>(
<div
className={classNames(
'tab-pane',
'fade',
{
active: visible,
fade: transition,
show: state === 'entered',
},
className,
Expand All @@ -58,6 +64,7 @@ CTabPane.propTypes = {
className: PropTypes.string,
onHide: PropTypes.func,
onShow: PropTypes.func,
transition: PropTypes.bool,
visible: PropTypes.bool,
}

Expand Down
98 changes: 98 additions & 0 deletions packages/coreui-react/src/components/tabs/CTabPanel.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement> {
/**
* 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<HTMLDivElement, CTabPanelProps>(
({ 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 (
<Transition
in={_visible}
nodeRef={tabPaneRef}
onEnter={onShow}
onExit={onHide}
timeout={tabPaneRef.current ? getTransitionDurationFromElement(tabPaneRef.current) : 0}
>
{(state) => (
<div
className={classNames(
'tab-pane',
{
active: _visible,
fade: transition,
show: state === 'entered',
},
className,
)}
id={`${id}${itemKey}-tab-pane`}
role="tabpanel"
aria-labelledby={`${id}${itemKey}-tab`}
tabIndex={0}
ref={forkedRef}
{...rest}
>
{children}
</div>
)}
</Transition>
)
},
)

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'
Loading