Skip to content

Commit

Permalink
feat(react-tag-picker): adds TagPickerOptionGroup component (microsof…
Browse files Browse the repository at this point in the history
  • Loading branch information
bsunderhus authored Apr 3, 2024
1 parent fe01c03 commit fd38bbc
Show file tree
Hide file tree
Showing 15 changed files with 240 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "feat: adds TagPickerOptionGroup component",
"packageName": "@fluentui/react-tag-picker-preview",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import type { ExtractSlotProps } from '@fluentui/react-utilities';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import type { Listbox } from '@fluentui/react-combobox';
import type { ListboxContextValue } from '@fluentui/react-combobox';
import { OptionGroupProps } from '@fluentui/react-combobox';
import { OptionGroupSlots } from '@fluentui/react-combobox';
import { OptionGroupState } from '@fluentui/react-combobox';
import { OptionSlots } from '@fluentui/react-combobox';
import { OptionState } from '@fluentui/react-combobox';
import { PortalProps } from '@fluentui/react-portal';
Expand Down Expand Up @@ -53,11 +56,14 @@ export function renderTagPickerGroup_unstable(state: TagPickerGroupState, contex
export const renderTagPickerInput_unstable: (state: TagPickerInputState) => JSX.Element;

// @public
export const renderTagPickerList_unstable: (state: TagPickerListState) => JSX.Element | null;
export const renderTagPickerList_unstable: (state: TagPickerListState) => JSX.Element;

// @public
export const renderTagPickerOption_unstable: (state: TagPickerOptionState) => JSX.Element;

// @public
export const renderTagPickerOptionGroup: (state: TagPickerOptionGroupState) => JSX.Element;

// @public
export const TagPicker: React_2.FC<TagPickerProps>;

Expand Down Expand Up @@ -163,6 +169,21 @@ export const TagPickerOption: ForwardRefComponent<TagPickerOptionProps>;
// @public (undocumented)
export const tagPickerOptionClassNames: SlotClassNames<TagPickerOptionSlots>;

// @public
export const TagPickerOptionGroup: ForwardRefComponent<TagPickerOptionGroupProps>;

// @public (undocumented)
export const tagPickerOptionGroupClassNames: SlotClassNames<TagPickerOptionGroupSlots>;

// @public
export type TagPickerOptionGroupProps = OptionGroupProps;

// @public (undocumented)
export type TagPickerOptionGroupSlots = OptionGroupSlots;

// @public
export type TagPickerOptionGroupState = OptionGroupState;

// @public
export type TagPickerOptionProps = ComponentProps<TagPickerOptionSlots> & {
children: React_2.ReactNode;
Expand Down Expand Up @@ -231,6 +252,12 @@ export const useTagPickerListStyles_unstable: (state: TagPickerListState) => Tag
// @public
export const useTagPickerOption_unstable: (props: TagPickerOptionProps, ref: React_2.Ref<HTMLDivElement>) => TagPickerOptionState;

// @public
export const useTagPickerOptionGroup: (props: TagPickerOptionGroupProps, ref: React_2.Ref<HTMLDivElement>) => TagPickerOptionGroupState;

// @public
export const useTagPickerOptionGroupStyles: (state: TagPickerOptionGroupState) => TagPickerOptionGroupState;

// @public
export const useTagPickerOptionStyles_unstable: (state: TagPickerOptionState) => TagPickerOptionState;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components/TagPickerOptionGroup/index';
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@

import { assertSlots } from '@fluentui/react-utilities';
import type { TagPickerListState, TagPickerListSlots } from './TagPickerList.types';
import * as React from 'react';

/**
* Render the final JSX of TagPickerList
*/
export const renderTagPickerList_unstable = (state: TagPickerListState) => {
assertSlots<TagPickerListSlots>(state);
if (React.Children.count(state.root.children) === 0) {
return null;
}
return <state.root />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import { isConformant } from '../../testing/isConformant';
import { TagPickerOptionGroup } from './TagPickerOptionGroup';

describe('TagPickerOptionGroup', () => {
isConformant({
Component: TagPickerOptionGroup,
displayName: 'TagPickerOptionGroup',
requiredProps: {
label: 'label',
},
});

// TODO add more tests here, and create visual regression tests in /apps/vr-tests

it('renders a default state', () => {
const result = render(<TagPickerOptionGroup>Default TagPickerOptionGroup</TagPickerOptionGroup>);
expect(result.container).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import { useTagPickerOptionGroup } from './useTagPickerOptionGroup';
import { renderTagPickerOptionGroup } from './renderTagPickerOptionGroup';
import { useTagPickerOptionGroupStyles } from './useTagPickerOptionGroupStyles.styles';
import type { TagPickerOptionGroupProps } from './TagPickerOptionGroup.types';

/**
* TagPickerOptionGroup component - TODO: add more docs
*/
export const TagPickerOptionGroup: ForwardRefComponent<TagPickerOptionGroupProps> = React.forwardRef((props, ref) => {
const state = useTagPickerOptionGroup(props, ref);

useTagPickerOptionGroupStyles(state);
return renderTagPickerOptionGroup(state);
});

TagPickerOptionGroup.displayName = 'TagPickerOptionGroup';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { OptionGroupProps, OptionGroupSlots, OptionGroupState } from '@fluentui/react-combobox';

export type TagPickerOptionGroupSlots = OptionGroupSlots;

/**
* TagPickerOptionGroup Props
*/
export type TagPickerOptionGroupProps = OptionGroupProps;

/**
* State used in rendering TagPickerOptionGroup
*/
export type TagPickerOptionGroupState = OptionGroupState;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`TagPickerOptionGroup renders a default state 1`] = `
<div>
<div
class="fui-TagPickerOptionGroup fui-OptionGroup"
role="group"
>
Default TagPickerOptionGroup
</div>
</div>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './TagPickerOptionGroup';
export * from './TagPickerOptionGroup.types';
export * from './renderTagPickerOptionGroup';
export * from './useTagPickerOptionGroup';
export * from './useTagPickerOptionGroupStyles.styles';
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/** @jsxRuntime automatic */
/** @jsxImportSource @fluentui/react-jsx-runtime */

import type { TagPickerOptionGroupState } from './TagPickerOptionGroup.types';
import { renderOptionGroup_unstable } from '@fluentui/react-combobox';

/**
* Render the final JSX of TagPickerOptionGroup
*/
export const renderTagPickerOptionGroup: (state: TagPickerOptionGroupState) => JSX.Element = renderOptionGroup_unstable;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from 'react';
import type { TagPickerOptionGroupProps, TagPickerOptionGroupState } from './TagPickerOptionGroup.types';
import { useOptionGroup_unstable } from '@fluentui/react-combobox';

/**
* Create the state required to render TagPickerOptionGroup.
*
* The returned state can be modified with hooks such as useTagPickerOptionGroupStyles_unstable,
* before being passed to renderTagPickerOptionGroup_unstable.
*
* @param props - props from this instance of TagPickerOptionGroup
* @param ref - reference to root HTMLDivElement of TagPickerOptionGroup
*/
export const useTagPickerOptionGroup: (
props: TagPickerOptionGroupProps,
ref: React.Ref<HTMLDivElement>,
) => TagPickerOptionGroupState = useOptionGroup_unstable;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { mergeClasses } from '@griffel/react';
import type { SlotClassNames } from '@fluentui/react-utilities';
import type { TagPickerOptionGroupSlots, TagPickerOptionGroupState } from './TagPickerOptionGroup.types';
import { useOptionGroupStyles_unstable } from '@fluentui/react-combobox';

export const tagPickerOptionGroupClassNames: SlotClassNames<TagPickerOptionGroupSlots> = {
root: 'fui-TagPickerOptionGroup',
label: 'fui-TagPickerOptionGroup__label',
};

/**
* Apply styling to the TagPickerOptionGroup slots based on the state
*/
export const useTagPickerOptionGroupStyles = (state: TagPickerOptionGroupState): TagPickerOptionGroupState => {
useOptionGroupStyles_unstable(state);
state.root.className = mergeClasses(tagPickerOptionGroupClassNames.root, state.root.className);

if (state.label) {
state.label.className = mergeClasses(tagPickerOptionGroupClassNames.label, state.label.className);
}

return state;
};
13 changes: 13 additions & 0 deletions packages/react-components/react-tag-picker-preview/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,16 @@ export {
useTagPickerGroup_unstable,
} from './TagPickerGroup';
export type { TagPickerGroupProps, TagPickerGroupSlots, TagPickerGroupState } from './TagPickerGroup';

export {
TagPickerOptionGroup,
tagPickerOptionGroupClassNames,
renderTagPickerOptionGroup,
useTagPickerOptionGroupStyles,
useTagPickerOptionGroup,
} from './TagPickerOptionGroup';
export type {
TagPickerOptionGroupProps,
TagPickerOptionGroupSlots,
TagPickerOptionGroupState,
} from './TagPickerOptionGroup';
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import * as React from 'react';
import {
TagPicker,
TagPickerList,
TagPickerInput,
TagPickerControl,
TagPickerProps,
TagPickerOption,
TagPickerGroup,
TagPickerOptionGroup,
} from '@fluentui/react-tag-picker-preview';
import { Tag, Avatar } from '@fluentui/react-components';

const managers = ['John Doe', 'Jane Doe', 'Max Mustermann', 'Erika Mustermann'];
const devs = ['Pierre Dupont', 'Amelie Dupont', 'Mario Rossi', 'Maria Rossi'];

export const Grouped = () => {
const [selectedOptions, setSelectedOptions] = React.useState<string[]>([]);
const onOptionSelect: TagPickerProps['onOptionSelect'] = (e, data) => {
setSelectedOptions(data.selectedOptions);
};
const unSelectedManagers = managers.filter(option => !selectedOptions.includes(option));
const unSelectedDevs = devs.filter(option => !selectedOptions.includes(option));

return (
<div style={{ maxWidth: 400 }}>
<TagPicker onOptionSelect={onOptionSelect} selectedOptions={selectedOptions}>
<TagPickerControl>
<TagPickerGroup>
{selectedOptions.map(option => (
<Tag key={option} shape="rounded" media={<Avatar name={option} color="colorful" />} value={option}>
{option}
</Tag>
))}
</TagPickerGroup>
<TagPickerInput />
</TagPickerControl>
<TagPickerList>
{unSelectedManagers.length > 0 && (
<TagPickerOptionGroup label="Managers">
{unSelectedManagers.map(option => (
<TagPickerOption
secondaryContent="Microsoft FTE"
media={<Avatar name={option} color="colorful" />}
value={option}
key={option}
>
{option}
</TagPickerOption>
))}
</TagPickerOptionGroup>
)}
{unSelectedDevs.length > 0 && (
<TagPickerOptionGroup label="Devs">
{unSelectedDevs.map(option => (
<TagPickerOption
secondaryContent="Microsoft FTE"
media={<Avatar name={option} color="colorful" />}
value={option}
key={option}
>
{option}
</TagPickerOption>
))}
</TagPickerOptionGroup>
)}
</TagPickerList>
</TagPicker>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export { Appearance } from './TagPickerAppearance.stories';
export { Disabled } from './TagPickerDisabled.stories';
export { ExpandIcon } from './TagPickerExpandIcon.stories';
export { SecondaryAction } from './TagPickerSecondaryAction.stories';
export { Grouped } from './TagPickerGrouped.stories';

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

0 comments on commit fd38bbc

Please sign in to comment.