diff --git a/.github/ISSUE_TEMPLATE/PF6_alpha_bug_request.md b/.github/ISSUE_TEMPLATE/PF6_alpha_bug_request.md new file mode 100644 index 00000000000..1251cfa2a1f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/PF6_alpha_bug_request.md @@ -0,0 +1,17 @@ +--- +name: PF6 alpha bug +about: Report a bug discovered while testing the PatternFly 6 alphas +title: "[short description]" +labels: 'v6 alpha bug' +assignees: '' + +--- + +**Please describe the issue** + +**Are there visuals for this issue? Please provide screenshots** +Include screenshots or links to Marvel or other mockups. + +**Could you point to a branch or draft PR where this issue exists? Please provide a link to the code** + +**Any other information?** diff --git a/.gitignore b/.gitignore index ac4282c4b23..07168936c32 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ lerna-debug.log .vscode # For vim *.swp +.yarn diff --git a/README.md b/README.md index 2ae010662f7..e5ae0c36ebb 100644 --- a/README.md +++ b/README.md @@ -86,3 +86,4 @@ All React contributors must first be [PatternFly community contributors](https:/ ### License PatternFly React is licensed under the [MIT License](https://github.com/patternfly/patternfly-react/tree/main/LICENSE). + diff --git a/babel.config.js b/babel.config.js index c03fd5c5ab3..a5caef7249f 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,20 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - esmodules: true - } - } - ], - '@babel/preset-typescript', - '@babel/preset-react' - ], - plugins: [ - ['@babel/plugin-proposal-decorators', { legacy: true }], - '@babel/plugin-proposal-optional-chaining', - '@babel/plugin-proposal-class-properties', - '@babel/plugin-proposal-object-rest-spread' - ] + presets: ['@babel/preset-typescript', '@babel/preset-react'], + plugins: ['@babel/plugin-transform-modules-commonjs'] }; diff --git a/jest.config.js b/jest.config.js index c3669208009..eac3fcc7b65 100644 --- a/jest.config.js +++ b/jest.config.js @@ -14,13 +14,11 @@ module.exports = { '^.+\\.svg$': 'jest-transform-stub' }, setupFilesAfterEnv: ['/packages/testSetup.ts'], - transformIgnorePatterns: [ - 'node_modules/(?!@patternfly|@novnc|@popperjs|lodash|monaco-editor|react-monaco-editor|case-anything)' - ], testPathIgnorePatterns: ['/packages/react-integration/'], coveragePathIgnorePatterns: ['/dist/'], moduleNameMapper: { '\\.(css|less)$': '/packages/react-styles/__mocks__/styleMock.js' }, - testEnvironment: 'jsdom' + testEnvironment: 'jsdom', + transformIgnorePatterns: ['/node_modules/(?!(case-anything)/)'] }; diff --git a/package.json b/package.json index 7c4fd7f5c0f..f3f6b761f97 100644 --- a/package.json +++ b/package.json @@ -24,16 +24,10 @@ }, "homepage": "https://github.com/patternfly/patternfly-react#readme", "devDependencies": { - "@babel/core": "^7.21.8", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-decorators": "^7.21.0", - "@babel/plugin-proposal-object-rest-spread": "^7.20.7", - "@babel/plugin-proposal-optional-chaining": "^7.21.0", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.21.0", - "@babel/preset-env": "^7.21.5", - "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.21.5", + "@babel/core": "^7.24.3", + "@babel/plugin-transform-modules-commonjs": "^7.24.1", + "@babel/preset-react": "^7.24.1", + "@babel/preset-typescript": "^7.24.1", "@octokit/rest": "^20.0.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", @@ -43,8 +37,7 @@ "@types/react-dom": "^18", "@typescript-eslint/eslint-plugin": "^5.59.2", "@typescript-eslint/parser": "^5.59.2", - "babel-jest": "^27.2.5", - "jest-transform-stub": "^2.0.0", + "babel-jest": "^29.7.0", "concurrently": "^7.6.0", "eslint": "^8.39.0", "eslint-plugin-markdown": "^3.0.0", @@ -56,6 +49,7 @@ "husky": "^4.3.0", "jest": "27.2.5", "jest-cli": "27.2.5", + "jest-transform-stub": "^2.0.0", "lerna": "^7.1.5", "lint-staged": "^14.0.0", "mutation-observer": "^1.0.3", @@ -64,6 +58,7 @@ "react": "^18", "react-dom": "^18", "surge": "^0.23.1", + "ts-node": "^10.9.1", "ts-patch": "^2.1.0", "typescript": "^4.7.4", "ts-node": "^10.9.1" diff --git a/packages/react-charts/src/components/ChartLegendTooltip/ChartLegendTooltip.tsx b/packages/react-charts/src/components/ChartLegendTooltip/ChartLegendTooltip.tsx index 19ca376359c..3599291d927 100644 --- a/packages/react-charts/src/components/ChartLegendTooltip/ChartLegendTooltip.tsx +++ b/packages/react-charts/src/components/ChartLegendTooltip/ChartLegendTooltip.tsx @@ -28,7 +28,7 @@ import { getTheme } from '../ChartUtils/chart-theme'; * * See https://github.com/FormidableLabs/victory/blob/main/packages/victory-tooltip/src/index.d.ts */ -export interface ChartLegendTooltipProps extends ChartCursorTooltipProps { +export interface ChartLegendTooltipProps extends Omit { /** * The active prop specifies whether the tooltip component should be displayed. */ @@ -295,8 +295,11 @@ export interface ChartLegendTooltipProps extends ChartCursorTooltipProps { themeColor?: string; /** * The title prop specifies a title to render with the legend. + * + * @propType number | string | Function | string[] + * @example title={(datum) => datum.x} */ - title?: string; + title?: string[] | StringOrNumberOrCallback; /** * This prop refers to the width of the svg that ChartLegendTooltip is rendered within. This prop is passed from * parents of ChartLegendTooltip, and should not be set manually. In versions before ^33.0.0 this prop referred to the diff --git a/packages/react-code-editor/README.md b/packages/react-code-editor/README.md index c1ba2ea1e39..9c9343c4cb5 100644 --- a/packages/react-code-editor/README.md +++ b/packages/react-code-editor/README.md @@ -26,7 +26,19 @@ yarn add @patternfly/react-code-editor or ``` -npm install @patternfly/react-code-editor --save +npm install @patternfly/react-code-editor +``` + +[!NOTE] For TypeScript type definitions, this package uses the `monaco-editor` package as a peer dependency. So, if you need types and don't already have the `monaco-editor package` installed, you will need to do so: + +``` +yarn add --dev monaco-editor +``` + +or + +``` +npm install --dev monaco-editor ``` ### Usage @@ -43,14 +55,6 @@ import '@patternfly/react-core/dist/styles/base.css'; import { CodeEditor } from '@patternfly/react-code-editor'; ``` -Install peer deps - -```json -"monaco-editor": "^0.21.3", -"react": "^17 || ^18", -"react-dom": "^17 || ^18" -``` - #### With create-react-app Projects If you created your project with `create-react-app` you'll have some extra work to do, or you wont have syntax highlighting. Using the webpack plugin requires updating your webpack config, which `create-react-app` abstracts away. You can `npm eject` your project, but you may not want to do that. To keep your app set up in the `create-react-app` style but to get access to your webpack config you can use `react-app-rewired`. diff --git a/packages/react-code-editor/package.json b/packages/react-code-editor/package.json index 8e267b8faee..56224581737 100644 --- a/packages/react-code-editor/package.json +++ b/packages/react-code-editor/package.json @@ -42,6 +42,7 @@ "react-dom": "^17 || ^18" }, "devDependencies": { + "monaco-editor": "^0.47.0", "rimraf": "^2.6.2", "typescript": "^4.7.4" } diff --git a/packages/react-code-editor/src/components/CodeEditor/CodeEditor.tsx b/packages/react-code-editor/src/components/CodeEditor/CodeEditor.tsx index 9aab8cc69a4..ac7fc3f5d4b 100644 --- a/packages/react-code-editor/src/components/CodeEditor/CodeEditor.tsx +++ b/packages/react-code-editor/src/components/CodeEditor/CodeEditor.tsx @@ -16,7 +16,7 @@ import { TooltipPosition } from '@patternfly/react-core'; import Editor, { EditorProps, Monaco } from '@monaco-editor/react'; -import { editor } from 'monaco-editor/esm/vs/editor/editor.api'; +import type { editor } from 'monaco-editor'; import CopyIcon from '@patternfly/react-icons/dist/esm/icons/copy-icon'; import UploadIcon from '@patternfly/react-icons/dist/esm/icons/upload-icon'; import DownloadIcon from '@patternfly/react-icons/dist/esm/icons/download-icon'; diff --git a/packages/react-core/README.md b/packages/react-core/README.md index 899d79c251c..cd5c12196a3 100644 --- a/packages/react-core/README.md +++ b/packages/react-core/README.md @@ -53,3 +53,4 @@ All React contributors must first be [PatternFly community contributors](https:/ ### License PatternFly React is licensed under the [MIT License](https://github.com/patternfly/patternfly-react/tree/main/LICENSE). + diff --git a/packages/react-core/src/components/Accordion/__tests__/AccordionToggle.test.tsx b/packages/react-core/src/components/Accordion/__tests__/AccordionToggle.test.tsx index cd8c6b03396..d86677109bb 100644 --- a/packages/react-core/src/components/Accordion/__tests__/AccordionToggle.test.tsx +++ b/packages/react-core/src/components/Accordion/__tests__/AccordionToggle.test.tsx @@ -211,6 +211,34 @@ test('Renders toggle icon before toggle text when togglePosition from context = expect(toggle.firstChild).toHaveClass(styles.accordionToggleIcon); }); +test('Renders toggle text before toggle icon by default', () => { + render( + + + Test + + + ); + + const toggle = screen.getByRole('button'); + + expect(toggle.firstChild).toHaveClass(styles.accordionToggleText); +}); + +test('Renders toggle icon before toggle text when togglePosition from context = "start"', () => { + render( + + + Test + + + ); + + const toggle = screen.getByRole('button'); + + expect(toggle.firstChild).toHaveClass(styles.accordionToggleIcon); +}); + test('Matches the snapshot', () => { const { asFragment } = render( diff --git a/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx b/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx index 6c5a3f722dd..879289a6725 100644 --- a/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx +++ b/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx @@ -83,9 +83,6 @@ export interface CalendarProps extends CalendarFormat, Omit boolean)[]; } -// Must be numeric given current header design -const yearFormat = (date: Date) => date.getFullYear(); - const buildCalendar = (year: number, month: number, weekStart: number, validators: ((date: Date) => boolean)[]) => { const defaultDate = new Date(year, month); @@ -164,6 +161,13 @@ export const CalendarMonth = ({ }; const initialDate = getInitialDate(); const [focusedDate, setFocusedDate] = React.useState(initialDate); + + // Must be numeric given current header design + const yearFormat = (date: Date) => date.getFullYear(); + // + const yearFormatted = yearFormat(focusedDate); + const [yearInput, setYearInput] = React.useState(yearFormatted.toString()); + const [hoveredDate, setHoveredDate] = React.useState(new Date(focusedDate)); const focusRef = React.useRef(); const [hiddenMonthId] = React.useState(getUniqueId('hidden-month-span')); @@ -191,6 +195,7 @@ export const CalendarMonth = ({ setHoveredDate(newDate); setShouldFocus(false); onMonthChange(ev, newDate); + setYearInput(yearFormat(newDate).toString()); }; const onKeyDown = (ev: React.KeyboardEvent) => { @@ -217,6 +222,34 @@ export const CalendarMonth = ({ const changeMonth = (newMonth: number, newYear?: number) => new Date(newYear ?? focusedDate.getFullYear(), newMonth, 1); + const MIN_YEAR = 1900; + const MAX_YEAR = 2100; + + const handleYearInputChange = (event: React.FormEvent, yearStr: string) => { + if (!/^\d{0,4}$/.test(yearStr)) { + return; + } + + setYearInput(yearStr); + + if (yearStr.length === 4) { + const yearNum = Number(yearStr); + + if (yearNum >= MIN_YEAR && yearNum <= MAX_YEAR) { + const newDate = changeYear(yearNum); + setFocusedDate(newDate); + setHoveredDate(newDate); + setShouldFocus(false); + + // We need to manually focus the year input in FireFox when the scroll buttons are clicked, as FireFox doesn't place focus automatically + (event.target as HTMLElement).focus(); + onMonthChange(event, newDate); + } else { + setYearInput(yearFormatted.toString()); + } + } + }; + const addMonth = (toAdd: -1 | 1) => { let newMonth = focusedDate.getMonth() + toAdd; let newYear = focusedDate.getFullYear(); @@ -254,7 +287,6 @@ export const CalendarMonth = ({ } const isHoveredDateValid = isValidated(hoveredDate); const monthFormatted = monthFormat(focusedDate); - const yearFormatted = yearFormat(focusedDate); const calendarToRender = (
@@ -321,15 +353,8 @@ export const CalendarMonth = ({ , year: string) => { - const newDate = changeYear(Number(year)); - setFocusedDate(newDate); - setHoveredDate(newDate); - setShouldFocus(false); - focusRef.current?.blur(); // will unfocus a date when changing year via up/down arrows - onMonthChange(ev, newDate); - }} + value={yearInput} + onChange={handleYearInputChange} />
diff --git a/packages/react-core/src/components/ClipboardCopy/ClipboardCopy.tsx b/packages/react-core/src/components/ClipboardCopy/ClipboardCopy.tsx index 1fc674de587..2ded3cc3cdc 100644 --- a/packages/react-core/src/components/ClipboardCopy/ClipboardCopy.tsx +++ b/packages/react-core/src/components/ClipboardCopy/ClipboardCopy.tsx @@ -10,8 +10,22 @@ import { ClipboardCopyToggle } from './ClipboardCopyToggle'; import { ClipboardCopyExpanded } from './ClipboardCopyExpanded'; import { getOUIAProps, OUIAProps } from '../../helpers'; -export const clipboardCopyFunc = (event: React.ClipboardEvent, text?: string) => { - navigator.clipboard.writeText(text.toString()); +export const clipboardCopyFunc = (_event: React.ClipboardEvent, text?: React.ReactNode) => { + try { + navigator.clipboard.writeText(text.toString()); + } catch (error) { + // eslint-disable-next-line no-console + console.warn( + "Clipboard API not found, this copy function will not work. This is likely because you're using an", + "unsupported browser or you're not using HTTPS. \n\nIf you're a developer building an application which needs", + "to support copying to the clipboard without the clipboard API, you'll have to create your own copy", + 'function and pass it to the ClipboardCopy component as the onCopy prop. For more information see', + 'https://developer.mozilla.org/en-US/docs/Web/API/Navigator/clipboard' + ); + + // eslint-disable-next-line no-console + console.error(error); + } }; export enum ClipboardCopyVariant { @@ -70,8 +84,8 @@ export interface ClipboardCopyProps extends Omit exitDelay?: number; /** Delay in ms before the tooltip appears. */ entryDelay?: number; - /** A function that is triggered on clicking the copy button. */ - onCopy?: (event: React.ClipboardEvent, text?: string) => void; + /** A function that is triggered on clicking the copy button. This will replace the existing clipboard copy functionality entirely. */ + onCopy?: (event: React.ClipboardEvent, text?: React.ReactNode) => void; /** A function that is triggered on changing the text. */ onChange?: (event: React.FormEvent, text?: string) => void; /** The text which is copied. */ diff --git a/packages/react-core/src/components/ClipboardCopy/__tests__/ClipboardCopy.test.tsx b/packages/react-core/src/components/ClipboardCopy/__tests__/ClipboardCopy.test.tsx index 4d2b38cb5a6..88221d26dcd 100644 --- a/packages/react-core/src/components/ClipboardCopy/__tests__/ClipboardCopy.test.tsx +++ b/packages/react-core/src/components/ClipboardCopy/__tests__/ClipboardCopy.test.tsx @@ -4,6 +4,8 @@ import { ClipboardCopy } from '../ClipboardCopy'; import styles from '@patternfly/react-styles/css/components/ClipboardCopy/clipboard-copy'; import userEvent from '@testing-library/user-event'; +jest.mock('../../../helpers/GenerateId/GenerateId'); + jest.mock('../ClipboardCopyButton', () => ({ ClipboardCopyButton: ({ 'aria-label': ariaLabel, children, entryDelay, exitDelay, maxWidth, position, onClick }) => (
diff --git a/packages/react-core/src/components/ClipboardCopy/__tests__/__snapshots__/ClipboardCopy.test.tsx.snap b/packages/react-core/src/components/ClipboardCopy/__tests__/__snapshots__/ClipboardCopy.test.tsx.snap index 3912ac8b5c8..3e5b6a2c056 100644 --- a/packages/react-core/src/components/ClipboardCopy/__tests__/__snapshots__/ClipboardCopy.test.tsx.snap +++ b/packages/react-core/src/components/ClipboardCopy/__tests__/__snapshots__/ClipboardCopy.test.tsx.snap @@ -21,7 +21,7 @@ exports[`Matches snapshot 1`] = ` data-ouia-component-id="OUIA-Generated-TextInputBase-26" data-ouia-component-type="PF5/TextInput" data-ouia-safe="true" - id="text-input-34" + id="text-input-generated-id" type="text" value="Copyable text" /> diff --git a/packages/react-core/src/components/Drawer/__tests__/Drawer.test.tsx b/packages/react-core/src/components/Drawer/__tests__/Drawer.test.tsx index 067370270c7..c215634bb50 100644 --- a/packages/react-core/src/components/Drawer/__tests__/Drawer.test.tsx +++ b/packages/react-core/src/components/Drawer/__tests__/Drawer.test.tsx @@ -14,6 +14,8 @@ import { render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { KeyTypes } from '../../../helpers'; +jest.mock('../../../helpers/GenerateId/GenerateId'); + Object.values([ { isExpanded: true, isInline: false, isStatic: false }, { isExpanded: false, isInline: false, isStatic: false }, diff --git a/packages/react-core/src/components/Drawer/__tests__/DrawerPanelContent.test.tsx b/packages/react-core/src/components/Drawer/__tests__/DrawerPanelContent.test.tsx index 0f1135707e9..afdbf1c1a89 100644 --- a/packages/react-core/src/components/Drawer/__tests__/DrawerPanelContent.test.tsx +++ b/packages/react-core/src/components/Drawer/__tests__/DrawerPanelContent.test.tsx @@ -34,6 +34,8 @@ test(`Renders with class ${styles.modifiers.secondary} when colorVariant="second expect(screen.getByText('Drawer panel content')).toHaveClass(styles.modifiers.secondary); }); +jest.mock('../../../helpers/GenerateId/GenerateId'); + test('Does not render with aria-labelledby by default', () => { render( diff --git a/packages/react-core/src/components/Drawer/__tests__/Generated/DrawerPanelContent.test.tsx b/packages/react-core/src/components/Drawer/__tests__/Generated/DrawerPanelContent.test.tsx index 4648dc9f0e5..ed8676d0943 100644 --- a/packages/react-core/src/components/Drawer/__tests__/Generated/DrawerPanelContent.test.tsx +++ b/packages/react-core/src/components/Drawer/__tests__/Generated/DrawerPanelContent.test.tsx @@ -7,6 +7,8 @@ import { DrawerPanelContent } from '../../DrawerPanelContent'; // any missing imports can usually be resolved by adding them here import {} from '../..'; +jest.mock('../../../../helpers/GenerateId/GenerateId'); + it('DrawerPanelContent should match snapshot (auto-generated)', () => { const { asFragment } = render(ReactNode
} />); expect(asFragment()).toMatchSnapshot(); diff --git a/packages/react-core/src/components/Drawer/__tests__/Generated/__snapshots__/DrawerPanelContent.test.tsx.snap b/packages/react-core/src/components/Drawer/__tests__/Generated/__snapshots__/DrawerPanelContent.test.tsx.snap index 2f08c599b56..b17ab76a1e7 100644 --- a/packages/react-core/src/components/Drawer/__tests__/Generated/__snapshots__/DrawerPanelContent.test.tsx.snap +++ b/packages/react-core/src/components/Drawer/__tests__/Generated/__snapshots__/DrawerPanelContent.test.tsx.snap @@ -5,7 +5,7 @@ exports[`DrawerPanelContent should match snapshot (auto-generated) 1`] = `
@@ -298,7 +298,7 @@ exports[`Drawer isExpanded = false and isInline = true and isStatic = false 1`]
@@ -324,7 +324,7 @@ exports[`Drawer isExpanded = true and isInline = false and isStatic = false 1`]
= ({ @@ -83,6 +87,8 @@ const DropdownBase: React.FunctionComponent = ({ zIndex = 9999, popperProps, onOpenChangeKeys = ['Escape', 'Tab'], + menuHeight, + maxMenuHeight, ...props }: DropdownProps) => { const localMenuRef = React.useRef(); @@ -111,8 +117,8 @@ const DropdownBase: React.FunctionComponent = ({ }; const handleClick = (event: MouseEvent) => { - // toggle was clicked open via keyboard, focus on first menu item - if (isOpen && toggleRef.current?.contains(event.target as Node) && event.detail === 0) { + // toggle was opened, focus on first menu item + if (isOpen && toggleRef.current?.contains(event.target as Node)) { setTimeout(() => { const firstElement = menuRef?.current?.querySelector( 'li button:not(:disabled),li input:not(:disabled),li a:not([aria-disabled="true"])' @@ -138,6 +144,8 @@ const DropdownBase: React.FunctionComponent = ({ }; }, [isOpen, menuRef, toggleRef, onOpenChange, onOpenChangeKeys]); + const scrollable = maxMenuHeight !== undefined || menuHeight !== undefined || isScrollable; + const menu = ( = ({ shouldFocusToggleOnSelect && toggleRef.current.focus(); }} isPlain={isPlain} - isScrollable={isScrollable} + isScrollable={scrollable} {...props} {...ouiaProps} > - {children} + + {children} + ); return ( diff --git a/packages/react-core/src/components/Form/__tests__/FormFieldGroup.test.tsx b/packages/react-core/src/components/Form/__tests__/FormFieldGroup.test.tsx index 04e42efa54c..3ddf4674ad7 100644 --- a/packages/react-core/src/components/Form/__tests__/FormFieldGroup.test.tsx +++ b/packages/react-core/src/components/Form/__tests__/FormFieldGroup.test.tsx @@ -7,6 +7,8 @@ import { FormFieldGroupExpandable } from '../FormFieldGroupExpandable'; import { FormFieldGroupHeader } from '../FormFieldGroupHeader'; import { Button } from '../../Button'; +jest.mock('../../../helpers/GenerateId/GenerateId'); + test('Check form field group example against snapshot', () => { const FieldGroup = ( { const Section = ; const { asFragment } = render(Section); diff --git a/packages/react-core/src/components/Form/__tests__/__snapshots__/FormFieldGroup.test.tsx.snap b/packages/react-core/src/components/Form/__tests__/__snapshots__/FormFieldGroup.test.tsx.snap index 614ced13041..e375a3d4385 100644 --- a/packages/react-core/src/components/Form/__tests__/__snapshots__/FormFieldGroup.test.tsx.snap +++ b/packages/react-core/src/components/Form/__tests__/__snapshots__/FormFieldGroup.test.tsx.snap @@ -17,12 +17,12 @@ exports[`Check expandable form field group example against snapshot 1`] = ` aria-disabled="false" aria-expanded="true" aria-label="toggle" - aria-labelledby="title-text-id2 form-field-group-toggle0" + aria-labelledby="title-text-id2 generated-id" class="pf-v6-c-button pf-m-plain" data-ouia-component-id="OUIA-Generated-Button-plain-1" data-ouia-component-type="PF5/Button" data-ouia-safe="true" - id="form-field-group-toggle0" + id="generated-id" type="button" >

Title

diff --git a/packages/react-core/src/components/Label/Label.tsx b/packages/react-core/src/components/Label/Label.tsx index 9d82b3644b5..78bef70da64 100644 --- a/packages/react-core/src/components/Label/Label.tsx +++ b/packages/react-core/src/components/Label/Label.tsx @@ -27,6 +27,8 @@ export interface LabelProps extends React.HTMLProps { status?: 'success' | 'warning' | 'danger' | 'info' | 'custom'; /** Flag indicating the label is compact. */ isCompact?: boolean; + /** Flag indicating the label is disabled. Works only on clickable labels, so either href or onClick props must be passed in. */ + isDisabled?: boolean; /** @beta Flag indicating the label is editable. */ isEditable?: boolean; /** @beta Additional props passed to the editable label text div. Optionally passing onInput and onBlur callbacks will allow finer custom text input control. */ @@ -106,6 +108,7 @@ export const Label: React.FunctionComponent = ({ variant = 'filled', status, isCompact = false, + isDisabled = false, isEditable = false, editableProps, textMaxWidth, @@ -224,29 +227,23 @@ export const Label: React.FunctionComponent = ({ } }; - const LabelComponent = (isOverflowLabel || isAddLabel ? 'button' : 'span') as any; + const isClickableDisabled = (href || onLabelClick) && isDisabled; - let _closeBtnAriaLabel = 'Close label'; - if (closeBtnAriaLabel) { - _closeBtnAriaLabel = closeBtnAriaLabel; - } else if (typeof children === 'string') { - _closeBtnAriaLabel = `Close ${children}`; - } - - const defaultButton = ( + const defaultCloseButton = ( ); - const button = {closeBtn || defaultButton}; + const closeButton = {closeBtn || defaultCloseButton}; const textRef = React.createRef(); // ref to apply tooltip when rendered is used const componentRef = React.useRef(); @@ -302,6 +299,8 @@ export const Label: React.FunctionComponent = ({ className: css(styles.labelContent, isClickable && styles.modifiers.clickable), ...(isTooltipVisible && { tabIndex: 0 }), ...(href && { href }), + // Need to prevent onClick since aria-disabled won't prevent AT from triggering the link + ...(href && isDisabled && { onClick: (event: MouseEvent) => event.preventDefault() }), ...(isButton && clickableLabelProps), ...(isEditable && { ref: editableButtonRef, @@ -310,7 +309,9 @@ export const Label: React.FunctionComponent = ({ e.stopPropagation(); }, ...editableProps - }) + }), + ...(isClickableDisabled && isButton && { disabled: true }), + ...(isClickableDisabled && href && { tabindex: -1, 'aria-disabled': true }) }; let labelComponentChild = ( @@ -336,11 +337,14 @@ export const Label: React.FunctionComponent = ({ ); } + const LabelComponent = (isOverflowLabel ? 'button' : 'span') as any; + return ( = ({ onClick={isOverflowLabel || isAddLabel ? onLabelClick : undefined} > {!isEditableActive && labelComponentChild} - {!isEditableActive && onClose && button} + {!isEditableActive && onClose && closeButton} {isEditableActive && ( { test('label group default', () => { const { asFragment } = render( diff --git a/packages/react-core/src/components/Label/__tests__/__snapshots__/LabelGroup.test.tsx.snap b/packages/react-core/src/components/Label/__tests__/__snapshots__/LabelGroup.test.tsx.snap index be9cdd92224..fe6a192ac94 100644 --- a/packages/react-core/src/components/Label/__tests__/__snapshots__/LabelGroup.test.tsx.snap +++ b/packages/react-core/src/components/Label/__tests__/__snapshots__/LabelGroup.test.tsx.snap @@ -134,12 +134,12 @@ exports[`LabelGroup label group with category 1`] = `
    @@ -177,12 +177,12 @@ exports[`LabelGroup label group with category and tooltip 1`] = `
      @@ -220,12 +220,12 @@ exports[`LabelGroup label group with closable category 1`] = `
        @@ -254,12 +254,12 @@ exports[`LabelGroup label group with closable category 1`] = `
diff --git a/packages/react-core/src/components/MenuToggle/MenuToggleCheckbox.tsx b/packages/react-core/src/components/MenuToggle/MenuToggleCheckbox.tsx index a2d7aeee354..ac9e017169c 100644 --- a/packages/react-core/src/components/MenuToggle/MenuToggleCheckbox.tsx +++ b/packages/react-core/src/components/MenuToggle/MenuToggleCheckbox.tsx @@ -84,7 +84,7 @@ class MenuToggleCheckbox extends React.Component ); return ( -