Skip to content

Commit 6231f45

Browse files
mainframevclaude
andcommitted
feat(react-headless-components-preview): add headless Tag, InteractionTag, InteractionTagPrimary, InteractionTagSecondary, TagGroup
Add five headless Tag-family components that delegate behaviour to the canonical base hooks in @fluentui/react-tags. The headless flavour: - Strips icons - InteractionTagSecondary does NOT inject a default DismissRegular icon (consumers supply children). - Strips Tabster - the headless useTagGroup omits the canonical arrowNavigationProps / onAfterTagDismiss options, so the group renders without data-tabster and without post-dismiss focus restoration; consumers wire those behaviours up themselves. - Strips styles - no Griffel hooks; the public root slot is decorated with data-disabled / data-dismissible / data-selected / data-has-secondary-action attributes for downstream styling. Each component exposes the standard {Component, useComponent, renderComponent, ComponentProps, ComponentState, ComponentSlots} surface plus context-value helpers where the canonical render contract requires them. Each is reachable through a kebab-case subpath export (./tag, ./interaction-tag, ./interaction-tag-primary, ./interaction-tag-secondary, ./tag-group). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9a05d90 commit 6231f45

41 files changed

Lines changed: 968 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
## API Report File for "@fluentui/react-headless-components-preview"
2+
3+
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
4+
5+
```ts
6+
7+
import type { ForwardRefComponent } from '@fluentui/react-utilities';
8+
import type { InteractionTagPrimaryBaseProps } from '@fluentui/react-tags';
9+
import { InteractionTagPrimaryBaseState } from '@fluentui/react-tags';
10+
import { InteractionTagPrimaryContextValues as InteractionTagPrimaryContextValues_2 } from '@fluentui/react-tags';
11+
import type { InteractionTagPrimarySlots as InteractionTagPrimarySlots_2 } from '@fluentui/react-tags';
12+
import { JSXElement } from '@fluentui/react-utilities';
13+
import * as React_2 from 'react';
14+
15+
// @public
16+
export const InteractionTagPrimary: ForwardRefComponent<InteractionTagPrimaryProps>;
17+
18+
// @public (undocumented)
19+
export type InteractionTagPrimaryContextValues = InteractionTagPrimaryContextValues_2;
20+
21+
// @public (undocumented)
22+
export type InteractionTagPrimaryProps = InteractionTagPrimaryBaseProps;
23+
24+
// @public (undocumented)
25+
export type InteractionTagPrimarySlots = InteractionTagPrimarySlots_2;
26+
27+
// @public (undocumented)
28+
export type InteractionTagPrimaryState = InteractionTagPrimaryBaseState & {
29+
root: {
30+
'data-disabled'?: string;
31+
'data-selected'?: string;
32+
'data-has-secondary-action'?: string;
33+
};
34+
};
35+
36+
// @public
37+
export const renderInteractionTagPrimary: (state: InteractionTagPrimaryBaseState, contextValues: InteractionTagPrimaryContextValues_2) => JSXElement;
38+
39+
// @public
40+
export const useInteractionTagPrimary: (props: InteractionTagPrimaryProps, ref: React_2.Ref<HTMLButtonElement>) => InteractionTagPrimaryState;
41+
42+
// @public
43+
export const useInteractionTagPrimaryContextValues: (_state: InteractionTagPrimaryState) => InteractionTagPrimaryContextValues;
44+
45+
// (No @packageDocumentation comment for this package)
46+
47+
```
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
## API Report File for "@fluentui/react-headless-components-preview"
2+
3+
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
4+
5+
```ts
6+
7+
import type { ForwardRefComponent } from '@fluentui/react-utilities';
8+
import type { InteractionTagSecondaryBaseProps } from '@fluentui/react-tags';
9+
import { InteractionTagSecondaryBaseState } from '@fluentui/react-tags';
10+
import type { InteractionTagSecondarySlots as InteractionTagSecondarySlots_2 } from '@fluentui/react-tags';
11+
import { JSXElement } from '@fluentui/react-utilities';
12+
import type * as React_2 from 'react';
13+
14+
// @public
15+
export const InteractionTagSecondary: ForwardRefComponent<InteractionTagSecondaryProps>;
16+
17+
// @public (undocumented)
18+
export type InteractionTagSecondaryProps = InteractionTagSecondaryBaseProps;
19+
20+
// @public (undocumented)
21+
export type InteractionTagSecondarySlots = InteractionTagSecondarySlots_2;
22+
23+
// @public (undocumented)
24+
export type InteractionTagSecondaryState = InteractionTagSecondaryBaseState & {
25+
root: {
26+
'data-disabled'?: string;
27+
'data-selected'?: string;
28+
};
29+
};
30+
31+
// @public
32+
export const renderInteractionTagSecondary: (state: InteractionTagSecondaryBaseState) => JSXElement;
33+
34+
// @public
35+
export const useInteractionTagSecondary: (props: InteractionTagSecondaryProps, ref: React_2.Ref<HTMLButtonElement>) => InteractionTagSecondaryState;
36+
37+
// (No @packageDocumentation comment for this package)
38+
39+
```
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
## API Report File for "@fluentui/react-headless-components-preview"
2+
3+
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
4+
5+
```ts
6+
7+
import type { ForwardRefComponent } from '@fluentui/react-utilities';
8+
import type { InteractionTagBaseProps } from '@fluentui/react-tags';
9+
import { InteractionTagBaseState } from '@fluentui/react-tags';
10+
import { InteractionTagContextValue } from '@fluentui/react-tags';
11+
import type { InteractionTagSlots as InteractionTagSlots_2 } from '@fluentui/react-tags';
12+
import { JSXElement } from '@fluentui/react-utilities';
13+
import * as React_2 from 'react';
14+
15+
// @public
16+
export const InteractionTag: ForwardRefComponent<InteractionTagProps>;
17+
18+
// @public (undocumented)
19+
export type InteractionTagContextValues = {
20+
interactionTag: InteractionTagContextValue;
21+
};
22+
23+
// @public (undocumented)
24+
export type InteractionTagProps = InteractionTagBaseProps;
25+
26+
// @public (undocumented)
27+
export type InteractionTagSlots = InteractionTagSlots_2;
28+
29+
// @public (undocumented)
30+
export type InteractionTagState = InteractionTagBaseState & {
31+
root: {
32+
'data-disabled'?: string;
33+
'data-selected'?: string;
34+
};
35+
};
36+
37+
// @public
38+
export const renderInteractionTag: (state: InteractionTagBaseState, contextValues: {
39+
interactionTag: InteractionTagContextValue;
40+
}) => JSXElement;
41+
42+
// @public
43+
export const useInteractionTag: (props: InteractionTagProps, ref: React_2.Ref<HTMLDivElement>) => InteractionTagState;
44+
45+
// @public
46+
export const useInteractionTagContextValues: (state: InteractionTagState) => InteractionTagContextValues;
47+
48+
// (No @packageDocumentation comment for this package)
49+
50+
```
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
## API Report File for "@fluentui/react-headless-components-preview"
2+
3+
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
4+
5+
```ts
6+
7+
import type { ForwardRefComponent } from '@fluentui/react-utilities';
8+
import { JSXElement } from '@fluentui/react-utilities';
9+
import * as React_2 from 'react';
10+
import type { TagGroupBaseProps } from '@fluentui/react-tags';
11+
import { TagGroupBaseState } from '@fluentui/react-tags';
12+
import type { TagGroupContextValue } from '@fluentui/react-tags';
13+
import { TagGroupContextValues as TagGroupContextValues_2 } from '@fluentui/react-tags';
14+
import type { TagGroupSlots as TagGroupSlots_2 } from '@fluentui/react-tags';
15+
16+
// @public
17+
export const renderTagGroup: (state: TagGroupBaseState, contextValue: TagGroupContextValues_2) => JSXElement;
18+
19+
// @public
20+
export const TagGroup: ForwardRefComponent<TagGroupProps>;
21+
22+
// @public (undocumented)
23+
export type TagGroupContextValues = {
24+
tagGroup: TagGroupContextValue;
25+
};
26+
27+
// @public (undocumented)
28+
export type TagGroupProps = TagGroupBaseProps;
29+
30+
// @public (undocumented)
31+
export type TagGroupSlots = TagGroupSlots_2;
32+
33+
// @public (undocumented)
34+
export type TagGroupState = TagGroupBaseState & {
35+
root: {
36+
'data-disabled'?: string;
37+
'data-dismissible'?: string;
38+
};
39+
};
40+
41+
// @public
42+
export const useTagGroup: (props: TagGroupProps, ref: React_2.Ref<HTMLDivElement>) => TagGroupState;
43+
44+
// @public
45+
export const useTagGroupContextValues: (state: TagGroupState) => TagGroupContextValues;
46+
47+
// (No @packageDocumentation comment for this package)
48+
49+
```
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
## API Report File for "@fluentui/react-headless-components-preview"
2+
3+
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
4+
5+
```ts
6+
7+
import type { ForwardRefComponent } from '@fluentui/react-utilities';
8+
import { JSXElement } from '@fluentui/react-utilities';
9+
import * as React_2 from 'react';
10+
import type { TagBaseProps } from '@fluentui/react-tags';
11+
import { TagBaseState } from '@fluentui/react-tags';
12+
import { TagContextValues as TagContextValues_2 } from '@fluentui/react-tags';
13+
import type { TagSlots as TagSlots_2 } from '@fluentui/react-tags';
14+
15+
// @public
16+
export const renderTag: (state: TagBaseState, contextValues: TagContextValues_2) => JSXElement;
17+
18+
// @public
19+
export const Tag: ForwardRefComponent<TagProps>;
20+
21+
// @public (undocumented)
22+
export type TagContextValues = TagContextValues_2;
23+
24+
// @public (undocumented)
25+
export type TagProps = TagBaseProps;
26+
27+
// @public (undocumented)
28+
export type TagSlots = TagSlots_2;
29+
30+
// @public (undocumented)
31+
export type TagState = TagBaseState & {
32+
root: {
33+
'data-disabled'?: string;
34+
'data-dismissible'?: string;
35+
'data-selected'?: string;
36+
};
37+
};
38+
39+
// @public
40+
export const useTag: (props: TagProps, ref: React_2.Ref<HTMLSpanElement | HTMLButtonElement>) => TagState;
41+
42+
// @public
43+
export const useTagContextValues: (_state: TagState) => TagContextValues;
44+
45+
// (No @packageDocumentation comment for this package)
46+
47+
```

