Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dialog implementation #23900

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
12 changes: 5 additions & 7 deletions packages/react-components/react-dialog/Spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,6 @@ The root level component serves as an interface for interaction with all possibl

```tsx
type DialogSlots = {
/**
* The dialog element itself
*/
root: Slot<'div'>;
/**
* Dimmed background of dialog.
* The default overlay is rendered as a `<div>` with styling.
Expand All @@ -110,8 +106,10 @@ type DialogProps = ComponentProps<DialogSlots> & {
* `non-modal`: When a non-modal dialog is open, the rest of the page is not dimmed out and users can interact with the rest of the page. This also implies that the tab focus can move outside the dialog when it reaches the last focusable element.
*
* `alert`: is a special type of modal dialogs that interrupts the user's workflow to communicate an important message or ask for a decision. Unlike a typical modal dialog, the user must take an action through the options given to dismiss the dialog, and it cannot be dismissed through the dimmed background or escape key.
*
* @default 'modal'
*/
type?: 'modal' | 'non-modal' | 'alert';
modalType?: 'modal' | 'non-modal' | 'alert';
/**
* Controls the open state of the dialog
* @default undefined
Expand Down Expand Up @@ -152,9 +150,10 @@ export type DialogTriggerProps = {
/**
* Explicitly declare if the trigger is responsible for opening,
* closing or toggling a Dialog visibility state.
*
* @default 'toggle'
*/
type?: 'open' | 'close' | 'toggle';
action?: 'open' | 'close' | 'toggle';
/**
* Explicitly require single child or render function
* to inject properties
Expand All @@ -166,7 +165,6 @@ export type DialogTriggerProps = {
### DialogContent

The `DialogContent` component represents the visual part of a `Dialog` as a whole, it contains everything that should be visible.
By itself it has no style, but it's responsible of showing/hiding content when `Dialog` visibility state changes, also it'll ensure a `Portal` is properly created for the content being provided as well as for the `overlay` element provided by `Dialog`

```tsx
type DialogTitleSlots = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"extends": "./api-extractor.json",
"mainEntryPointFilePath": "<projectFolder>/dist/packages/react-components/<unscopedPackageName>/src/index.d.ts"
"mainEntryPointFilePath": "<projectFolder>/dist/types/packages/react-components/<unscopedPackageName>/src/index.d.ts"
}
166 changes: 160 additions & 6 deletions packages/react-components/react-dialog/etc/react-dialog.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,193 @@

