Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: Overrides support (unstable)",
Comment thread
miroslavstastny marked this conversation as resolved.
Outdated
"packageName": "@fluentui/react-components",
"email": "miroslav.stastny@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: Overrides context (unstable)",
Comment thread
miroslavstastny marked this conversation as resolved.
Outdated
"packageName": "@fluentui/react-context-selector",
"email": "miroslav.stastny@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: Allow default appearance override",
"packageName": "@fluentui/react-input",
"email": "miroslav.stastny@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: Overrides support (unstable)",
"packageName": "@fluentui/react-provider",
"email": "miroslav.stastny@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: Overrides context",
"packageName": "@fluentui/react-shared-contexts",
"email": "miroslav.stastny@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ import { mergeClasses } from '@griffel/react';
import { OnOpenChangeData } from '@fluentui/react-popover';
import { OnVisibleChangeData } from '@fluentui/react-tooltip';
import { OpenPopoverEvents } from '@fluentui/react-popover';
import { OverridesContextValue_unstable } from '@fluentui/react-shared-contexts';
import { PartialTheme } from '@fluentui/react-theme';
import { Popover } from '@fluentui/react-popover';
import { PopoverContextValue } from '@fluentui/react-popover';
Expand Down Expand Up @@ -1106,6 +1107,8 @@ export { OnVisibleChangeData }

export { OpenPopoverEvents }

export { OverridesContextValue_unstable }
Comment thread
miroslavstastny marked this conversation as resolved.
Outdated

export { PartialTheme }

