diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPickerOption/useTagPickerOptionStyles.styles.ts b/packages/react-components/react-tag-picker/library/src/components/TagPickerOption/useTagPickerOptionStyles.styles.ts
index d9ae8758f70399..c8b27a0f42eb16 100644
--- a/packages/react-components/react-tag-picker/library/src/components/TagPickerOption/useTagPickerOptionStyles.styles.ts
+++ b/packages/react-components/react-tag-picker/library/src/components/TagPickerOption/useTagPickerOptionStyles.styles.ts
@@ -2,6 +2,7 @@ import { makeStyles, mergeClasses } from '@griffel/react';
import type { SlotClassNames } from '@fluentui/react-utilities';
import type { TagPickerOptionSlots, TagPickerOptionState } from './TagPickerOption.types';
import { useOptionStyles_unstable } from '@fluentui/react-combobox';
+import { typographyStyles } from '@fluentui/react-theme';
export const tagPickerOptionClassNames: SlotClassNames = {
root: 'fui-TagPickerOption',
@@ -22,6 +23,7 @@ const useStyles = makeStyles({
secondaryContent: {
gridColumnStart: 2,
gridRowStart: 2,
+ ...typographyStyles.caption1,
},
media: {
@@ -35,6 +37,9 @@ const useStyles = makeStyles({
export const useTagPickerOptionStyles_unstable = (state: TagPickerOptionState): TagPickerOptionState => {
'use no memo';
+ const styles = useStyles();
+
+ state.root.className = mergeClasses(tagPickerOptionClassNames.root, styles.root, state.root.className);
useOptionStyles_unstable({
...state,
active: false,
@@ -43,9 +48,6 @@ export const useTagPickerOptionStyles_unstable = (state: TagPickerOptionState):
checkIcon: undefined,
selected: false,
});
- const styles = useStyles();
-
- state.root.className = mergeClasses(tagPickerOptionClassNames.root, styles.root, state.root.className);
if (state.media) {
state.media.className = mergeClasses(tagPickerOptionClassNames.media, styles.media, state.media.className);
}
diff --git a/packages/react-components/react-tag-picker/library/src/index.ts b/packages/react-components/react-tag-picker/library/src/index.ts
index 77af524786d038..68e8e6f000f3a5 100644
--- a/packages/react-components/react-tag-picker/library/src/index.ts
+++ b/packages/react-components/react-tag-picker/library/src/index.ts
@@ -71,3 +71,6 @@ export type {
} from './TagPickerOptionGroup';
export { useTagPickerFilter } from './utils/useTagPickerFilter';
+
+export { useTagPickerContext_unstable } from './contexts/TagPickerContext';
+export type { TagPickerContextValue } from './contexts/TagPickerContext';
diff --git a/packages/react-components/react-tag-picker/stories/src/TagPicker/TagPickerSingleLine.stories.tsx b/packages/react-components/react-tag-picker/stories/src/TagPicker/TagPickerSingleLine.stories.tsx
new file mode 100644
index 00000000000000..78d105c0a449e6
--- /dev/null
+++ b/packages/react-components/react-tag-picker/stories/src/TagPicker/TagPickerSingleLine.stories.tsx
@@ -0,0 +1,199 @@
+import * as React from 'react';
+import {
+ TagPicker,
+ TagPickerList,
+ TagPickerInput,
+ TagPickerControl,
+ TagPickerProps,
+ TagPickerOption,
+ TagPickerGroup,
+ makeStyles,
+ tagPickerGroupClassNames,
+ useOverflowCount,
+ TagPickerInputProps,
+ useTagPickerContext_unstable,
+ TagProps,
+ Tag,
+ Avatar,
+ Overflow,
+ OverflowItem,
+} from '@fluentui/react-components';
+import { ChevronDownRegular, ChevronUpRegular } from '@fluentui/react-icons';
+
+const useStyles = makeStyles({
+ focusedExpandIcon: { alignSelf: 'flex-end' },
+ countButton: { minWidth: 0 },
+ control: {
+ flexWrap: 'nowrap',
+ display: 'flex',
+ flexGrow: 1,
+ minWidth: 0,
+ overflow: 'hidden',
+ [`& > .${tagPickerGroupClassNames.root}`]: {
+ flexWrap: 'nowrap',
+ },
+ ':focus-within': {
+ flexWrap: 'wrap',
+ [`& > .${tagPickerGroupClassNames.root}`]: {
+ flexWrap: 'wrap',
+ },
+ },
+ },
+});
+
+const options = [
+ 'John Doe',
+ 'Jane Doe',
+ 'Max Mustermann',
+ 'Erika Mustermann',
+ 'Pierre Dupont',
+ 'Amelie Dupont',
+ 'Mario Rossi',
+ 'Maria Rossi',
+];
+
+type ExpandIconProps = { open: boolean; focus: boolean };
+
+const ExpandIcon = (props: ExpandIconProps) => {
+ const overflowCount = useOverflowCount();
+
+ if (props.open) {
+ return ;
+ }
+ if (overflowCount === 0 || props.focus) {
+ return ;
+ }
+ return null;
+};
+
+const OverFlowCountTag = (props: TagProps) => {
+ const overflowCount = useOverflowCount();
+ const styles = useStyles();
+ if (overflowCount === 0) {
+ return null;
+ }
+ return (
+
+ +{overflowCount}
+
+ );
+};
+
+type CustomTagPickerInputProps = TagPickerInputProps & { focus: boolean };
+
+const CustomTagPickerInput = React.forwardRef(
+ ({ focus, onMouseDown, placeholder, ...rest }, ref) => {
+ const overflowCount = useOverflowCount();
+ const selectedOptionsAmount = useTagPickerContext_unstable(ctx => ctx.selectedOptions.length);
+ return (
+ 0 && focus) ? placeholder : undefined}
+ />
+ );
+ },
+);
+
+export const SingleLine = () => {
+ const styles = useStyles();
+ const [open, setOpen] = React.useState(false);
+ const [selectedOptions, setSelectedOptions] = React.useState([]);
+ const inputRef = React.useRef(null);
+ const onOptionSelect: TagPickerProps['onOptionSelect'] = (e, data) => {
+ setSelectedOptions(data.selectedOptions);
+ };
+ const tagPickerOptions = options.filter(option => !selectedOptions.includes(option));
+
+ const [hasFocus, setFocus] = React.useState(false);
+
+ const handleOpenChange: TagPickerProps['onOpenChange'] = (_, data) => setOpen(data.open);
+
+ const handleFocus = () => {
+ setFocus(true);
+ };
+
+ const handleBlur = (event: React.FocusEvent) => {
+ if (event.currentTarget.contains(event.relatedTarget as Node)) {
+ return;
+ }
+ setFocus(false);
+ };
+
+ const handleOverflowCountTagMouseDown = (event: React.MouseEvent) => {
+ event.preventDefault();
+ inputRef.current?.focus();
+ };
+
+ return (
+
+ {/* 24 = min input size */}
+ {/* 30 = padding right */}
+ {/* 2 = gap between input and tags */}
+ {/* 4 = gap between tags */}
+
+ ,
+ }}
+ onFocus={handleFocus}
+ onBlur={handleBlur}
+ className={styles.control}
+ >
+
+ {selectedOptions.map(option => (
+