```ts

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

import { ARIAButtonSlotProps } from '@fluentui/react-aria';
import type { ComponentProps } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import type { FluentTriggerComponent } from '@fluentui/react-utilities';
import { ForwardRefComponent } from '@fluentui/react-utilities';
import { JSXElementConstructor } from 'react';
import * as React_2 from 'react';
import { ReactElement } from 'react';
import type { Slot } from '@fluentui/react-utilities';
import type { SlotClassNames } from '@fluentui/react-utilities';

// @public
// @public (undocumented)
export const Dialog: ForwardRefComponent<DialogProps>;

// @public
export const DialogActions: ForwardRefComponent<DialogActionsProps>;

// @public (undocumented)
export const dialogActionsClassName = "fui-DialogActions";

// @public (undocumented)
export const dialogActionsClassNames: SlotClassNames<DialogActionsSlots>;

// @public
export type DialogActionsProps = ComponentProps<DialogActionsSlots> & {};

// @public (undocumented)
export type DialogActionsSlots = {
root: Slot<'div'>;
};

// @public
export type DialogActionsState = ComponentState<DialogActionsSlots>;

// @public
export const DialogBody: ForwardRefComponent<DialogBodyProps>;

// @public (undocumented)
export const dialogBodyClassNames: SlotClassNames<DialogBodySlots>;

// @public
export type DialogBodyProps = ComponentProps<DialogBodySlots> & {};

// @public (undocumented)
export type DialogBodySlots = {
root: Slot<'div'>;
};

// @public
export type DialogBodyState = ComponentState<DialogBodySlots>;

// @public (undocumented)
export const dialogClassNames: SlotClassNames<DialogSlots>;

// @public
export type DialogProps = ComponentProps<DialogSlots>;
export const DialogContent: ForwardRefComponent<DialogContentProps>;

// @public (undocumented)
export const dialogContentClassNames: SlotClassNames<DialogContentSlots>;

// @public
export type DialogContentProps = ComponentProps<DialogContentSlots>;

// @public (undocumented)
export type DialogContentSlots = {
root: Slot<'div', 'main'>;
};

// @public
export type DialogContentState = ComponentState<DialogContentSlots>;

// @public (undocumented)
export type DialogProps = ComponentProps<Partial<DialogSlots>> & {
modalType?: DialogModalType;
open?: boolean;
defaultOpen?: boolean;
onOpenChange?: DialogOnOpenChange;
children: [JSX.Element, JSX.Element] | JSX.Element;
};

// @public (undocumented)
export type DialogSlots = {
root: Slot<'div'>;
overlay?: Slot<'div'>;
root: NonNullable<Slot<'div'>>;
};

// @public (undocumented)
export type DialogState = ComponentState<DialogSlots> & DialogContextValue & {
content: React_2.ReactNode;
trigger: React_2.ReactNode;
};

// @public
export type DialogState = ComponentState<DialogSlots>;
export const DialogTitle: ForwardRefComponent<DialogTitleProps>;

// @public (undocumented)
export const dialogTitleClassNames: SlotClassNames<DialogTitleSlots>;

// @public
export const renderDialog_unstable: (state: DialogState) => JSX.Element;
export type DialogTitleProps = ComponentProps<DialogTitleSlots> & {};

// @public (undocumented)
export type DialogTitleSlots = {
root: Slot<'div', 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'>;
closeButton?: Slot<ARIAButtonSlotProps>;
};

// @public
export type DialogTitleState = ComponentState<DialogTitleSlots>;

// @public
export const DialogTrigger: React_2.FC<DialogTriggerProps> & FluentTriggerComponent;

// @public (undocumented)
export type DialogTriggerAction = 'open' | 'close' | 'toggle';

// @public
export type DialogTriggerChildProps = Required<Pick<React_2.HTMLAttributes<HTMLElement>, 'onClick' | 'aria-haspopup'>> & {
ref?: React_2.Ref<never>;
};

// @public (undocumented)
export type DialogTriggerProps = {
action?: DialogTriggerAction;
children: (React_2.ReactElement & {
ref?: React_2.Ref<unknown>;
}) | ((props: DialogTriggerChildProps) => React_2.ReactElement | null);
};

// @public (undocumented)
export type DialogTriggerState = {
children: React_2.ReactElement | null;
};

// @public
export const renderDialog_unstable: (state: DialogState, contextValues: DialogContextValues) => JSX.Element;

// @public
export const renderDialogActions_unstable: (state: DialogActionsState) => JSX.Element;

// @public
export const renderDialogBody_unstable: (state: DialogBodyState) => JSX.Element;

// @public
export const renderDialogContent_unstable: (state: DialogContentState) => JSX.Element;

// @public
export const renderDialogTitle_unstable: (state: DialogTitleState) => JSX.Element;

// @public
export const renderDialogTrigger_unstable: (state: DialogTriggerState) => ReactElement<any, string | JSXElementConstructor<any>> | null;

// @public
export const useDialog_unstable: (props: DialogProps, ref: React_2.Ref<HTMLElement>) => DialogState;

// @public
export const useDialogActions_unstable: (props: DialogActionsProps, ref: React_2.Ref<HTMLElement>) => DialogActionsState;

// @public
export const useDialogActionsStyles_unstable: (state: DialogActionsState) => DialogActionsState;

// @public
export const useDialogBody_unstable: (props: DialogBodyProps, ref: React_2.Ref<HTMLElement>) => DialogBodyState;

// @public
export const useDialogBodyStyles_unstable: (state: DialogBodyState) => DialogBodyState;

// @public
export const useDialogContent_unstable: (props: DialogContentProps, ref: React_2.Ref<HTMLElement>) => DialogContentState;

// @public
export const useDialogContentStyles_unstable: (state: DialogContentState) => DialogContentState;

// @public
export const useDialogStyles_unstable: (state: DialogState) => DialogState;

// @public
export const useDialogTitle_unstable: (props: DialogTitleProps, ref: React_2.Ref<HTMLElement>) => DialogTitleState;

// @public
export const useDialogTitleStyles_unstable: (state: DialogTitleState) => DialogTitleState;

// @public
export const useDialogTrigger_unstable: (props: DialogTriggerProps) => DialogTriggerState;

// (No @packageDocumentation comment for this package)

```
12 changes: 10 additions & 2 deletions packages/react-components/react-dialog/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"start": "yarn storybook",
"test": "jest --passWithNoTests",
"docs": "api-extractor run --config=config/api-extractor.local.json --local",
"build:local": "tsc -p ./tsconfig.lib.json --module esnext --emitDeclarationOnly && node ../../../scripts/typescript/normalize-import --output ./dist/packages/react-components/react-dialog/src && yarn docs",
"build:local": "tsc -p ./tsconfig.lib.json --module esnext --emitDeclarationOnly && node ../../../scripts/typescript/normalize-import --output ./dist/types/packages/react-components/react-dialog/src && yarn docs",
"storybook": "node ../../../scripts/storybook/runner",
"type-check": "tsc -b tsconfig.json"
},
Expand All @@ -33,7 +33,15 @@
},
"dependencies": {
"@griffel/react": "^1.2.0",
"@fluentui/react-utilities": "^9.0.0",
"@fluentui/keyboard-keys": "9.0.0",
"@fluentui/react-context-selector": "9.0.0",
"@fluentui/react-shared-contexts": "9.0.0",
"@fluentui/react-aria": "9.0.0",
"@fluentui/react-icons": "^2.0.175",
"@fluentui/react-tabster": "9.0.1",
"@fluentui/react-theme": "9.0.0",
"@fluentui/react-portal": "9.0.1",
"@fluentui/react-utilities": "9.0.0",
"tslib": "^2.1.0"
},
"peerDependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components/DialogActions/index';
1 change: 1 addition & 0 deletions packages/react-components/react-dialog/src/DialogBody.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components/DialogBody/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components/DialogContent/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components/DialogTitle/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components/DialogTrigger/index';
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,27 @@ describe('Dialog', () => {
isConformant({
Component: Dialog,
displayName: 'Dialog',
disabledTests: ['component-has-static-classname-exported'],
disabledTests: [
// Menu does not render DOM elements
'component-handles-ref',
'component-has-root-ref',
'component-handles-classname',
'component-has-static-classname',
'component-has-static-classnames-object',
'component-has-static-classname-exported',
// Menu does not have own styles
'make-styles-overrides-win',
],
});

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

it('renders a default state', () => {
const result = render(<Dialog>Default Dialog</Dialog>);
const result = render(
<Dialog>
<div>Default Dialog</div>
</Dialog>,
);
expect(result.container).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@ import { useDialog_unstable } from './useDialog';
import { renderDialog_unstable } from './renderDialog';
import { useDialogStyles_unstable } from './useDialogStyles';
import type { DialogProps } from './Dialog.types';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import { useDialogContextValues_unstable } from './useDialogContextValues';

/**
* A Dialog is an elevated Card triggered by a user’s action.
* The `Dialog` root level component serves as an interface for interaction with all possible behaviors exposed.
* It provides context down the hierarchy to `children` compound components to allow functionality.
* This component expects to receive as children either a `DialogContent` or a `DialogTrigger`
* and a `DialogContent` (or some component that will eventually render one of those compound components)
* in this specific order
*/
export const Dialog: ForwardRefComponent<DialogProps> = React.forwardRef((props, ref) => {
const state = useDialog_unstable(props, ref);
export const Dialog: React.FC<DialogProps> = React.memo(props => {
const state = useDialog_unstable(props);
const contextValues = useDialogContextValues_unstable(state);

useDialogStyles_unstable(state);
return renderDialog_unstable(state);
return renderDialog_unstable(state, contextValues);
});

Dialog.displayName = 'Dialog';
Loading