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,13 @@
'use client';
import * as React from 'react';
import { renderAriaLiveAnnouncer_unstable } from './renderAriaLiveAnnouncer';
import { useAriaLiveAnnouncer_unstable } from './useAriaLiveAnnouncer';
import { useAriaLiveAnnouncerContextValues_unstable } from './useAriaLiveAnnouncerContextValues';
/**
* A sample implementation of a component that manages aria live announcements.
*/ export const AriaLiveAnnouncer = (props)=>{
const state = useAriaLiveAnnouncer_unstable(props);
const contextValues = useAriaLiveAnnouncerContextValues_unstable(state);
return renderAriaLiveAnnouncer_unstable(state, contextValues);
};
AriaLiveAnnouncer.displayName = 'AriaLiveAnnouncer';

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/AriaLiveAnnouncer/AriaLiveAnnouncer.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\n\nimport type { AriaLiveAnnouncerProps } from './AriaLiveAnnouncer.types';\nimport { renderAriaLiveAnnouncer_unstable } from './renderAriaLiveAnnouncer';\nimport { useAriaLiveAnnouncer_unstable } from './useAriaLiveAnnouncer';\nimport { useAriaLiveAnnouncerContextValues_unstable } from './useAriaLiveAnnouncerContextValues';\n\n/**\n * A sample implementation of a component that manages aria live announcements.\n */\nexport const AriaLiveAnnouncer: React.FC<AriaLiveAnnouncerProps> = props => {\n const state = useAriaLiveAnnouncer_unstable(props);\n const contextValues = useAriaLiveAnnouncerContextValues_unstable(state);\n\n return renderAriaLiveAnnouncer_unstable(state, contextValues);\n};\n\nAriaLiveAnnouncer.displayName = 'AriaLiveAnnouncer';\n"],"names":["React","renderAriaLiveAnnouncer_unstable","useAriaLiveAnnouncer_unstable","useAriaLiveAnnouncerContextValues_unstable","AriaLiveAnnouncer","props","state","contextValues","displayName"],"mappings":"AAAA;AAEA,YAAYA,WAAW,QAAQ;AAG/B,SAASC,gCAAgC,QAAQ,4BAA4B;AAC7E,SAASC,6BAA6B,QAAQ,yBAAyB;AACvE,SAASC,0CAA0C,QAAQ,sCAAsC;AAEjG;;CAEC,GACD,OAAO,MAAMC,oBAAsDC,CAAAA;IACjE,MAAMC,QAAQJ,8BAA8BG;IAC5C,MAAME,gBAAgBJ,2CAA2CG;IAEjE,OAAOL,iCAAiCK,OAAOC;AACjD,EAAE;AAEFH,kBAAkBI,WAAW,GAAG"}

View File

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

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/AriaLiveAnnouncer/AriaLiveAnnouncer.types.ts"],"sourcesContent":["import type { AnnounceContextValue } from '@fluentui/react-shared-contexts';\nimport * as React from 'react';\n\nexport type AriaLiveAnnounceFn = AnnounceContextValue['announce'];\n\nexport type AriaLiveMessage = {\n message: string;\n\n createdAt: number;\n\n priority: number;\n batchId?: string;\n};\n\nexport type AriaLiveAnnouncerProps = {\n children?: React.ReactNode;\n};\n\nexport type AriaLiveAnnouncerState = {\n announce: AriaLiveAnnounceFn;\n children?: React.ReactNode;\n};\n\nexport type AriaLiveAnnouncerContextValues = {\n announce: { announce: AriaLiveAnnounceFn };\n};\n"],"names":["React"],"mappings":"AACA,YAAYA,WAAW,QAAQ"}

View File

