Skip to content

Commit 70972f1

Browse files
authored
feat(react-headless-components-preview): add Provider component (microsoft#36000)
1 parent a771e44 commit 70972f1

12 files changed

Lines changed: 165 additions & 0 deletions

File tree

packages/react-components/react-headless-components-preview/library/etc/react-headless-components-preview.api.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import type { CheckboxBaseProps } from '@fluentui/react-checkbox';
4444
import { CheckboxBaseState } from '@fluentui/react-checkbox';
4545
import type { CheckboxSlots as CheckboxSlots_2 } from '@fluentui/react-checkbox';
4646
import { ComponentProps } from '@fluentui/react-utilities';
47+
import type { ComponentState } from '@fluentui/react-utilities';
4748
import { ContextSelector } from '@fluentui/react-context-selector';
4849
import type { DividerBaseProps } from '@fluentui/react-divider';
4950
import { DividerBaseState } from '@fluentui/react-divider';
@@ -52,6 +53,8 @@ import type { FieldBaseProps } from '@fluentui/react-field';
5253
import { FieldBaseState } from '@fluentui/react-field';
5354
import { FieldContextValues } from '@fluentui/react-field';
5455
import type { FieldSlots as FieldSlots_2 } from '@fluentui/react-field';
56+
import type { FluentProviderContextValues } from '@fluentui/react-provider';
57+
import { FluentProviderProps } from '@fluentui/react-provider';
5558
import type { ForwardRefComponent } from '@fluentui/react-utilities';
5659
import type { InputBaseProps } from '@fluentui/react-input';
5760
import { InputBaseState } from '@fluentui/react-input';
@@ -116,6 +119,7 @@ import type { SkeletonSlots as SkeletonSlots_2 } from '@fluentui/react-skeleton'
116119
import type { SliderBaseProps } from '@fluentui/react-slider';
117120
import { SliderBaseState } from '@fluentui/react-slider';
118121
import type { SliderSlots as SliderSlots_2 } from '@fluentui/react-slider';
122+
import type { Slot } from '@fluentui/react-utilities';
119123
import type { SpinButtonBaseProps } from '@fluentui/react-spinbutton';
120124
import { SpinButtonBaseState } from '@fluentui/react-spinbutton';
121125
import type { SpinButtonSlots as SpinButtonSlots_2 } from '@fluentui/react-spinbutton';
@@ -139,6 +143,7 @@ import type { TextareaSlots as TextareaSlots_2 } from '@fluentui/react-textarea'
139143
import type { ToggleButtonBaseProps } from '@fluentui/react-button';
140144
import type { ToggleButtonBaseState } from '@fluentui/react-button';
141145
import { useMessageBarBodyContextValues_unstable } from '@fluentui/react-message-bar';
146+
import { useFluent_unstable as useProviderContext } from '@fluentui/react-shared-contexts';
142147

143148
// @public
144149
export const Accordion: ForwardRefComponent<AccordionProps>;
@@ -469,6 +474,15 @@ export type ProgressBarSlots = ProgressBarSlots_2;
469474
// @public
470475
export type ProgressBarState = ProgressBarBaseState;
471476

477+
// @public
478+
export const Provider: React_2.ForwardRefExoticComponent<Omit<ProviderSlots, "root"> & React_2.FragmentProps & Pick<FluentProviderProps, "dir" | "targetDocument"> & React_2.RefAttributes<HTMLDivElement>>;
479+
480+
// @public
481+
export type ProviderProps = ComponentProps<ProviderSlots> & Pick<FluentProviderProps, 'dir' | 'targetDocument'>;
482+
483+
// @public
484+
export type ProviderState = ComponentState<ProviderSlots> & Pick<FluentProviderProps, 'dir' | 'targetDocument'>;
485+
472486
// @public
473487
export const Radio: React_2.ForwardRefExoticComponent<Omit<ComponentProps<Partial<RadioSlots_2>, "input">, "onChange" | "size"> & {
474488
value?: string;
@@ -611,6 +625,9 @@ export const renderMessageBarTitle: (state: MessageBarTitleState_2) => JSXElemen
611625
// @public
612626
export const renderProgressBar: (state: ProgressBarState) => JSXElement;
613627

628+
// @public
629+
export const renderProvider: (state: ProviderState, contextValues: FluentProviderContextValues) => JSXElement;
630+
614631
// @public
615632
export const renderRadio: (state: RadioBaseState) => JSXElement;
616633

@@ -942,6 +959,11 @@ export const useMessageBarTitle: (props: MessageBarTitleProps, ref: React_2.Ref<
942959
// @public
943960
export const useProgressBar: (props: ProgressBarProps, ref: React_2.Ref<HTMLDivElement>) => ProgressBarState;
944961

962+
// @public (undocumented)
963+
export const useProvider: (props: ProviderProps, ref: React_2.Ref<HTMLDivElement>) => ProviderState;
964+
965+
export { useProviderContext }
966+
945967
// @public
946968
export const useRadio: (props: RadioProps, ref: React_2.Ref<HTMLInputElement>) => RadioState;
947969

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"@fluentui/react-message-bar": "^9.6.23",
3636
"@fluentui/react-persona": "^9.7.1",
3737
"@fluentui/react-progress": "^9.4.17",
38+
"@fluentui/react-provider": "^9.22.15",
3839
"@fluentui/react-radio": "^9.6.0",
3940
"@fluentui/react-rating": "^9.4.0",
4041
"@fluentui/react-search": "^9.4.0",
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { Provider, renderProvider, useProvider, useProviderContext } from './components/Provider/index';
2+
export type { ProviderProps, ProviderState } from './components/Provider/index';
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import * as React from 'react';
2+
import { render } from '@testing-library/react';
3+
import { isConformant } from '../../testing/isConformant';
4+
import { Provider } from './Provider';
5+
import { useProviderContext } from './useProviderContext';
6+
7+
describe('Provider', () => {
8+
isConformant({
9+
Component: Provider,
10+
displayName: 'Provider',
11+
disabledTests: ['component-handles-classname', 'component-has-root-ref', 'component-handles-ref'],
12+
});
13+
14+
const TestComponent = ({ children }: { children?: React.ReactNode }) => {
15+
const context = useProviderContext();
16+
17+
return <div dir={context.dir}>{children}</div>;
18+
};
19+
20+
it('renders a default state', () => {
21+
const result = render(
22+
<Provider>
23+
<TestComponent>Content</TestComponent>
24+
</Provider>,
25+
);
26+
expect(result.container).toHaveTextContent('Content');
27+
expect(result.container.firstChild).toHaveAttribute('dir', 'ltr');
28+
});
29+
30+
it('renders with custom props', () => {
31+
const result = render(
32+
<Provider dir="rtl">
33+
<TestComponent>Content</TestComponent>
34+
</Provider>,
35+
);
36+
expect(result.container).toHaveTextContent('Content');
37+
expect(result.container.firstChild).toHaveAttribute('dir', 'rtl');
38+
});
39+
});
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+
5+
import { renderProvider } from './renderProvider';
6+
import { useProvider } from './useProvider';
7+
import type { ProviderProps } from './Provider.types';
8+
import { useProviderContextValues } from './useProviderContextValues';
9+
10+
/**
11+
* Renders required context providers for Fluent Headless Components.
12+
*/
13+
export const Provider = React.forwardRef<HTMLDivElement, ProviderProps>((props, ref) => {
14+
const state = useProvider(props, ref);
15+
const contextValues = useProviderContextValues(state);
16+
17+
return renderProvider(state, contextValues);
18+
});
19+
20+
Provider.displayName = 'Provider';
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type * as React from 'react';
2+
import type { FluentProviderContextValues, FluentProviderProps } from '@fluentui/react-provider';
3+
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
4+
5+
export type ProviderSlots = {
6+
root: Slot<React.FragmentProps>;
7+
};
8+
9+
/**
10+
* Provider Props
11+
*/
12+
export type ProviderProps = ComponentProps<ProviderSlots> & Pick<FluentProviderProps, 'dir' | 'targetDocument'>;
13+
14+
/**
15+
* State used in rendering Provider
16+
*/
17+
export type ProviderState = ComponentState<ProviderSlots> & Pick<FluentProviderProps, 'dir' | 'targetDocument'>;
18+
19+
export type ProviderContextValues = FluentProviderContextValues;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export { Provider } from './Provider';
2+
export type { ProviderProps, ProviderState } from './Provider.types';
3+
export { renderProvider } from './renderProvider';
4+
export { useProvider } from './useProvider';
5+
export { useProviderContext } from './useProviderContext';
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/** @jsxRuntime automatic */
2+
/** @jsxImportSource @fluentui/react-jsx-runtime */
3+
4+
import { assertSlots } from '@fluentui/react-utilities';
5+
import type { JSXElement } from '@fluentui/react-utilities';
6+
import {
7+
Provider_unstable as Provider,
8+
TooltipVisibilityProvider_unstable as TooltipVisibilityProvider,
9+
} from '@fluentui/react-shared-contexts';
10+
import type { FluentProviderContextValues, FluentProviderSlots } from '@fluentui/react-provider';
11+
import type { ProviderState } from './Provider.types';
12+
13+
/**
14+
* Render the final JSX of Provider
15+
*/
16+
export const renderProvider = (state: ProviderState, contextValues: FluentProviderContextValues): JSXElement => {
17+
assertSlots<FluentProviderSlots>(state);
18+
19+
return (
20+
<Provider value={contextValues.provider}>
21+
<TooltipVisibilityProvider value={contextValues.tooltip}>
22+
<state.root />
23+
</TooltipVisibilityProvider>
24+
</Provider>
25+
);
26+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use client';
2+
3+
import * as React from 'react';
4+
import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';
5+
import { slot } from '@fluentui/react-utilities';
6+
7+
import type { ProviderProps, ProviderState } from './Provider.types';
8+
9+
export const useProvider = (props: ProviderProps, ref: React.Ref<HTMLDivElement>): ProviderState => {
10+
const parentContext = useFluent();
11+
const { children, dir, targetDocument } = props;
12+
13+
return {
14+
dir: dir || parentContext.dir,
15+
targetDocument: targetDocument || parentContext.targetDocument,
16+
components: { root: React.Fragment },
17+
root: slot.always({ children }, { elementType: React.Fragment }),
18+
};
19+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { useFluent_unstable as useProviderContext } from '@fluentui/react-shared-contexts';

0 commit comments

Comments
 (0)