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): Adds disabled property to TagGroup #32317

Merged
merged 13 commits into from
Aug 19, 2024
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
emmayjiang marked this conversation as resolved.
Show resolved Hide resolved
"type": "patch",
"comment": "fix(react-tag-picker): allows TagPickerGroup to be disabled",
"packageName": "@fluentui/react-tag-picker",
"email": "jiangemma@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix(react-tags): allows TagGroup to be disabled",
"packageName": "@fluentui/react-tags",
"email": "jiangemma@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const useTagPickerGroup_unstable = (
const selectOption = useTagPickerContext_unstable(ctx => ctx.selectOption);
const size = useTagPickerContext_unstable(ctx => tagPickerSizeToTagSize(ctx.size));
const appearance = useTagPickerContext_unstable(ctx => ctx.appearance);
const disabled = useTagPickerContext_unstable(ctx => ctx.disabled);

const arrowNavigationProps = useArrowNavigationGroup({
circular: false,
Expand All @@ -37,6 +38,7 @@ export const useTagPickerGroup_unstable = (
const state = useTagGroup_unstable(
{
role: 'listbox',
disabled,
...props,
...arrowNavigationProps,
size,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export const Disabled = () => {
<TagPickerGroup>
{selectedOptions.map(option => (
<Tag
disabled
key={option}
shape="rounded"
media={<Avatar aria-hidden name={option} color="colorful" />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export type TagGroupContextValues = {
// @public
export type TagGroupProps<Value = TagValue> = ComponentProps<TagGroupSlots> & {
onDismiss?: TagDismissHandler<Value>;
disabled?: boolean;
size?: TagSize;
appearance?: TagAppearance;
dismissible?: boolean;
Expand All @@ -144,7 +145,7 @@ export type TagGroupSlots = {
};

// @public
export type TagGroupState<Value = TagValue> = ComponentState<TagGroupSlots> & Required<Pick<TagGroupProps, 'size' | 'appearance' | 'dismissible'>> & {
export type TagGroupState<Value = TagValue> = ComponentState<TagGroupSlots> & Required<Pick<TagGroupProps, 'disabled' | 'size' | 'appearance' | 'dismissible'>> & {
handleTagDismiss: TagDismissHandler<Value>;
role?: React_2.AriaRole;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ export const useInteractionTag_unstable = (
props: InteractionTagProps,
ref: React.Ref<HTMLDivElement>,
): InteractionTagState => {
const { handleTagDismiss, size: contextSize, appearance: contextAppearance } = useTagGroupContext_unstable();
const {
handleTagDismiss,
size: contextSize,
disabled: contextDisabled,
appearance: contextAppearance,
} = useTagGroupContext_unstable();

const id = useId('fui-InteractionTag-', props.id);

Expand All @@ -32,7 +37,7 @@ export const useInteractionTag_unstable = (

return {
appearance,
disabled,
disabled: contextDisabled ? true : disabled,
handleTagDismiss,
interactionTagPrimaryId,
shape,
Expand All @@ -47,6 +52,7 @@ export const useInteractionTag_unstable = (
getIntrinsicElementProps('div', {
ref,
...props,
disabled: contextDisabled ? true : disabled,
id,
}),
{ elementType: 'div' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const useTag_unstable = (props: TagProps, ref: React.Ref<HTMLSpanElement
const {
handleTagDismiss,
size: contextSize,
disabled: contextDisabled,
appearance: contextAppearance,
dismissible: contextDismissible,
role: tagGroupRole,
Expand Down Expand Up @@ -65,7 +66,7 @@ export const useTag_unstable = (props: TagProps, ref: React.Ref<HTMLSpanElement
appearance,
avatarShape: tagAvatarShapeMap[shape],
avatarSize: tagAvatarSizeMap[size],
disabled,
disabled: contextDisabled ? true : disabled,
dismissible,
shape,
size,
Expand All @@ -84,6 +85,7 @@ export const useTag_unstable = (props: TagProps, ref: React.Ref<HTMLSpanElement
ref,
role: tagGroupRole === 'listbox' ? 'option' : undefined,
...props,
disabled: contextDisabled ? true : disabled,
id,
...(dismissible && { onClick: dismissOnClick, onKeyDown: dismissOnKeyDown }),
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import '@testing-library/jest-dom';
import * as React from 'react';
import { TagGroup } from './TagGroup';
import { isConformant } from '../../testing/isConformant';
Expand Down Expand Up @@ -35,4 +36,24 @@ describe('TagGroup', () => {

expect(onDismiss).toHaveBeenCalledWith(expect.anything(), { value: '1' });
});

it('if disabled, should disable children Tags', () => {
const { getByRole } = render(
<TagGroup disabled>
<Tag value={'1'} dismissible />
</TagGroup>,
);

expect(getByRole('button')).toBeDisabled();
});

it('if disabled, should override children Tags disabled prop', () => {
const { getByRole } = render(
<TagGroup disabled>
<Tag value={'1'} disabled={false} dismissible />
</TagGroup>,
);

expect(getByRole('button')).toBeDisabled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ export type TagGroupProps<Value = TagValue> = ComponentProps<TagGroupSlots> & {
// eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback
onDismiss?: TagDismissHandler<Value>;

/**
* A TagGroup can show that it cannot be interacted with.
*
* @default false
*/
disabled?: boolean;

size?: TagSize;
appearance?: TagAppearance;
dismissible?: boolean;
Expand All @@ -30,7 +37,7 @@ export type TagGroupProps<Value = TagValue> = ComponentProps<TagGroupSlots> & {
* State used in rendering TagGroup
*/
export type TagGroupState<Value = TagValue> = ComponentState<TagGroupSlots> &
Required<Pick<TagGroupProps, 'size' | 'appearance' | 'dismissible'>> & {
Required<Pick<TagGroupProps, 'disabled' | 'size' | 'appearance' | 'dismissible'>> & {
handleTagDismiss: TagDismissHandler<Value>;
role?: React.AriaRole;
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ import { interactionTagSecondaryClassNames } from '../InteractionTagSecondary/us
* @param ref - reference to root HTMLDivElement of TagGroup
*/
export const useTagGroup_unstable = (props: TagGroupProps, ref: React.Ref<HTMLDivElement>): TagGroupState => {
const { onDismiss, size = 'medium', appearance = 'filled', dismissible = false, role = 'toolbar' } = props;
const {
onDismiss,
disabled = false,
size = 'medium',
appearance = 'filled',
dismissible = false,
role = 'toolbar',
} = props;

const innerRef = React.useRef<HTMLElement>();
const { targetDocument } = useFluent();
Expand Down Expand Up @@ -55,6 +62,7 @@ export const useTagGroup_unstable = (props: TagGroupProps, ref: React.Ref<HTMLDi
handleTagDismiss,
role,
size,
disabled,
appearance,
dismissible,
components: {
Expand All @@ -68,6 +76,7 @@ export const useTagGroup_unstable = (props: TagGroupProps, ref: React.Ref<HTMLDi
// but since it would be a breaking change to fix it, we are casting ref to it's proper type
ref: useMergedRefs(ref, innerRef) as React.Ref<HTMLDivElement>,
role,
'aria-disabled': disabled,
...arrowNavigationProps,
...props,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import * as React from 'react';
import type { TagGroupContextValues, TagGroupState } from './TagGroup.types';

export function useTagGroupContextValues_unstable(state: TagGroupState): TagGroupContextValues {
const { handleTagDismiss, size, appearance, dismissible, role } = state;
const { handleTagDismiss, size, disabled, appearance, dismissible, role } = state;
return {
tagGroup: React.useMemo(
() => ({ handleTagDismiss, size, appearance, dismissible, role }),
[handleTagDismiss, size, appearance, dismissible, role],
() => ({ handleTagDismiss, size, disabled, appearance, dismissible, role }),
[handleTagDismiss, size, disabled, appearance, dismissible, role],
),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const tagGroupContextDefaultValue: TagGroupContextValue = {
* Context shared between TagGroup and its children components
*/
export type TagGroupContextValue = Required<Pick<TagGroupState, 'handleTagDismiss' | 'size'>> &
Partial<Pick<TagGroupState, 'appearance' | 'dismissible' | 'role'>>;
Partial<Pick<TagGroupState, 'disabled' | 'appearance' | 'dismissible' | 'role'>>;

export const TagGroupContextProvider = TagGroupContext.Provider;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as React from 'react';
import { TagGroup, InteractionTag, InteractionTagPrimary, Tag, makeStyles } from '@fluentui/react-components';

const WithTags = () => (
<TagGroup disabled aria-label="Disabled tag group with Tag" role="list">
<Tag role="listitem">Tag 1</Tag>
<Tag role="listitem">Tag 2</Tag>
<Tag role="listitem">Tag 3</Tag>
</TagGroup>
);

const WithInteractionTags = () => (
<TagGroup disabled aria-label="Disabled tag group with InteractionTag">
<InteractionTag>
<InteractionTagPrimary>Tag 1</InteractionTagPrimary>
</InteractionTag>
<InteractionTag>
<InteractionTagPrimary>Tag 2</InteractionTagPrimary>
</InteractionTag>
<InteractionTag>
<InteractionTagPrimary>Tag 3</InteractionTagPrimary>
</InteractionTag>
</TagGroup>
);

const useStyles = makeStyles({
container: {
display: 'flex',
flexDirection: 'column',
rowGap: '10px',
},
});

export const Disabled = () => {
const styles = useStyles();
return (
<div className={styles.container}>
Disabled example with Tag:
<WithTags />
Disabled example with InteractionTag:
<WithInteractionTags />
</div>
);
};

Disabled.storyName = 'Disabled';
Disabled.parameters = {
docs: {
description: {
story: 'A TagGroup can be disabled. The collection of Tag/InteractionTag will also be disabled.',
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export { Default } from './TagGroupDefault.stories';
export { Dismiss } from './TagGroupDismiss.stories';
export { Sizes } from './TagGroupSizes.stories';
export { WithOverflow } from './TagGroupOverflow.stories';
export { Disabled } from './TagGroupDisabled.stories';

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