Private
Public Access
1
0

feat: Fluent UI Outlook Lite + connections mockup

This commit is contained in:
2026-04-14 18:52:25 +00:00
parent 1199eff6c3
commit dfa4010406
34820 changed files with 1003813 additions and 205 deletions

View File

@@ -0,0 +1,12 @@
'use client';
import * as React from 'react';
import { usePortal_unstable } from './usePortal';
import { renderPortal_unstable } from './renderPortal';
/**
* A portal provides a way to render children into a DOM node
* that exists outside the DOM hierarchy of the parent component.
*/ export const Portal = (props)=>{
const state = usePortal_unstable(props);
return renderPortal_unstable(state);
};
Portal.displayName = 'Portal';

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Portal/Portal.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\n\nimport { usePortal_unstable } from './usePortal';\nimport { renderPortal_unstable } from './renderPortal';\nimport type { PortalProps } from './Portal.types';\n\n/**\n * A portal provides a way to render children into a DOM node\n * that exists outside the DOM hierarchy of the parent component.\n */\nexport const Portal: React.FC<PortalProps> = props => {\n const state = usePortal_unstable(props);\n\n return renderPortal_unstable(state);\n};\n\nPortal.displayName = 'Portal';\n"],"names":["React","usePortal_unstable","renderPortal_unstable","Portal","props","state","displayName"],"mappings":"AAAA;AAEA,YAAYA,WAAW,QAAQ;AAE/B,SAASC,kBAAkB,QAAQ,cAAc;AACjD,SAASC,qBAAqB,QAAQ,iBAAiB;AAGvD;;;CAGC,GACD,OAAO,MAAMC,SAAgCC,CAAAA;IAC3C,MAAMC,QAAQJ,mBAAmBG;IAEjC,OAAOF,sBAAsBG;AAC/B,EAAE;AAEFF,OAAOG,WAAW,GAAG"}

View File