@@ -0,0 +1,4 @@
export { AriaLiveAnnouncer } from './AriaLiveAnnouncer';
export { renderAriaLiveAnnouncer_unstable } from './renderAriaLiveAnnouncer';
export { useAriaLiveAnnouncer_unstable } from './useAriaLiveAnnouncer';
export { useAriaLiveAnnouncerContextValues_unstable } from './useAriaLiveAnnouncerContextValues';

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/AriaLiveAnnouncer/index.ts"],"sourcesContent":["export { AriaLiveAnnouncer } from './AriaLiveAnnouncer';\nexport type { AriaLiveAnnouncerProps, AriaLiveAnnouncerState } from './AriaLiveAnnouncer.types';\nexport { renderAriaLiveAnnouncer_unstable } from './renderAriaLiveAnnouncer';\nexport { useAriaLiveAnnouncer_unstable } from './useAriaLiveAnnouncer';\nexport { useAriaLiveAnnouncerContextValues_unstable } from './useAriaLiveAnnouncerContextValues';\n"],"names":["AriaLiveAnnouncer","renderAriaLiveAnnouncer_unstable","useAriaLiveAnnouncer_unstable","useAriaLiveAnnouncerContextValues_unstable"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,sBAAsB;AAExD,SAASC,gCAAgC,QAAQ,4BAA4B;AAC7E,SAASC,6BAA6B,QAAQ,yBAAyB;AACvE,SAASC,0CAA0C,QAAQ,sCAAsC"}

View File

@@ -0,0 +1,7 @@
import * as React from 'react';
import { AnnounceProvider } from '@fluentui/react-shared-contexts';
export const renderAriaLiveAnnouncer_unstable = (state, contextValues)=>{
return /*#__PURE__*/ React.createElement(AnnounceProvider, {
value: contextValues.announce
}, state.children);
};

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/AriaLiveAnnouncer/renderAriaLiveAnnouncer.tsx"],"sourcesContent":["import * as React from 'react';\nimport { AnnounceProvider } from '@fluentui/react-shared-contexts';\n\nimport type { AriaLiveAnnouncerContextValues, AriaLiveAnnouncerState } from './AriaLiveAnnouncer.types';\nimport type { JSXElement } from '@fluentui/react-utilities';\n\nexport const renderAriaLiveAnnouncer_unstable = (\n state: AriaLiveAnnouncerState,\n contextValues: AriaLiveAnnouncerContextValues,\n): JSXElement => {\n return <AnnounceProvider value={contextValues.announce}>{state.children}</AnnounceProvider>;\n};\n"],"names":["React","AnnounceProvider","renderAriaLiveAnnouncer_unstable","state","contextValues","value","announce","children"],"mappings":"AAAA,YAAYA,WAAW,QAAQ;AAC/B,SAASC,gBAAgB,QAAQ,kCAAkC;AAKnE,OAAO,MAAMC,mCAAmC,CAC9CC,OACAC;IAEA,qBAAO,oBAACH;QAAiBI,OAAOD,cAAcE,QAAQ;OAAGH,MAAMI,QAAQ;AACzE,EAAE"}

View File

