Skip to content

Commit

Permalink
feat(react-tags): add overflow story (#28012)
Browse files Browse the repository at this point in the history
* wip

* nowrap

* update

* update

* fix comment

* type update in test

* Update packages/react-components/react-tags/stories/TagGroup/TagGroupOverflow.stories.tsx

Co-authored-by: ling1726 <[email protected]>

* update spacing according to design

* remove tag from doc

---------

Co-authored-by: ling1726 <[email protected]>
  • Loading branch information
YuanboXue-Amber and ling1726 authored Jun 16, 2023
1 parent 89f6b7c commit 676dc58
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 3 deletions.
2 changes: 1 addition & 1 deletion packages/react-components/react-tags/etc/react-tags.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export type TagProps<Value = string> = ComponentProps<Partial<TagSlots>> & {

// @public (undocumented)
export type TagSlots = {
root: NonNullable<Slot<'button'>>;
root: NonNullable<Slot<'button', 'span'>>;
media?: Slot<'span'>;
icon?: Slot<'span'>;
primaryText: Slot<'span'>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const requiredProps: TagProps = {
};

describe('Tag', () => {
isConformant({
isConformant<TagProps>({
Component: Tag,
displayName: 'Tag',
requiredProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export type TagContextValues = {
};

export type TagSlots = {
root: NonNullable<Slot<'button'>>;
root: NonNullable<Slot<'button', 'span'>>;

/**
* Slot for an icon or other visual element
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const useTagBaseStyles = makeStyles({
...typographyStyles.body1,
paddingLeft: tokens.spacingHorizontalXXS,
paddingRight: tokens.spacingHorizontalXXS,
whiteSpace: 'nowrap',
},
primaryTextWithSecondaryText: {
...shorthands.gridArea('primary'),
Expand All @@ -47,6 +48,7 @@ export const useTagBaseStyles = makeStyles({
paddingLeft: tokens.spacingHorizontalXXS,
paddingRight: tokens.spacingHorizontalXXS,
...typographyStyles.caption2,
whiteSpace: 'nowrap',
},
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import * as React from 'react';
import { TagGroup, Tag, TagProps } from '@fluentui/react-tags';
import {
makeStyles,
shorthands,
Menu,
MenuItem,
MenuList,
MenuPopover,
MenuTrigger,
useIsOverflowItemVisible,
useOverflowMenu,
Overflow,
OverflowItem,
Avatar,
tokens,
} from '@fluentui/react-components';

const names = [
'Johnie McConnell',
'Allan Munger',
'Erik Nason',
'Kristin Patterson',
'Daisy Phillips',
'Carole Poland',
'Carlos Slattery',
'Robert Tolbert',
'Kevin Sturgis',
'Charlotte Waltson',
'Elliot Woodward',
];
const defaultItems: TagProps[] = names.map(name => ({
value: name.replace(' ', '_'),
children: name,
media: (
<Avatar
name={name}
badge={{
status: 'available',
}}
/>
),
secondaryText: 'Available',
}));

//----- OverflowMenuItem -----//

type OverflowMenuItemProps = {
tag: TagProps;
};

const useMenuItemStyles = makeStyles({
menuItem: shorthands.padding(tokens.spacingVerticalSNudge, tokens.spacingHorizontalXS),
tag: {
backgroundColor: 'transparent',
...shorthands.borderColor('transparent'),
},
});

/**
* A menu item for an overflow menu that only displays when the tab is not visible
*/
const OverflowMenuItem = (props: OverflowMenuItemProps) => {
const { tag } = props;
const isVisible = useIsOverflowItemVisible(tag.value!);

const styles = useMenuItemStyles();

if (isVisible) {
return null;
}

return (
<MenuItem key={tag.value} className={styles.menuItem}>
<Tag {...tag} as="span" className={styles.tag} />
</MenuItem>
);
};

//----- OverflowMenu -----//

/**
* A menu for viewing tags that have overflowed and are not visible.
*/
const OverflowMenu = () => {
const { ref, isOverflowing, overflowCount } = useOverflowMenu<HTMLButtonElement>();

if (!isOverflowing) {
return null;
}

return (
<Menu>
<MenuTrigger disableButtonEnhancement>
<Tag ref={ref} aria-label={`${overflowCount} more tags`}>{`+${overflowCount}`}</Tag>
</MenuTrigger>
<MenuPopover>
<MenuList>
{defaultItems.map(item => (
<OverflowMenuItem key={item.value} tag={item} />
))}
</MenuList>
</MenuPopover>
</Menu>
);
};

//----- Stories -----//

const useStyles = makeStyles({
container: {
...shorthands.overflow('hidden'),
...shorthands.padding('5px'),
zIndex: 0, // stop the browser resize handle from piercing the overflow menu
height: 'fit-content',
minWidth: '150px',
resize: 'horizontal',
width: '100%',
},
tagGroup: {
display: 'flex', // TagGroup is inline-flex by default, but we want it to be same width as the container
},
});

export const WithOverflow = () => {
const styles = useStyles();

return (
<div className={styles.container}>
<Overflow minimumVisible={2} padding={30}>
<TagGroup className={styles.tagGroup}>
{defaultItems.map(item => (
<OverflowItem key={item.value} id={item.value!}>
<Tag key={item.value} {...item} />
</OverflowItem>
))}
<OverflowMenu />
</TagGroup>
</Overflow>
</div>
);
};

WithOverflow.storyName = 'With Overflow';
WithOverflow.parameters = {
docs: {
description: {
story: 'A TagGroup can support overflow by using Overflow and OverflowItem.',
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import bestPracticesMd from './TagGroupBestPractices.md';
export { Default } from './TagGroupDefault.stories';
export { Dismiss } from './TagGroupDismiss.stories';
export { Sizes } from './TagGroupSizes.stories';
export { WithOverflow } from './TagGroupOverflow.stories';

export default {
title: 'Preview Components/Tag/TagGroup',
Expand Down

0 comments on commit 676dc58

Please sign in to comment.