export { Popover }
Expand Down
1 change: 1 addition & 0 deletions packages/react-components/react-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export {
useTooltipVisibility_unstable as useTooltipVisibility,
useThemeClassName_unstable as useThemeClassName,
} from '@fluentui/react-shared-contexts';
export type { OverridesContextValue_unstable } from '@fluentui/react-shared-contexts';
export {
getNativeElementProps,
getPartitionedNativeProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type ContextReducer<Value, SelectedValue> = React.Reducer<
* @internal
* This hook returns context selected value by selector.
* It will only accept context created by `createContext`.
* It will trigger re-render if only the selected value is referencially changed.
* It will trigger re-render if only the selected value is referentially changed.
*/
export const useContextSelector = <Value, SelectedValue>(
context: Context<Value>,
Expand Down
1 change: 1 addition & 0 deletions packages/react-components/react-input/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@fluentui/scripts": "^1.0.0"
},
"dependencies": {
"@fluentui/react-shared-contexts": "^9.0.1",
"@fluentui/react-theme": "^9.1.0",
"@fluentui/react-utilities": "^9.1.1",
"@griffel/react": "^1.4.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
useEventCallback,
} from '@fluentui/react-utilities';
import type { InputProps, InputState } from './Input.types';
import { useOverrides_unstable as useOverrides } from '@fluentui/react-shared-contexts';

/**
* Create the state required to render Input.
Expand All @@ -17,7 +18,9 @@ import type { InputProps, InputState } from './Input.types';
* @param ref - reference to `<input>` element of Input
*/
export const useInput_unstable = (props: InputProps, ref: React.Ref<HTMLInputElement>): InputState => {
const { size = 'medium', appearance = 'outline', onChange } = props;
const overrides = useOverrides();
Comment thread
miroslavstastny marked this conversation as resolved.

const { size = 'medium', appearance = overrides.inputDefaultAppearance ?? 'outline', onChange } = props;

if (
process.env.NODE_ENV !== 'production' &&
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import * as React from 'react';
import { makeStyles, mergeClasses, shorthands, tokens, useId, Input, Label } from '@fluentui/react-components';
import {
makeStyles,
mergeClasses,
shorthands,
tokens,
useId,
Input,
Label,
FluentProvider,
OverridesContextValue_unstable,
} from '@fluentui/react-components';

const useStyles = makeStyles({
base: {
Expand Down Expand Up @@ -27,15 +37,20 @@ const useStyles = makeStyles({
},
});

export const Appearance = () => {
const AppearanceExample = () => {
const outlineId = useId('input-outline');
const underlineId = useId('input-underline');
const filledLighterId = useId('input-filledLighter');
const filledDarkerId = useId('input-filledDarker');
const styles = useStyles();

return (
<div className={styles.base}>
<>
<div className={styles.field}>
<Label htmlFor={outlineId}>No appearance (defaults to outline)</Label>
<Input id={outlineId} />
</div>

<div className={styles.field}>
<Label htmlFor={outlineId}>Outline appearance (default)</Label>
<Input appearance="outline" id={outlineId} />
Expand All @@ -55,6 +70,27 @@ export const Appearance = () => {
<Label htmlFor={filledDarkerId}>Filled darker appearance</Label>
<Input appearance="filled-darker" id={filledDarkerId} />
</div>
</>
);
};

export const Appearance = () => {
Comment thread
miroslavstastny marked this conversation as resolved.
Outdated
const styles = useStyles();
const [overrides, _] = React.useState<OverridesContextValue_unstable>({
inputDefaultAppearance: 'filled-darker',
});

return (
<div className={styles.base}>
<h2>No overrides</h2>

<AppearanceExample />

<FluentProvider overrides_unstable={overrides}>
<h2>With overrides</h2>

<AppearanceExample />
</FluentProvider>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { ComponentProps } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import { OverridesContextValue_unstable } from '@fluentui/react-shared-contexts';
import type { PartialTheme } from '@fluentui/react-theme';
import type { ProviderContextValue_unstable } from '@fluentui/react-shared-contexts';
import * as React_2 from 'react';
Expand All @@ -21,6 +22,7 @@ export const FluentProvider: React_2.ForwardRefExoticComponent<Omit<ComponentPro
dir?: "ltr" | "rtl" | undefined;
targetDocument?: Document | undefined;
theme?: Partial<Theme> | undefined;
overrides_unstable?: OverridesContextValue_unstable | undefined;
} & React_2.RefAttributes<HTMLElement>>;

// @public (undocumented)
Expand All @@ -32,13 +34,15 @@ export type FluentProviderContextValues = Pick<FluentProviderState, 'theme'> & {
themeClassName: ThemeClassNameContextValue_unstable;
textDirection: 'ltr' | 'rtl';
tooltip: TooltipVisibilityContextValue_unstable;
overrides: OverridesContextValue_unstable;
};

// @public (undocumented)
export type FluentProviderProps = Omit<ComponentProps<FluentProviderSlots>, 'dir'> & {
dir?: 'ltr' | 'rtl';
targetDocument?: Document;
theme?: PartialTheme;
overrides_unstable?: OverridesContextValue_unstable;
};

// @public (undocumented)
Expand All @@ -50,6 +54,7 @@ export type FluentProviderSlots = {
export type FluentProviderState = ComponentState<FluentProviderSlots> & Pick<FluentProviderProps, 'targetDocument'> & Required<Pick<FluentProviderProps, 'dir'>> & {
theme: ThemeContextValue_unstable;
themeClassName: string;
overrides: OverridesContextValue_unstable;
};

// @public
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {
OverridesContextValue_unstable as OverridesContextValue,
ProviderContextValue_unstable as ProviderContextValue,
TooltipVisibilityContextValue_unstable as TooltipVisibilityContextValue,
ThemeClassNameContextValue_unstable as ThemeClassNameContextValue,
Expand All @@ -19,18 +20,23 @@ export type FluentProviderProps = Omit<ComponentProps<FluentProviderSlots>, 'dir
targetDocument?: Document;

theme?: PartialTheme;

// eslint-disable-next-line @typescript-eslint/naming-convention
overrides_unstable?: OverridesContextValue; // FIXME: better type
};

export type FluentProviderState = ComponentState<FluentProviderSlots> &
Pick<FluentProviderProps, 'targetDocument'> &
Required<Pick<FluentProviderProps, 'dir'>> & {
theme: ThemeContextValue;
themeClassName: string;
overrides: OverridesContextValue;
Comment thread
layershifter marked this conversation as resolved.
Outdated
};

export type FluentProviderContextValues = Pick<FluentProviderState, 'theme'> & {
provider: ProviderContextValue;
themeClassName: ThemeClassNameContextValue;
textDirection: 'ltr' | 'rtl';
tooltip: TooltipVisibilityContextValue;
overrides: OverridesContextValue;
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import { TextDirectionProvider } from '@griffel/react';
import {
OverridesProvider_unstable as OverridesProvider,
Provider_unstable as Provider,
TooltipVisibilityProvider_unstable as TooltipVisibilityProvider,
ThemeProvider_unstable as ThemeProvider,
Expand All @@ -24,7 +25,9 @@ export const renderFluentProvider_unstable = (
<ThemeClassNameProvider value={contextValues.themeClassName}>
<TooltipVisibilityProvider value={contextValues.tooltip}>
<TextDirectionProvider dir={contextValues.textDirection}>
<slots.root {...slotProps.root}>{state.root.children}</slots.root>
<OverridesProvider value={contextValues.overrides}>
<slots.root {...slotProps.root}>{state.root.children}</slots.root>
</OverridesProvider>
</TextDirectionProvider>
</TooltipVisibilityProvider>
</ThemeClassNameProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as React from 'react';
import { FluentProvider } from './FluentProvider';
import { useFluentProvider_unstable } from './useFluentProvider';
import type { PartialTheme } from '@fluentui/react-theme';
import { OverridesContextValue_unstable } from '@fluentui/react-shared-contexts';

describe('useFluentProvider_unstable', () => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
Expand Down Expand Up @@ -50,4 +51,34 @@ describe('useFluentProvider_unstable', () => {
}
`);
});

it('should merge overrides', () => {
const overridesA: OverridesContextValue_unstable = {
inputDefaultAppearance: 'underline',
// currently the overrides object contains a single value, adding one more to test the merging
customValue: 'shouldNotBeOverridden',
} as OverridesContextValue_unstable;
const overridesB: OverridesContextValue_unstable = {
inputDefaultAppearance: 'filled-darker',
};

const Wrapper: React.FC = ({ children }) => (
<FluentProvider overrides_unstable={overridesA}>{children}</FluentProvider>
);

const { result } = renderHook(
// eslint-disable-next-line @typescript-eslint/naming-convention
() => useFluentProvider_unstable({ overrides_unstable: overridesB }, React.createRef()),
{
wrapper: Wrapper,
},
);

expect(result.current.overrides).toMatchInlineSnapshot(`
Object {
"customValue": "shouldNotBeOverridden",
"inputDefaultAppearance": "filled-darker",
}
`);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useFocusVisible } from '@fluentui/react-tabster';
import {
ThemeContext_unstable as ThemeContext,
useFluent_unstable as useFluent,
useOverrides_unstable as useOverrides,
} from '@fluentui/react-shared-contexts';
import type { ThemeContextValue_unstable as ThemeContextValue } from '@fluentui/react-shared-contexts';
import { getNativeElementProps, useMergedRefs } from '@fluentui/react-utilities';
Expand All @@ -24,14 +25,22 @@ export const useFluentProvider_unstable = (
): FluentProviderState => {
const parentContext = useFluent();
const parentTheme = useTheme();
const parentOverrides = useOverrides();

/**
* TODO: add merge functions to "dir" merge,
* nesting providers with the same "dir" should not add additional attributes to DOM
* see https://github.com/microsoft/fluentui/blob/0dc74a19f3aa5a058224c20505016fbdb84db172/packages/fluentui/react-northstar/src/utils/mergeProviderContexts.ts#L89-L93
*/
const { dir = parentContext.dir, targetDocument = parentContext.targetDocument, theme } = props;
const mergedTheme = mergeThemes(parentTheme, theme);
const {
dir = parentContext.dir,
targetDocument = parentContext.targetDocument,
theme,
overrides_unstable: overrides = {},
} = props;
const mergedTheme = mergeObjects(parentTheme, theme);

const mergedOverrides = mergeObjects(parentOverrides, overrides);
Comment thread
miroslavstastny marked this conversation as resolved.
Outdated

React.useEffect(() => {
if (process.env.NODE_ENV !== 'production' && mergedTheme === undefined) {
Expand All @@ -49,6 +58,7 @@ export const useFluentProvider_unstable = (
dir,
targetDocument,
theme: mergedTheme,
overrides: mergedOverrides,
themeClassName: useFluentProviderThemeStyleTag({ theme: mergedTheme, targetDocument }),

components: {
Expand All @@ -63,7 +73,7 @@ export const useFluentProvider_unstable = (
};
};

function mergeThemes(a: ThemeContextValue, b: ThemeContextValue): ThemeContextValue {
function mergeObjects<T>(a: T, b: T): T {
// Merge impacts perf: we should like to avoid it if it's possible
if (a && b) {
return { ...a, ...b };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import * as React from 'react';
import type { FluentProviderContextValues, FluentProviderState } from './FluentProvider.types';

export function useFluentProviderContextValues_unstable(state: FluentProviderState): FluentProviderContextValues {
const { root, dir, targetDocument, theme } = state;
const { root, dir, targetDocument, theme, overrides } = state;
Comment thread
miroslavstastny marked this conversation as resolved.
Outdated

const provider = React.useMemo(() => ({ dir, targetDocument }), [dir, targetDocument]);
// "Tooltip" component mutates an object in this context, instance should be stable
const [tooltip] = React.useState(() => ({}));

return {
overrides,
provider,
textDirection: dir,
tooltip,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@
import * as React_2 from 'react';
import type { Theme } from '@fluentui/react-theme';

// @internal (undocumented)
export type OverridesContextValue_unstable = {
inputDefaultAppearance?: 'outline' | 'underline' | 'filled-darker' | 'filled-lighter';
};

// @internal (undocumented)
export const OverridesProvider_unstable: React_2.Provider<OverridesContextValue_unstable | undefined>;

// @internal (undocumented)
export const Provider_unstable: React_2.Provider<ProviderContextValue_unstable>;

Expand Down Expand Up @@ -44,6 +52,11 @@ export const TooltipVisibilityProvider_unstable: React_2.Provider<TooltipVisibil
// @public (undocumented)
export function useFluent_unstable(): ProviderContextValue_unstable;

// Warning: (ae-incompatible-release-tags) The symbol "useOverrides" is marked as @public, but its signature references "OverridesContextValue" which is marked as @internal
//
// @public (undocumented)
export function useOverrides_unstable(): OverridesContextValue_unstable;

// @internal (undocumented)
export function useThemeClassName_unstable(): ThemeClassNameContextValue_unstable;

Expand Down
Loading