@@ -0,0 +1,23 @@
'use client';
import * as React from 'react';
import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';
import { useDomAnnounce_unstable } from './useDomAnnounce';
import { useAriaNotifyAnnounce_unstable } from './useAriaNotifyAnnounce';
export const useAriaLiveAnnouncer_unstable = (props)=>{
const { targetDocument } = useFluent();
const domAnnounce = useDomAnnounce_unstable();
const ariaNotifyAnnounce = useAriaNotifyAnnounce_unstable();
const announce = React.useMemo(()=>{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const supportsAriaNotify = typeof (targetDocument === null || targetDocument === void 0 ? void 0 : targetDocument.ariaNotify) === 'function';
return supportsAriaNotify ? ariaNotifyAnnounce : domAnnounce;
}, [
targetDocument,
ariaNotifyAnnounce,
domAnnounce
]);
return {
announce,
children: props.children
};
};

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/AriaLiveAnnouncer/useAriaLiveAnnouncer.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';\nimport { useDomAnnounce_unstable } from './useDomAnnounce';\nimport { useAriaNotifyAnnounce_unstable } from './useAriaNotifyAnnounce';\n\nimport type { AriaLiveAnnouncerState, AriaLiveAnnouncerProps } from './AriaLiveAnnouncer.types';\n\nexport const useAriaLiveAnnouncer_unstable = (props: AriaLiveAnnouncerProps): AriaLiveAnnouncerState => {\n const { targetDocument } = useFluent();\n const domAnnounce = useDomAnnounce_unstable();\n const ariaNotifyAnnounce = useAriaNotifyAnnounce_unstable();\n\n const announce = React.useMemo(() => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const supportsAriaNotify = typeof (targetDocument as any)?.ariaNotify === 'function';\n return supportsAriaNotify ? ariaNotifyAnnounce : domAnnounce;\n }, [targetDocument, ariaNotifyAnnounce, domAnnounce]);\n\n return {\n announce,\n children: props.children,\n };\n};\n"],"names":["React","useFluent_unstable","useFluent","useDomAnnounce_unstable","useAriaNotifyAnnounce_unstable","useAriaLiveAnnouncer_unstable","props","targetDocument","domAnnounce","ariaNotifyAnnounce","announce","useMemo","supportsAriaNotify","ariaNotify","children"],"mappings":"AAAA;AAEA,YAAYA,WAAW,QAAQ;AAC/B,SAASC,sBAAsBC,SAAS,QAAQ,kCAAkC;AAClF,SAASC,uBAAuB,QAAQ,mBAAmB;AAC3D,SAASC,8BAA8B,QAAQ,0BAA0B;AAIzE,OAAO,MAAMC,gCAAgC,CAACC;IAC5C,MAAM,EAAEC,cAAc,EAAE,GAAGL;IAC3B,MAAMM,cAAcL;IACpB,MAAMM,qBAAqBL;IAE3B,MAAMM,WAAWV,MAAMW,OAAO,CAAC;QAC7B,8DAA8D;QAC9D,MAAMC,qBAAqB,QAAQL,2BAAAA,qCAAD,AAACA,eAAwBM,UAAU,MAAK;QAC1E,OAAOD,qBAAqBH,qBAAqBD;IACnD,GAAG;QAACD;QAAgBE;QAAoBD;KAAY;IAEpD,OAAO;QACLE;QACAI,UAAUR,MAAMQ,QAAQ;IAC1B;AACF,EAAE"}

View File

@@ -0,0 +1,12 @@
'use client';
import * as React from 'react';
export function useAriaLiveAnnouncerContextValues_unstable(state) {
const { announce } = state;
return React.useMemo(()=>({
announce: {
announce
}
}), [
announce
]);
}

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/AriaLiveAnnouncer/useAriaLiveAnnouncerContextValues.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type { AriaLiveAnnouncerContextValues, AriaLiveAnnouncerState } from './AriaLiveAnnouncer.types';\n\nexport function useAriaLiveAnnouncerContextValues_unstable(\n state: AriaLiveAnnouncerState,\n): AriaLiveAnnouncerContextValues {\n const { announce } = state;\n\n return React.useMemo(() => ({ announce: { announce } }), [announce]);\n}\n"],"names":["React","useAriaLiveAnnouncerContextValues_unstable","state","announce","useMemo"],"mappings":"AAAA;AAEA,YAAYA,WAAW,QAAQ;AAG/B,OAAO,SAASC,2CACdC,KAA6B;IAE7B,MAAM,EAAEC,QAAQ,EAAE,GAAGD;IAErB,OAAOF,MAAMI,OAAO,CAAC,IAAO,CAAA;YAAED,UAAU;gBAAEA;YAAS;QAAE,CAAA,GAAI;QAACA;KAAS;AACrE"}

View File

