diff --git a/packages/react-components/react-breadcrumb/etc/react-breadcrumb.api.md b/packages/react-components/react-breadcrumb/etc/react-breadcrumb.api.md index 7b68019e883116..f08607c6ae9eb1 100644 --- a/packages/react-components/react-breadcrumb/etc/react-breadcrumb.api.md +++ b/packages/react-components/react-breadcrumb/etc/react-breadcrumb.api.md @@ -151,6 +151,12 @@ export const renderBreadcrumbItem_unstable: (state: BreadcrumbItemState) => JSX. // @public export const renderBreadcrumbLink_unstable: (state: BreadcrumbLinkState) => JSX.Element; +// @public (undocumented) +export const truncateBreadcrumbLongName: (content: string, maxLength?: number | undefined) => string; + +// @public (undocumented) +export const truncateBreadcrumLongTooltip: (content: string, maxLength?: number | undefined) => string; + // @public export const useBreadcrumb_unstable: (props: BreadcrumbProps, ref: React_2.Ref) => BreadcrumbState; diff --git a/packages/react-components/react-breadcrumb/src/index.ts b/packages/react-components/react-breadcrumb/src/index.ts index 9aad9d3e51deaf..0cf53cf84aa0c6 100644 --- a/packages/react-components/react-breadcrumb/src/index.ts +++ b/packages/react-components/react-breadcrumb/src/index.ts @@ -22,7 +22,7 @@ export { useBreadcrumbItem_unstable, } from './BreadcrumbItem'; export type { BreadcrumbItemProps, BreadcrumbItemSlots, BreadcrumbItemState } from './BreadcrumbItem'; -export { partitionBreadcrumbItems } from './utils/index'; +export { partitionBreadcrumbItems, truncateBreadcrumbLongName, truncateBreadcrumLongTooltip } from './utils/index'; export type { PartitionBreadcrumbItemsOptions, PartitionBreadcrumbItems } from './utils/index'; export { BreadcrumbButton, diff --git a/packages/react-components/react-breadcrumb/src/utils/index.ts b/packages/react-components/react-breadcrumb/src/utils/index.ts index 2dd789f3fdc64f..6cb6ccdd99efe7 100644 --- a/packages/react-components/react-breadcrumb/src/utils/index.ts +++ b/packages/react-components/react-breadcrumb/src/utils/index.ts @@ -1,2 +1,3 @@ export { partitionBreadcrumbItems } from './partitionBreadcrumbItems'; export type { PartitionBreadcrumbItems, PartitionBreadcrumbItemsOptions } from './partitionBreadcrumbItems'; +export { truncateBreadcrumbLongName, truncateBreadcrumLongTooltip } from './truncateBreadcrumb'; diff --git a/packages/react-components/react-breadcrumb/src/utils/truncateBreadcrumb.test.ts b/packages/react-components/react-breadcrumb/src/utils/truncateBreadcrumb.test.ts new file mode 100644 index 00000000000000..cefb43c8096c82 --- /dev/null +++ b/packages/react-components/react-breadcrumb/src/utils/truncateBreadcrumb.test.ts @@ -0,0 +1,44 @@ +import { truncateBreadcrumbLongName, truncateBreadcrumLongTooltip } from './truncateBreadcrumb'; + +describe('truncate method', () => { + it('returns empty string when name is empty', () => { + expect(truncateBreadcrumbLongName('')).toEqual(''); + }); + it('returns the same string when name is less than 30 characters', () => { + expect(truncateBreadcrumbLongName('Regular name')).toEqual('Regular name'); + }); + it('truncates name correctly when length is longer than 30 characters', () => { + expect(truncateBreadcrumbLongName('Name which is longer than 30 characters')).toEqual( + 'Name which is longer than 30 c...', + ); + }); + it('returns the same string when name is equal to 30 characters', () => { + expect(truncateBreadcrumbLongName('Name which equal 30 characters')).toEqual('Name which equal 30 characters'); + }); + it('truncates name correctly with custom length', () => { + expect(truncateBreadcrumbLongName('Name which is longer than 10 characters', 10)).toEqual('Name which...'); + }); + it('truncates RTL name correctly with custom length', () => { + expect(truncateBreadcrumbLongName('رحیمی خسرو رحیمی', 10)).toEqual('رحیمی خسرو...'); + }); + it('returns the same content for regular tooltips', () => { + expect(truncateBreadcrumLongTooltip('Just a tooltip')).toEqual('Just a tooltip'); + }); + it('truncates long tooltip correctly', () => { + const longTooltipContent = + "Super long tooltip which is longer than 80 characters. Don't think about what you want to be, but what you want to do."; + expect(truncateBreadcrumLongTooltip(longTooltipContent)).toEqual( + "Super long tooltip which is longer than 80 characters. Don't think about what yo...", + ); + expect(truncateBreadcrumLongTooltip(longTooltipContent, 100)).toEqual( + "Super long tooltip which is longer than 80 characters. Don't think about what you want to be, but wh...", + ); + }); + it('truncates long tooltip correctly with custom length', () => { + const longTooltipContent = + "Super long tooltip which is longer than 80 characters. Don't think about what you want to be, but what you want to do."; + expect(truncateBreadcrumLongTooltip(longTooltipContent, 100)).toEqual( + "Super long tooltip which is longer than 80 characters. Don't think about what you want to be, but wh...", + ); + }); +}); diff --git a/packages/react-components/react-breadcrumb/src/utils/truncateBreadcrumb.ts b/packages/react-components/react-breadcrumb/src/utils/truncateBreadcrumb.ts new file mode 100644 index 00000000000000..6e8e065eb91e43 --- /dev/null +++ b/packages/react-components/react-breadcrumb/src/utils/truncateBreadcrumb.ts @@ -0,0 +1,16 @@ +const MAX_NAME_LENGTH = 30; +const MAX_TOOLTIP_LENGTH = 80; + +const truncateBreadcrumb = (content: string, maxLength: number): string => { + return content.length > maxLength ? content.trim().slice(0, maxLength).concat('...') : content; +}; + +export const truncateBreadcrumbLongName = (content: string, maxLength?: number): string => { + const truncateLength = maxLength || MAX_NAME_LENGTH; + return truncateBreadcrumb(content, truncateLength); +}; + +export const truncateBreadcrumLongTooltip = (content: string, maxLength?: number): string => { + const truncateLength = maxLength || MAX_TOOLTIP_LENGTH; + return truncateBreadcrumb(content, truncateLength); +}; diff --git a/packages/react-components/react-breadcrumb/stories/BreadcrumbItem/BreadcrumbItemDefault.stories.tsx b/packages/react-components/react-breadcrumb/stories/BreadcrumbItem/BreadcrumbItemDefault.stories.tsx index b30a603a7bcbd4..1345e6ccbd3c06 100644 --- a/packages/react-components/react-breadcrumb/stories/BreadcrumbItem/BreadcrumbItemDefault.stories.tsx +++ b/packages/react-components/react-breadcrumb/stories/BreadcrumbItem/BreadcrumbItemDefault.stories.tsx @@ -1,15 +1,103 @@ import * as React from 'react'; -import { Breadcrumb, BreadcrumbItem, BreadcrumbDivider } from '@fluentui/react-breadcrumb'; -import { ArrowRight16Filled } from '@fluentui/react-icons'; - -export const Default = () => ( - - Item with custom divider - - - - Item - - Item - -); +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbDivider, + BreadcrumbProps, + partitionBreadcrumbItems, + BreadcrumbButton, + truncateBreadcrumbLongName, + truncateBreadcrumLongTooltip, +} from '@fluentui/react-breadcrumb'; +import type { PartitionBreadcrumbItems } from '@fluentui/react-breadcrumb'; +import { ArrowRight16Filled, MoreHorizontalRegular, MoreHorizontalFilled, bundleIcon } from '@fluentui/react-icons'; +import { Tooltip } from '@fluentui/react-components'; + +const MoreHorizontal = bundleIcon(MoreHorizontalFilled, MoreHorizontalRegular); + +type Item = { + key: number; + value: string; +}; +const items: Item[] = [ + { + key: 0, + value: 'Item 1', + }, + { + key: 1, + value: 'Item 2', + }, + { + key: 2, + value: 'Item 3', + }, + { + key: 3, + value: 'Item 4', + }, + { + key: 4, + value: 'Item 5 which is longer than 30 characters', + }, + { + key: 5, + value: "Item 6 is long even for tooltip. Don't think about what you want to be, but what you want to do.", + }, +]; + +function renderItem(item: Item, size: BreadcrumbProps['size']) { + const isLastItem = items.length - 1 === item.key; + return ( + + + {truncateBreadcrumbLongName(item.value)} + + {!isLastItem && } + + ); +} + +export const Default = () => { + const { startDisplayedItems, overflowItems, endDisplayedItems }: PartitionBreadcrumbItems = + partitionBreadcrumbItems({ + items, + maxDisplayedItems: 3, + }); + return ( + <> + + {startDisplayedItems.map(item => renderItem(item, 'medium'))} + {overflowItems && ( + + + } aria-label={`more items`} /> + + + )} + {endDisplayedItems && endDisplayedItems.map(item => renderItem(item, 'medium'))} + + + Item with custom divider + + + + Item + + Item + + + ); +}; + +const getTooltipContent = (breadcrumbItems: readonly Item[]) => { + return breadcrumbItems.reduce((acc, initialValue, idx, arr) => { + return ( +
+ {acc} + {arr[0].value !== initialValue.value && } + {initialValue.value} +
+ ); + },
); +};