Skip to content
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

feat(react-tags): add overflow story #28012

Merged
merged 10 commits into from
Jun 16, 2023
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
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