@@ -0,0 +1,25 @@
'use client';
import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';
import * as React from 'react';
/* INTERNAL: implementation of the announcer using the ariaNotify API */ export const useAriaNotifyAnnounce_unstable = ()=>{
const { targetDocument } = useFluent();
const announce = React.useCallback((message, options = {})=>{
if (!targetDocument) {
return;
}
const { alert = false, polite } = options;
// default priority to 0 if polite, 2 if alert, and 1 by default
// used to set both ariaNotify's priority and interrupt
const defaultPriority = polite ? 0 : alert ? 2 : 1;
var _options_priority;
const priority = (_options_priority = options.priority) !== null && _options_priority !== void 0 ? _options_priority : defaultPriority;
// map fluent announce options to ariaNotify options
const ariaNotifyOptions = {
priority: priority > 1 ? 'high' : 'normal'
};
targetDocument.ariaNotify(message, ariaNotifyOptions);
}, [
targetDocument
]);
return announce;
};

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/AriaLiveAnnouncer/useAriaNotifyAnnounce.ts"],"sourcesContent":["'use client';\n\nimport { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';\nimport type { AnnounceOptions } from '@fluentui/react-shared-contexts';\nimport * as React from 'react';\n\nimport type { AriaLiveAnnounceFn } from './AriaLiveAnnouncer.types';\n\ntype AriaNotifyOptions = {\n priority?: 'high' | 'normal';\n};\n\ntype DocumentWithAriaNotify = Document & {\n ariaNotify: (message: string, options: AriaNotifyOptions) => void;\n};\n\n/* INTERNAL: implementation of the announcer using the ariaNotify API */\nexport const useAriaNotifyAnnounce_unstable = (): AriaLiveAnnounceFn => {\n const { targetDocument } = useFluent();\n\n const announce: AriaLiveAnnounceFn = React.useCallback(\n (message: string, options: AnnounceOptions = {}) => {\n if (!targetDocument) {\n return;\n }\n\n const { alert = false, polite } = options;\n\n // default priority to 0 if polite, 2 if alert, and 1 by default\n // used to set both ariaNotify's priority and interrupt\n const defaultPriority = polite ? 0 : alert ? 2 : 1;\n const priority = options.priority ?? defaultPriority;\n\n // map fluent announce options to ariaNotify options\n const ariaNotifyOptions: AriaNotifyOptions = {\n priority: priority > 1 ? 'high' : 'normal',\n };\n\n (targetDocument as DocumentWithAriaNotify).ariaNotify(message, ariaNotifyOptions);\n },\n [targetDocument],\n );\n\n return announce;\n};\n"],"names":["useFluent_unstable","useFluent","React","useAriaNotifyAnnounce_unstable","targetDocument","announce","useCallback","message","options","alert","polite","defaultPriority","priority","ariaNotifyOptions","ariaNotify"],"mappings":"AAAA;AAEA,SAASA,sBAAsBC,SAAS,QAAQ,kCAAkC;AAElF,YAAYC,WAAW,QAAQ;AAY/B,sEAAsE,GACtE,OAAO,MAAMC,iCAAiC;IAC5C,MAAM,EAAEC,cAAc,EAAE,GAAGH;IAE3B,MAAMI,WAA+BH,MAAMI,WAAW,CACpD,CAACC,SAAiBC,UAA2B,CAAC,CAAC;QAC7C,IAAI,CAACJ,gBAAgB;YACnB;QACF;QAEA,MAAM,EAAEK,QAAQ,KAAK,EAAEC,MAAM,EAAE,GAAGF;QAElC,gEAAgE;QAChE,uDAAuD;QACvD,MAAMG,kBAAkBD,SAAS,IAAID,QAAQ,IAAI;YAChCD;QAAjB,MAAMI,WAAWJ,CAAAA,oBAAAA,QAAQI,QAAQ,cAAhBJ,+BAAAA,oBAAoBG;QAErC,oDAAoD;QACpD,MAAME,oBAAuC;YAC3CD,UAAUA,WAAW,IAAI,SAAS;QACpC;QAECR,eAA0CU,UAAU,CAACP,SAASM;IACjE,GACA;QAACT;KAAe;IAGlB,OAAOC;AACT,EAAE"}

View File

@@ -0,0 +1,134 @@
'use client';
import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';
import { createPriorityQueue, useTimeout } from '@fluentui/react-utilities';
import { useDangerousNeverHidden_unstable as useDangerousNeverHidden } from '@fluentui/react-tabster';
import * as React from 'react';
/** The duration the message needs to be in present in DOM for screen readers to register a change and announce */ const MESSAGE_DURATION = 500;
const VISUALLY_HIDDEN_STYLES = {
clip: 'rect(0px, 0px, 0px, 0px)',
height: '1px',
margin: '-1px',
width: '1px',
position: 'absolute',
overflow: 'hidden',
textWrap: 'nowrap'
};
/* INTERNAL: implementation of the announcer using a live region element */ export const useDomAnnounce_unstable = ()=>{
const { targetDocument } = useFluent();
const timeoutRef = React.useRef(undefined);
const [setAnnounceTimeout, clearAnnounceTimeout] = useTimeout();
const tabsterNeverHiddenAttributes = useDangerousNeverHidden();
const elementRef = React.useRef(null);
const order = React.useRef(0);
// investigate alert implementation later
// const [alertList, setAlertList] = React.useState<string[]>([]);
const batchMessages = React.useRef([]);
const [messageQueue] = React.useState(()=>createPriorityQueue((a, b)=>{
if (a.priority !== b.priority) {
return b.priority - a.priority;
}
return a.createdAt - b.createdAt;
}));
const queueMessage = React.useCallback(()=>{
if (timeoutRef.current || !elementRef.current) {
return;
}
const runCycle = ()=>{
if (!elementRef.current) {
return;
}
if (targetDocument && messageQueue.peek()) {
// need a wrapping element for Narrator/Edge, which currently does not pick up text-only live region changes
// consistently
// if this is fixed, we can set textContent to the string directly
const wrappingEl = targetDocument.createElement('span');
wrappingEl.innerText = messageQueue.all().filter((msg)=>msg.message.trim().length > 0).reduce((prevText, currMsg)=>prevText + currMsg.message + '. ', '');
elementRef.current.innerText = '';
elementRef.current.appendChild(wrappingEl);
messageQueue.clear();
batchMessages.current = [];
// begin new cycle to clear (or update) messages
timeoutRef.current = setAnnounceTimeout(()=>{
runCycle();
}, MESSAGE_DURATION);
} else {
elementRef.current.textContent = '';
clearAnnounceTimeout();
timeoutRef.current = undefined;
}
};
// Run the first cycle with a 0 timeout to ensure multiple messages in the same tick are handled
timeoutRef.current = setAnnounceTimeout(()=>{
runCycle();
}, 0);
}, [
clearAnnounceTimeout,
messageQueue,
setAnnounceTimeout,
targetDocument
]);
const announce = React.useCallback((message, options = {})=>{
const { alert = false, priority = 0, batchId } = options;
// check if message is an alert
if (alert) {
// TODO: alert implementation
// setAlertList([...alertList, message]);
}
const liveMessage = {
message,
createdAt: order.current++,
priority,
batchId
};
// check if batchId exists
if (batchId) {
// update associated msg if it does
const batchMessage = batchMessages.current.find((msg)=>msg.batchId === batchId);
if (batchMessage) {
// replace existing message in queue
messageQueue.remove(batchMessage.message);
// update list of existing batchIds w/ most recent message
batchMessage.message = liveMessage;
} else {
// update list of existing batchIds, add new if doesn't already exist
batchMessages.current = [
...batchMessages.current,
{
batchId,
message: liveMessage
}
];
}
}
// add new message
messageQueue.enqueue(liveMessage);
queueMessage();
}, [
messageQueue,
queueMessage
]);
React.useEffect(()=>{
if (!targetDocument) {
return;
}
const element = targetDocument.createElement('div');
element.setAttribute('aria-live', 'assertive');
Object.entries(tabsterNeverHiddenAttributes).forEach(([key, value])=>{
element.setAttribute(key, value);
});
Object.assign(element.style, VISUALLY_HIDDEN_STYLES);
targetDocument.body.append(element);
elementRef.current = element;
return ()=>{
element.remove();
elementRef.current = null;
clearAnnounceTimeout();
timeoutRef.current = undefined;
};
}, [
clearAnnounceTimeout,
tabsterNeverHiddenAttributes,
targetDocument
]);
return announce;
};

File diff suppressed because one or more lines are too long