packages/react-components/react-headless-components-preview/library/package.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,24 @@
160160
"import": "./lib/input.js",
161161
"require": "./lib-commonjs/input.js"
162162
},
163+
"./interaction-tag": {
164+
"types": "./dist/interaction-tag.d.ts",
165+
"node": "./lib-commonjs/interaction-tag.js",
166+
"import": "./lib/interaction-tag.js",
167+
"require": "./lib-commonjs/interaction-tag.js"
168+
},
169+
"./interaction-tag-primary": {
170+
"types": "./dist/interaction-tag-primary.d.ts",
171+
"node": "./lib-commonjs/interaction-tag-primary.js",
172+
"import": "./lib/interaction-tag-primary.js",
173+
"require": "./lib-commonjs/interaction-tag-primary.js"
174+
},
175+
"./interaction-tag-secondary": {
176+
"types": "./dist/interaction-tag-secondary.d.ts",
177+
"node": "./lib-commonjs/interaction-tag-secondary.js",
178+
"import": "./lib/interaction-tag-secondary.js",
179+
"require": "./lib-commonjs/interaction-tag-secondary.js"
180+
},
163181
"./label": {
164182
"types": "./dist/label.d.ts",
165183
"node": "./lib-commonjs/label.js",
@@ -274,6 +292,18 @@
274292
"import": "./lib/tab-list.js",
275293
"require": "./lib-commonjs/tab-list.js"
276294
},
295+
"./tag": {
296+
"types": "./dist/tag.d.ts",
297+
"node": "./lib-commonjs/tag.js",
298+
"import": "./lib/tag.js",
299+
"require": "./lib-commonjs/tag.js"
300+
},
301+
"./tag-group": {
302+
"types": "./dist/tag-group.d.ts",
303+
"node": "./lib-commonjs/tag-group.js",
304+
"import": "./lib/tag-group.js",
305+
"require": "./lib-commonjs/tag-group.js"
306+
},
277307
"./textarea": {
278308
"types": "./dist/textarea.d.ts",
279309
"node": "./lib-commonjs/textarea.js",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import * as React from 'react';
2+
import { render } from '@testing-library/react';
3+
import { isConformant } from '../../testing/isConformant';
4+
import { InteractionTag } from './InteractionTag';
5+
import { InteractionTagPrimary } from '../InteractionTagPrimary';
6+
7+
const requiredProps = { children: <InteractionTagPrimary>tag</InteractionTagPrimary> };
8+
9+
describe('InteractionTag', () => {
10+
isConformant({
11+
Component: InteractionTag,
12+
displayName: 'InteractionTag',
13+
requiredProps,
14+
});
15+
16+
it('provides a child InteractionTagPrimary with an aria-pressed when handleTagSelect is wired', () => {
17+
const result = render(<InteractionTag {...requiredProps} />);
18+
expect(result.getByRole('button')).toBeInTheDocument();
19+
});
20+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use client';
2+
3+
import * as React from 'react';
4+
import type { ForwardRefComponent } from '@fluentui/react-utilities';
5+
6+
import type { InteractionTagProps } from './InteractionTag.types';
7+
import { useInteractionTag, useInteractionTagContextValues } from './useInteractionTag';
8+
import { renderInteractionTag } from './renderInteractionTag';
9+
10+
/**
11+
* A visual representation of an attribute that can have primary and/or secondary actions.
12+
* Composed with `InteractionTagPrimary` and optionally `InteractionTagSecondary` children.
13+
*/
14+
export const InteractionTag: ForwardRefComponent<InteractionTagProps> = React.forwardRef((props, ref) => {
15+
const state = useInteractionTag(props, ref);
16+
const contextValues = useInteractionTagContextValues(state);
17+
return renderInteractionTag(state, contextValues);
18+
});
19+
20+
InteractionTag.displayName = 'InteractionTag';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type {
2+
InteractionTagSlots as InteractionTagBaseSlots,
3+
InteractionTagBaseProps,
4+
InteractionTagBaseState,
5+
} from '@fluentui/react-tags';
6+
import type { InteractionTagContextValue } from '@fluentui/react-tags';
7+
8+
export type InteractionTagSlots = InteractionTagBaseSlots;
9+
10+
export type InteractionTagProps = InteractionTagBaseProps;
11+
12+
export type InteractionTagState = InteractionTagBaseState & {
13+
root: {
14+
/**
15+
* Data attribute set when the interaction tag is disabled.
16+
*/
17+
'data-disabled'?: string;
18+
19+
/**
20+
* Data attribute set when the interaction tag is selected.
21+
*/
22+
'data-selected'?: string;
23+
};
24+
};
25+
26+
export type InteractionTagContextValues = {
27+
interactionTag: InteractionTagContextValue;
28+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export { InteractionTag } from './InteractionTag';
2+
export { renderInteractionTag } from './renderInteractionTag';
3+
export { useInteractionTag, useInteractionTagContextValues } from './useInteractionTag';
4+
export type {
5+
InteractionTagSlots,
6+
InteractionTagProps,
7+
InteractionTagState,
8+
InteractionTagContextValues,
9+
} from './InteractionTag.types';

0 commit comments

Comments
 (0)