Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
Expand Up @@ -4,35 +4,42 @@

```ts

/// <reference types="react" />

import type { ComponentProps } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import type { PopoverProps } from '@fluentui/react-popover';
import type { PopoverSurface } from '@fluentui/react-popover';
import * as React_2 from 'react';
import type { Slot } from '@fluentui/react-utilities';
import type { SlotClassNames } from '@fluentui/react-utilities';

// @public
export const InfoButton: ForwardRefComponent<InfoButtonProps>;
export const InfoButton: React_2.FC<InfoButtonProps>;

// @public (undocumented)
export const infoButtonClassNames: SlotClassNames<InfoButtonSlots>;

// @public
export type InfoButtonProps = ComponentProps<InfoButtonSlots> & {};
export type InfoButtonProps = Omit<ComponentProps<Partial<InfoButtonSlots>>, 'children'>;

// @public (undocumented)
export type InfoButtonSlots = {
root: Slot<'div'>;
root: NonNullable<Slot<PopoverProps>>;
button: NonNullable<Slot<'button'>>;
content: NonNullable<Slot<typeof PopoverSurface>>;
};

// @public
export type InfoButtonState = ComponentState<InfoButtonSlots>;
export type InfoButtonState = ComponentState<InfoButtonSlots> & {
popoverOpen: boolean;
};

// @public
export const renderInfoButton_unstable: (state: InfoButtonState) => JSX.Element;

// @public
export const useInfoButton_unstable: (props: InfoButtonProps, ref: React_2.Ref<HTMLElement>) => InfoButtonState;
export const useInfoButton_unstable: (props: InfoButtonProps) => InfoButtonState;

// @public
export const useInfoButtonStyles_unstable: (state: InfoButtonState) => InfoButtonState;
Expand Down
3 changes: 3 additions & 0 deletions packages/react-components/react-infobutton/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
"@fluentui/scripts": "^1.0.0"
},
"dependencies": {
"@fluentui/react-icons": "^2.0.175",
"@fluentui/react-popover": "^9.2.0",
"@fluentui/react-tabster": "^9.1.3",
"@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
@@ -0,0 +1,3 @@
import { Info12Regular, Info12Filled, bundleIcon } from '@fluentui/react-icons';

export const DefaultInfoButtonIcon = bundleIcon(Info12Filled, Info12Regular);
Original file line number Diff line number Diff line change
@@ -1,18 +1,52 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import { InfoButton } from './InfoButton';
import { isConformant } from '../../common/isConformant';
import { infoButtonClassNames } from './useInfoButtonStyles';
import { RenderResult } from '@testing-library/react';
Comment thread
sopranopillow marked this conversation as resolved.
Outdated

// testing-library's queryByRole function doesn't look inside portals
function queryByRoleDialog(result: RenderResult) {
const dialogs = result.baseElement.querySelectorAll('*[role="dialog"]');
Comment thread
sopranopillow marked this conversation as resolved.
Outdated
if (!dialogs?.length) {
return null;
} else {
expect(dialogs.length).toBe(1);
return dialogs.item(0) as HTMLElement;
}
}

const getPopoverSurfaceElement = (result: RenderResult) => {
// button needs to be clicked otherwise content won't be rendered.
result.getByRole('button').click();
const dialog = queryByRoleDialog(result);
expect(dialog).not.toBeNull();
return dialog!;
};

describe('InfoButton', () => {
isConformant({
Component: InfoButton,
displayName: 'InfoButton',
});

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

it('renders a default state', () => {
const result = render(<InfoButton>Default InfoButton</InfoButton>);
expect(result.container).toMatchSnapshot();
requiredProps: {
content: 'Popover content',
},
disabledTests: [
'component-handles-ref',
'component-has-root-ref',
'component-handles-classname',
'make-styles-overrides-win',
],
testOptions: {
'has-static-classnames': [
{
props: {},
expectedClassNames: {
// root shouldn't be expected since the root is a Popover
button: infoButtonClassNames.button,
content: infoButtonClassNames.content,
},
getPortalElement: getPopoverSurfaceElement,
},
],
},
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ import { useInfoButton_unstable } from './useInfoButton';
import { renderInfoButton_unstable } from './renderInfoButton';
import { useInfoButtonStyles_unstable } from './useInfoButtonStyles';
import type { InfoButtonProps } from './InfoButton.types';
import type { ForwardRefComponent } from '@fluentui/react-utilities';

/**
* InfoButton component - TODO: add more docs
* InfoButtons provide a way to display additional information about a form field or an area in the UI.
*/
export const InfoButton: ForwardRefComponent<InfoButtonProps> = React.forwardRef((props, ref) => {
const state = useInfoButton_unstable(props, ref);
export const InfoButton: React.FC<InfoButtonProps> = props => {
const state = useInfoButton_unstable(props);

useInfoButtonStyles_unstable(state);
return renderInfoButton_unstable(state);
});
};

InfoButton.displayName = 'InfoButton';
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
import type { PopoverProps, PopoverSurface } from '@fluentui/react-popover';
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';

export type InfoButtonSlots = {
root: Slot<'div'>;
root: NonNullable<Slot<PopoverProps>>;

/**
* The button that triggers the Popover.
*/
button: NonNullable<Slot<'button'>>;

/**
* The content to be displayed in the Popover.
*/
content: NonNullable<Slot<typeof PopoverSurface>>;
};

/**
* InfoButton Props
*/
export type InfoButtonProps = ComponentProps<InfoButtonSlots> & {};
export type InfoButtonProps = Omit<ComponentProps<Partial<InfoButtonSlots>>, 'children'>;
Comment thread
sopranopillow marked this conversation as resolved.
Outdated

/**
* State used in rendering InfoButton
*/
export type InfoButtonState = ComponentState<InfoButtonSlots>;
export type InfoButtonState = ComponentState<InfoButtonSlots> & {
popoverOpen: boolean;
};

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import * as React from 'react';
import { getSlots } from '@fluentui/react-utilities';
import type { InfoButtonState, InfoButtonSlots } from './InfoButton.types';
import { PopoverProps, PopoverTrigger } from '@fluentui/react-popover';

/**
* Render the final JSX of InfoButton
*/
export const renderInfoButton_unstable = (state: InfoButtonState) => {
const { slots, slotProps } = getSlots<InfoButtonSlots>(state);

// TODO Add additional slots in the appropriate place
return <slots.root {...slotProps.root} />;
return (
<slots.root {...(slotProps.root as PopoverProps)}>
<PopoverTrigger>
<slots.button {...slotProps.button} />
</PopoverTrigger>
<slots.content {...slotProps.content} />
</slots.root>
);
};

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as React from 'react';
import { DefaultInfoButtonIcon } from './DefaultInfoButtonIcon';
import { OnOpenChangeData, OpenPopoverEvents, Popover, PopoverSurface } from '@fluentui/react-popover';
import { resolveShorthand } from '@fluentui/react-utilities';
import { useControllableState } from '@fluentui/react-utilities';
import type { InfoButtonProps, InfoButtonState } from './InfoButton.types';

/**
* Create the state required to render InfoButton.
*
* The returned state can be modified with hooks such as useInfoButtonStyles_unstable,
* before being passed to renderInfoButton_unstable.
*
* @param props - props from this instance of InfoButton
*/
export const useInfoButton_unstable = (props: InfoButtonProps): InfoButtonState => {
const [popoverOpen, setPopoverOpen] = useControllableState({
state: props.open,
defaultState: props.defaultOpen,
initialState: false,
});

const handleOnPopoverChange = (e: OpenPopoverEvents, data: OnOpenChangeData) => {
props.onOpenChange?.(e, data);
setPopoverOpen(data.open);
};

return {
popoverOpen,

components: {
root: Popover,
button: 'button',
content: PopoverSurface,
},

root: {
children: <></>,
size: 'small',
withArrow: true,
positioning: 'above-start',
...props,
open: popoverOpen,
onOpenChange: handleOnPopoverChange,
},
content: resolveShorthand(props.content, {
required: true,
defaultProps: {
role: 'dialog',

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Popover requires an aria-label, this will be added in the future.

},
}),
button: resolveShorthand(props.button, {
required: true,
defaultProps: {
children: <DefaultInfoButtonIcon />,
type: 'button',
},
}),
};
};
Loading