From ca3ddd62e004c7c8e131ec7f1825ed32aacf1bbe Mon Sep 17 00:00:00 2001 From: Dmitry Artemov Date: Fri, 22 Nov 2024 13:22:51 +0100 Subject: [PATCH] feat(Breadcrumbs): add disabledCurrent prop --- .../lab/Breadcrumbs/BreadcrumbItem.tsx | 15 ++++---- .../lab/Breadcrumbs/Breadcrumbs.tsx | 5 ++- src/components/lab/Breadcrumbs/README.md | 35 ++++++++++--------- .../__tests__/Breadcrumbs.test.tsx | 34 ++++++++++++++++++ 4 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/components/lab/Breadcrumbs/BreadcrumbItem.tsx b/src/components/lab/Breadcrumbs/BreadcrumbItem.tsx index e7c32c5f24..00c03df808 100644 --- a/src/components/lab/Breadcrumbs/BreadcrumbItem.tsx +++ b/src/components/lab/Breadcrumbs/BreadcrumbItem.tsx @@ -13,6 +13,7 @@ interface BreadcrumbProps extends BreadcrumbsItemProps { current?: boolean; itemType?: 'link' | 'menu'; disabled?: boolean; + disabledLink?: boolean; navigate?: (href: Href, routerOptions: RouterOptions | undefined) => void; } export function BreadcrumbItem(props: BreadcrumbProps) { @@ -25,7 +26,7 @@ export function BreadcrumbItem(props: BreadcrumbProps) { } const handleAction = (event: React.MouseEvent | React.KeyboardEvent) => { - if (props.disabled || props.current) { + if (props.disabled) { event.preventDefault(); return; } @@ -43,11 +44,11 @@ export function BreadcrumbItem(props: BreadcrumbProps) { } }; - const isDisabled = props.disabled || props.current; let linkProps: React.AnchorHTMLAttributes = { title, onClick: handleAction, - 'aria-disabled': isDisabled ? true : undefined, + 'aria-current': props.current ? 'page' : undefined, + 'aria-disabled': props.disabled ? true : undefined, }; if (Element === 'a') { linkProps.href = props.href; @@ -59,7 +60,7 @@ export function BreadcrumbItem(props: BreadcrumbProps) { linkProps.referrerPolicy = props.referrerPolicy; } else { linkProps.role = 'link'; - linkProps.tabIndex = isDisabled ? undefined : 0; + linkProps.tabIndex = props.disabled ? undefined : 0; linkProps.onKeyDown = (event: React.KeyboardEvent) => { if (event.key === 'Enter') { handleAction(event); @@ -67,10 +68,6 @@ export function BreadcrumbItem(props: BreadcrumbProps) { }; } - if (props.current) { - linkProps['aria-current'] = 'page'; - } - if (props.itemType === 'menu') { linkProps = {}; } @@ -84,7 +81,7 @@ export function BreadcrumbItem(props: BreadcrumbProps) { ? b('menu') : b('link', { 'is-current': props.current, - 'is-disabled': isDisabled && !props.current, + 'is-disabled': props.disabledLink, }) } > diff --git a/src/components/lab/Breadcrumbs/Breadcrumbs.tsx b/src/components/lab/Breadcrumbs/Breadcrumbs.tsx index 8cd6ef2120..3f220adf16 100644 --- a/src/components/lab/Breadcrumbs/Breadcrumbs.tsx +++ b/src/components/lab/Breadcrumbs/Breadcrumbs.tsx @@ -44,6 +44,7 @@ export interface BreadcrumbsProps extends DOMProps, AriaLabelingProps, QAProps { children: React.ReactElement | React.ReactElement[]; navigate?: (href: Href, routerOptions: RouterOptions | undefined) => void; disabled?: boolean; + disabledCurrent?: boolean; onAction?: (key: Key) => void; } @@ -222,6 +223,7 @@ export const Breadcrumbs = React.forwardRef(function Breadcrumbs( } const lastIndex = contents.length - 1; + const disabledCurrent = props.disabledCurrent ?? true; const breadcrumbItems = contents.map((child, index) => { const isCurrent = index === lastIndex; const key = child.key ?? index; @@ -237,7 +239,8 @@ export const Breadcrumbs = React.forwardRef(function Breadcrumbs( {...child.props} key={key} current={isCurrent} - disabled={props.disabled} + disabled={props.disabled || (isCurrent && disabledCurrent)} + disabledLink={props.disabled} onAction={handleAction} navigate={navigate} > diff --git a/src/components/lab/Breadcrumbs/README.md b/src/components/lab/Breadcrumbs/README.md index b3e3683234..f266903334 100644 --- a/src/components/lab/Breadcrumbs/README.md +++ b/src/components/lab/Breadcrumbs/README.md @@ -471,23 +471,24 @@ LANDING_BLOCK--> ## Properties -| Name | Description | Type | Default | -| :--------------- | :-------------------------------------------------------------------- | :----------------------------------------- | :------ | -| children | Breadcrumb items. | `React.ReactElement` | | -| disabled | Whether the Breadcrumbs are disabled. | `boolean` | | -| showRoot | Whether to always show the root item if the items are collapsed. | `boolean` | | -| popupPlacement | Style of collapsed item popup. | `PopupPlacement` | | -| popupStyle | Style of collapsed item popup. | `"staircase"` | | -| qa | HTML `data-qa` attribute, used in tests. | `string` | | -| separator | Custom separator node. | `React.ReactNode` | "/" | -| action | `click` event handler. | `(id: Key) => void` | | -| navigate | client side navigation. | `(href: string) => void` | | -| id | The element's unique identifier. | `string` | | -| className | CSS class name for the element. | `string` | | -| style | Sets inline style for the element. | `CSSProperties` | | -| aria-label | Defines a string value that labels the current element. | `string` | | -| aria-labelledby | Identifies the element (or elements) that labels the current element. | `string` | | -| aria-describedby | Identifies the element (or elements) that describes the object. | `string` | | +| Name | Description | Type | Default | +| :--------------- | :------------------------------------------------------------------------------- | :----------------------------------------- | :------ | +| children | Breadcrumb items. | `React.ReactElement` | | +| disabled | Whether the Breadcrumbs are disabled. | `boolean` | | +| disabledCurrent | Whether to disable the last item (only if the component itself is not disabled). | `boolean` | `true` | +| showRoot | Whether to always show the root item if the items are collapsed. | `boolean` | | +| popupPlacement | Style of collapsed item popup. | `PopupPlacement` | | +| popupStyle | Style of collapsed item popup. | `"staircase"` | | +| qa | HTML `data-qa` attribute, used in tests. | `string` | | +| separator | Custom separator node. | `React.ReactNode` | "/" | +| action | `click` event handler. | `(id: Key) => void` | | +| navigate | client side navigation. | `(href: string) => void` | | +| id | The element's unique identifier. | `string` | | +| className | CSS class name for the element. | `string` | | +| style | Sets inline style for the element. | `CSSProperties` | | +| aria-label | Defines a string value that labels the current element. | `string` | | +| aria-labelledby | Identifies the element (or elements) that labels the current element. | `string` | | +| aria-describedby | Identifies the element (or elements) that describes the object. | `string` | | ### BreadcrumbsItemProps diff --git a/src/components/lab/Breadcrumbs/__tests__/Breadcrumbs.test.tsx b/src/components/lab/Breadcrumbs/__tests__/Breadcrumbs.test.tsx index f44ec07f73..fb045b9b9d 100644 --- a/src/components/lab/Breadcrumbs/__tests__/Breadcrumbs.test.tsx +++ b/src/components/lab/Breadcrumbs/__tests__/Breadcrumbs.test.tsx @@ -282,3 +282,37 @@ it('should support RouterProvider', async () => { await userEvent.click(items[1]); expect(navigate).toHaveBeenCalledWith('/foo', {foo: 'foo'}); }); + +it('should disable all items', () => { + render( + + Folder 1 + Folder 2 + Folder 3 + , + ); + + const item1 = screen.getByText('Folder 1'); + expect(item1.tabIndex).toBe(-1); + expect(item1).toHaveAttribute('aria-disabled', 'true'); + const item2 = screen.getByText('Folder 2'); + expect(item2.tabIndex).toBe(-1); + expect(item2).toHaveAttribute('aria-disabled', 'true'); + const item3 = screen.getByText('Folder 3'); + expect(item3.tabIndex).toBe(-1); + expect(item3).toHaveAttribute('aria-disabled', 'true'); +}); + +it('should not disable current item', () => { + render( + + Folder 1 + Folder 2 + Folder 3 + , + ); + + const item3 = screen.getByText('Folder 3'); + expect(item3.tabIndex).toBe(0); + expect(item3).not.toHaveAttribute('aria-disabled'); +});