@@ -0,0 +1 @@
import * as React from 'react';

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Portal/Portal.types.ts"],"sourcesContent":["import * as React from 'react';\n\nexport type PortalProps = {\n /**\n * React children\n */\n children?: React.ReactNode;\n\n /**\n * Where the portal children are mounted on DOM\n *\n * @default a new element on document.body without any styling\n */\n mountNode?: HTMLElement | null | { element?: HTMLElement | null; className?: string };\n};\n\nexport type PortalState = Pick<PortalProps, 'children'> & {\n mountNode: HTMLElement | null | undefined;\n\n /**\n * Ref to the root span element as virtual parent\n */\n virtualParentRootRef: // eslint-disable-next-line @typescript-eslint/no-deprecated\n React.MutableRefObject<HTMLSpanElement | null>;\n};\n"],"names":["React"],"mappings":"AAAA,YAAYA,WAAW,QAAQ"}

View File

@@ -0,0 +1,3 @@
export { Portal } from './Portal';
export { renderPortal_unstable } from './renderPortal';
export { usePortal_unstable } from './usePortal';

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Portal/index.ts"],"sourcesContent":["export { Portal } from './Portal';\nexport type { PortalProps, PortalState } from './Portal.types';\nexport { renderPortal_unstable } from './renderPortal';\nexport { usePortal_unstable } from './usePortal';\n"],"names":["Portal","renderPortal_unstable","usePortal_unstable"],"mappings":"AAAA,SAASA,MAAM,QAAQ,WAAW;AAElC,SAASC,qBAAqB,QAAQ,iBAAiB;AACvD,SAASC,kBAAkB,QAAQ,cAAc"}

View File

@@ -0,0 +1,12 @@
import * as ReactDOM from 'react-dom';
import * as React from 'react';
/**
* Render the final JSX of Portal
*/ export const renderPortal_unstable = (state)=>{
return /*#__PURE__*/ React.createElement("span", {
hidden: true,
ref: state.virtualParentRootRef
}, state.mountNode && /*#__PURE__*/ ReactDOM.createPortal(/*#__PURE__*/ React.createElement(React.Fragment, null, state.children, /*#__PURE__*/ React.createElement("span", {
hidden: true
})), state.mountNode));
};

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Portal/renderPortal.tsx"],"sourcesContent":["import * as ReactDOM from 'react-dom';\nimport * as React from 'react';\nimport type { PortalState } from './Portal.types';\n\n/**\n * Render the final JSX of Portal\n */\nexport const renderPortal_unstable = (state: PortalState): React.ReactElement => {\n return (\n <span hidden ref={state.virtualParentRootRef}>\n {state.mountNode &&\n ReactDOM.createPortal(\n <>\n {state.children}\n {/* Heads up!\n * This node exists only to ensure that the portal is not empty as we rely on that in `usePortalMountNode`\n * hook for React 18+.\n */}\n <span hidden />\n </>,\n state.mountNode,\n )}\n </span>\n );\n};\n"],"names":["ReactDOM","React","renderPortal_unstable","state","span","hidden","ref","virtualParentRootRef","mountNode","createPortal","children"],"mappings":"AAAA,YAAYA,cAAc,YAAY;AACtC,YAAYC,WAAW,QAAQ;AAG/B;;CAEC,GACD,OAAO,MAAMC,wBAAwB,CAACC;IACpC,qBACE,oBAACC;QAAKC,QAAAA;QAAOC,KAAKH,MAAMI,oBAAoB;OACzCJ,MAAMK,SAAS,kBACdR,SAASS,YAAY,eACnB,0CACGN,MAAMO,QAAQ,gBAKf,oBAACN;QAAKC,QAAAA;SAERF,MAAMK,SAAS;AAIzB,EAAE"}

View File

@@ -0,0 +1,81 @@
'use client';
import { setVirtualParent } from '@fluentui/react-utilities';
import * as React from 'react';
import { toMountNodeProps } from '../../utils/toMountNodeProps';
import { usePortalMountNode } from './usePortalMountNode';
/**
* Create the state required to render Portal.
*
* The returned state can be modified with hooks such as usePortalStyles, before being passed to renderPortal_unstable.
*
* @param props - props from this instance of Portal
*/ export const usePortal_unstable = (props)=>{
const { element, className } = toMountNodeProps(props.mountNode);
const virtualParentRootRef = React.useRef(null);
const fallbackElement = usePortalMountNode({
disabled: !!element,
className
});
const mountNode = element !== null && element !== void 0 ? element : fallbackElement;
const state = {
children: props.children,
mountNode,
virtualParentRootRef
};
React.useEffect(()=>{
if (!mountNode) {
return;
}
const virtualParent = virtualParentRootRef.current;
// By default, we create a mount node for portal on `document.body` (see usePortalMountNode()) and have following structure:
//
// <body>
// <!-- ⚛️ application root -->
// <div id="root">
// <!-- ⬇️ portal node rendered in a tree to anchor (virtual parent node) -->
// <span aria-hidden="true"></span>
// </div>
// <div id="portal-mount-node">
// <!-- 🧩portal content -->
// </div>
// </body>
//
// To make sure that `.elementContains()` works correctly, we link a virtual parent to a portal node (a virtual parent node becomes a parent of mount node):
// virtual.contains(mountNode) === false
// (while we need ⬇️⬇️⬇️)
// elementsContains(virtualParent, mountNode) === true
// elementsContains(mountNode, virtualParent) === false
//
// For more details, check docs for virtual parent utils.
//
// However, if a user provides a custom mount node (via `props`) the structure could be different:
//
// <body>
// <!-- application root -->
// <div id="root">
// <div id="portal-mount-node">
// <!-- 🧩portal content -->
//
// <span aria-hidden="true"></span>
// </div>
// </div>
// </body>
//
// A mount node in this case contains portal's content and a virtual parent node. In this case nodes linking is redundant and the check below avoids it.
//
// Otherwise, there is a circular reference - both elements are parents of each other:
// elementsContains(mountNode, virtualParent) === true
// elementsContains(virtualParent, mountNode) === true
const isVirtualParentInsideChild = mountNode.contains(virtualParent);
if (virtualParent && !isVirtualParentInsideChild) {
setVirtualParent(mountNode, virtualParent);
return ()=>{
setVirtualParent(mountNode, undefined);
};
}
}, [
virtualParentRootRef,
mountNode
]);
return state;
};

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Portal/usePortal.ts"],"sourcesContent":["'use client';\n\nimport { setVirtualParent } from '@fluentui/react-utilities';\nimport * as React from 'react';\n\nimport { toMountNodeProps } from '../../utils/toMountNodeProps';\nimport { usePortalMountNode } from './usePortalMountNode';\nimport type { PortalProps, PortalState } from './Portal.types';\n\n/**\n * Create the state required to render Portal.\n *\n * The returned state can be modified with hooks such as usePortalStyles, before being passed to renderPortal_unstable.\n *\n * @param props - props from this instance of Portal\n */\nexport const usePortal_unstable = (props: PortalProps): PortalState => {\n const { element, className } = toMountNodeProps(props.mountNode);\n\n const virtualParentRootRef = React.useRef<HTMLSpanElement>(null);\n const fallbackElement = usePortalMountNode({ disabled: !!element, className });\n\n const mountNode = element ?? fallbackElement;\n const state: PortalState = {\n children: props.children,\n mountNode,\n virtualParentRootRef,\n };\n\n React.useEffect(() => {\n if (!mountNode) {\n return;\n }\n\n const virtualParent = virtualParentRootRef.current;\n\n // By default, we create a mount node for portal on `document.body` (see usePortalMountNode()) and have following structure:\n //\n // <body>\n // <!-- ⚛️ application root -->\n // <div id=\"root\">\n // <!-- ⬇️ portal node rendered in a tree to anchor (virtual parent node) -->\n // <span aria-hidden=\"true\"></span>\n // </div>\n // <div id=\"portal-mount-node\">\n // <!-- 🧩portal content -->\n // </div>\n // </body>\n //\n // To make sure that `.elementContains()` works correctly, we link a virtual parent to a portal node (a virtual parent node becomes a parent of mount node):\n // virtual.contains(mountNode) === false\n // (while we need ⬇️⬇️⬇️)\n // elementsContains(virtualParent, mountNode) === true\n // elementsContains(mountNode, virtualParent) === false\n //\n // For more details, check docs for virtual parent utils.\n //\n // However, if a user provides a custom mount node (via `props`) the structure could be different:\n //\n // <body>\n // <!-- application root -->\n // <div id=\"root\">\n // <div id=\"portal-mount-node\">\n // <!-- 🧩portal content -->\n //\n // <span aria-hidden=\"true\"></span>\n // </div>\n // </div>\n // </body>\n //\n // A mount node in this case contains portal's content and a virtual parent node. In this case nodes linking is redundant and the check below avoids it.\n //\n // Otherwise, there is a circular reference - both elements are parents of each other:\n // elementsContains(mountNode, virtualParent) === true\n // elementsContains(virtualParent, mountNode) === true\n const isVirtualParentInsideChild = mountNode.contains(virtualParent);\n\n if (virtualParent && !isVirtualParentInsideChild) {\n setVirtualParent(mountNode, virtualParent);\n\n return () => {\n setVirtualParent(mountNode, undefined);\n };\n }\n }, [virtualParentRootRef, mountNode]);\n\n return state;\n};\n"],"names":["setVirtualParent","React","toMountNodeProps","usePortalMountNode","usePortal_unstable","props","element","className","mountNode","virtualParentRootRef","useRef","fallbackElement","disabled","state","children","useEffect","virtualParent","current","isVirtualParentInsideChild","contains","undefined"],"mappings":"AAAA;AAEA,SAASA,gBAAgB,QAAQ,4BAA4B;AAC7D,YAAYC,WAAW,QAAQ;AAE/B,SAASC,gBAAgB,QAAQ,+BAA+B;AAChE,SAASC,kBAAkB,QAAQ,uBAAuB;AAG1D;;;;;;CAMC,GACD,OAAO,MAAMC,qBAAqB,CAACC;IACjC,MAAM,EAAEC,OAAO,EAAEC,SAAS,EAAE,GAAGL,iBAAiBG,MAAMG,SAAS;IAE/D,MAAMC,uBAAuBR,MAAMS,MAAM,CAAkB;IAC3D,MAAMC,kBAAkBR,mBAAmB;QAAES,UAAU,CAAC,CAACN;QAASC;IAAU;IAE5E,MAAMC,YAAYF,oBAAAA,qBAAAA,UAAWK;IAC7B,MAAME,QAAqB;QACzBC,UAAUT,MAAMS,QAAQ;QACxBN;QACAC;IACF;IAEAR,MAAMc,SAAS,CAAC;QACd,IAAI,CAACP,WAAW;YACd;QACF;QAEA,MAAMQ,gBAAgBP,qBAAqBQ,OAAO;QAElD,4HAA4H;QAC5H,EAAE;QACF,SAAS;QACT,iCAAiC;QACjC,oBAAoB;QACpB,iFAAiF;QACjF,uCAAuC;QACvC,WAAW;QACX,iCAAiC;QACjC,gCAAgC;QAChC,WAAW;QACX,UAAU;QACV,EAAE;QACF,4JAA4J;QAC5J,0CAA0C;QAC1C,2BAA2B;QAC3B,wDAAwD;QACxD,yDAAyD;QACzD,EAAE;QACF,yDAAyD;QACzD,EAAE;QACF,kGAAkG;QAClG,EAAE;QACF,SAAS;QACT,8BAA8B;QAC9B,oBAAoB;QACpB,mCAAmC;QACnC,kCAAkC;QAClC,EAAE;QACF,yCAAyC;QACzC,aAAa;QACb,WAAW;QACX,UAAU;QACV,EAAE;QACF,wJAAwJ;QACxJ,EAAE;QACF,sFAAsF;QACtF,wDAAwD;QACxD,wDAAwD;QACxD,MAAMC,6BAA6BV,UAAUW,QAAQ,CAACH;QAEtD,IAAIA,iBAAiB,CAACE,4BAA4B;YAChDlB,iBAAiBQ,WAAWQ;YAE5B,OAAO;gBACLhB,iBAAiBQ,WAAWY;YAC9B;QACF;IACF,GAAG;QAACX;QAAsBD;KAAU;IAEpC,OAAOK;AACT,EAAE"}

View File

@@ -0,0 +1,203 @@
'use client';
import * as React from 'react';
import { useThemeClassName_unstable as useThemeClassName, useFluent_unstable as useFluent, usePortalMountNode as usePortalMountNodeContext } from '@fluentui/react-shared-contexts';
import { mergeClasses } from '@griffel/react';
import { useFocusVisible } from '@fluentui/react-tabster';
import { usePortalMountNodeStylesStyles } from './usePortalMountNodeStyles.styles';
const useInsertionEffect = React['useInsertion' + 'Effect'];
/**
* Legacy element factory for React 17 and below. It's not safe for concurrent rendering.
*
* Creates a new element on a "document.body" to mount portals.
*/ const useLegacyElementFactory = (options)=>{
'use no memo';
const { className, dir, focusVisibleRef, targetNode } = options;
const targetElement = React.useMemo(()=>{
if (targetNode === undefined || options.disabled) {
return null;
}
const element = targetNode.ownerDocument.createElement('div');
targetNode.appendChild(element);
return element;
}, [
targetNode,
options.disabled
]);
// Heads up!
// This useMemo() call is intentional for React 17 & below.
//
// We don't want to re-create the portal element when its attributes change. This also cannot not be done in an effect
// because, changing the value of CSS variables after an initial mount will trigger interesting CSS side effects like
// transitions.
React.useMemo(()=>{
if (!targetElement) {
return;
}
targetElement.className = className;
targetElement.setAttribute('dir', dir);
targetElement.setAttribute('data-portal-node', 'true');
focusVisibleRef.current = targetElement;
}, [
className,
dir,
targetElement,
focusVisibleRef
]);
React.useEffect(()=>{
return ()=>{
targetElement === null || targetElement === void 0 ? void 0 : targetElement.remove();
};
}, [
targetElement
]);
return targetElement;
};
const initializeElementFactory = ()=>{
let currentElement = undefined;
function get(targetRoot, forceCreation) {
if (currentElement) {
return currentElement;
}
if (forceCreation) {
currentElement = targetRoot.ownerDocument.createElement('div');
targetRoot.appendChild(currentElement);
}
return currentElement;
}
function dispose() {
if (currentElement) {
currentElement.remove();
currentElement = undefined;
}
}
return {
get,
dispose
};
};
/**
* This is a modern element factory for React 18 and above. It is safe for concurrent rendering.
*
* It abuses the fact that React will mount DOM once (unlike hooks), so by using a proxy we can intercept:
* - the `remove()` method (we call it in `useEffect()`) and remove the element only when the portal is unmounted
* - all other methods (and properties) will be called by React once a portal is mounted
*/ const useModernElementFactory = (options)=>{
'use no memo';
const { className, dir, focusVisibleRef, targetNode } = options;
const [elementFactory] = React.useState(initializeElementFactory);
const elementProxy = React.useMemo(()=>{
if (targetNode === undefined || options.disabled) {
return null;
}
return new Proxy({}, {
get (_, property) {
// Heads up!
// `createPortal()` performs a check for `nodeType` property to determine if the mount node is a valid DOM node
// before mounting the portal. We hardcode the value to `Node.ELEMENT_NODE` to pass this check and avoid
// premature node creation
if (property === 'nodeType') {
// Can't use the `Node.ELEMENT_NODE` as it's a browser API and not available in all environments, e.g SSR
return 1; // `Node.ELEMENT_NODE`
}
// Heads up!
// We intercept the `remove()` method to remove the mount node only when portal has been unmounted already.
if (property === 'remove') {
const targetElement = elementFactory.get(targetNode, false);
if (targetElement) {
// If the mountElement has children, the portal is still mounted, otherwise we can dispose of it
const portalHasNoChildren = targetElement.childNodes.length === 0;
if (portalHasNoChildren) {
elementFactory.dispose();
}
}
return ()=>{
// Always return a no-op function to avoid errors in the code
};
}
const targetElement = elementFactory.get(targetNode, true);
const targetProperty = targetElement ? targetElement[property] : undefined;
if (typeof targetProperty === 'function') {
return targetProperty.bind(targetElement);
}
return targetProperty;
},
set (_, property, value) {
const ignoredProperty = property === '_virtual' || property === 'focusVisible';
// We should use the `elementFactory.get(targetNode, !ignoredProperty)`,
// but TypeScript requires a literal `true` or `false` for the overload signature.
// This workaround ensures the correct overload is called and avoids TypeScript errors.
const targetElement = ignoredProperty ? elementFactory.get(targetNode, false) : elementFactory.get(targetNode, true);
if (ignoredProperty && !targetElement) {
// We ignore the `_virtual` and `focusVisible` properties to avoid conflicts with the proxy
return true;
}
if (targetElement) {
Object.assign(targetElement, {
[property]: value
});
return true;
}
return false;
}
});
}, [
elementFactory,
targetNode,
options.disabled
]);
useInsertionEffect(()=>{
if (!elementProxy) {
return;
}
const classesToApply = className.split(' ').filter(Boolean);
elementProxy.classList.add(...classesToApply);
elementProxy.setAttribute('dir', dir);
elementProxy.setAttribute('data-portal-node', 'true');
focusVisibleRef.current = elementProxy;
return ()=>{
elementProxy.classList.remove(...classesToApply);
elementProxy.removeAttribute('dir');
};
}, [
className,
dir,
elementProxy,
focusVisibleRef
]);
React.useEffect(()=>{
return ()=>{
elementProxy === null || elementProxy === void 0 ? void 0 : elementProxy.remove();
};
}, [
elementProxy
]);
return elementProxy;
};
/**
* Element factory based on the React version.
*
* React 17 and below:
* - useLegacyElementFactory
*
* React 18 and above:
* - useModernElementFactory
*/ const useElementFactory = useInsertionEffect ? useModernElementFactory : useLegacyElementFactory;
/**
* Creates a new element on a "document.body" to mount portals.
*/ export const usePortalMountNode = (options)=>{
'use no memo';
const { targetDocument, dir } = useFluent();
const mountNode = usePortalMountNodeContext();
// eslint-disable-next-line @typescript-eslint/no-deprecated
const focusVisibleRef = useFocusVisible();
const classes = usePortalMountNodeStylesStyles();
const themeClassName = useThemeClassName();
const factoryOptions = {
dir,
disabled: options.disabled,
focusVisibleRef,
className: mergeClasses(themeClassName, classes.root, options.className),
targetNode: mountNode !== null && mountNode !== void 0 ? mountNode : targetDocument === null || targetDocument === void 0 ? void 0 : targetDocument.body
};
return useElementFactory(factoryOptions);
};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,14 @@
'use client';
import { __styles } from '@griffel/react';
export const usePortalMountNodeStylesStyles = /*#__PURE__*/__styles({
root: {
qhf8xq: "f1euv43f",
Bhzewxz: "f15twtuk",
oyh7mz: ["f1vgc2s3", "f1e31b4d"],
j35jbq: ["f1e31b4d", "f1vgc2s3"],
Bj3rh1h: "f494woh"
}
}, {
d: [".f1euv43f{position:absolute;}", ".f15twtuk{top:0;}", ".f1vgc2s3{left:0;}", ".f1e31b4d{right:0;}", ".f494woh{z-index:1000000;}"]
});

View File

@@ -0,0 +1 @@
{"version":3,"names":["__styles","usePortalMountNodeStylesStyles","root","qhf8xq","Bhzewxz","oyh7mz","j35jbq","Bj3rh1h","d"],"sources":["usePortalMountNodeStyles.styles.js"],"sourcesContent":["'use client';\nimport { makeStyles } from '@griffel/react';\nexport const usePortalMountNodeStylesStyles = makeStyles({\n root: {\n // Creates new stacking context to prevent z-index issues\n // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index/Stacking_context\n //\n // Also keeps a portal on top of a page to prevent scrollbars from appearing\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n zIndex: 1000000\n }\n});\n"],"mappings":"AAAA,YAAY;;AACZ,SAAAA,QAAA,QAA2B,gBAAgB;AAC3C,OAAO,MAAMC,8BAA8B,gBAAGD,QAAA;EAAAE,IAAA;IAAAC,MAAA;IAAAC,OAAA;IAAAC,MAAA;IAAAC,MAAA;IAAAC,OAAA;EAAA;AAAA;EAAAC,CAAA;AAAA,CAY7C,CAAC","ignoreList":[]}

View File

@@ -0,0 +1,15 @@
'use client';
import { makeStyles } from '@griffel/react';
export const usePortalMountNodeStylesStyles = makeStyles({
root: {
// Creates new stacking context to prevent z-index issues
// https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index/Stacking_context
//
// Also keeps a portal on top of a page to prevent scrollbars from appearing
position: 'absolute',
top: 0,
left: 0,
right: 0,
zIndex: 1000000
}
});

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Portal/usePortalMountNodeStyles.styles.ts"],"sourcesContent":["'use client';\n\nimport { makeStyles } from '@griffel/react';\n\nexport const usePortalMountNodeStylesStyles = makeStyles({\n root: {\n // Creates new stacking context to prevent z-index issues\n // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index/Stacking_context\n //\n // Also keeps a portal on top of a page to prevent scrollbars from appearing\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n\n zIndex: 1000000,\n },\n});\n"],"names":["makeStyles","usePortalMountNodeStylesStyles","root","position","top","left","right","zIndex"],"mappings":"AAAA;AAEA,SAASA,UAAU,QAAQ,iBAAiB;AAE5C,OAAO,MAAMC,iCAAiCD,WAAW;IACvDE,MAAM;QACJ,yDAAyD;QACzD,gHAAgH;QAChH,EAAE;QACF,4EAA4E;QAC5EC,UAAU;QACVC,KAAK;QACLC,MAAM;QACNC,OAAO;QAEPC,QAAQ;IACV;AACF,GAAG"}