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,15 @@
'use client';
import * as React from 'react';
import { useProgressBar_unstable } from './useProgressBar';
import { renderProgressBar_unstable } from './renderProgressBar';
import { useProgressBarStyles_unstable } from './useProgressBarStyles.styles';
import { useCustomStyleHook_unstable } from '@fluentui/react-shared-contexts';
/**
* A ProgressBar bar shows the progression of a task.
*/ export const ProgressBar = /*#__PURE__*/ React.forwardRef((props, ref)=>{
const state = useProgressBar_unstable(props, ref);
useProgressBarStyles_unstable(state);
useCustomStyleHook_unstable('useProgressBarStyles_unstable')(state);
return renderProgressBar_unstable(state);
});
ProgressBar.displayName = 'ProgressBar';

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/ProgressBar/ProgressBar.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { useProgressBar_unstable } from './useProgressBar';\nimport { renderProgressBar_unstable } from './renderProgressBar';\nimport { useProgressBarStyles_unstable } from './useProgressBarStyles.styles';\nimport type { ProgressBarProps } from './ProgressBar.types';\nimport type { ForwardRefComponent } from '@fluentui/react-utilities';\nimport { useCustomStyleHook_unstable } from '@fluentui/react-shared-contexts';\n\n/**\n * A ProgressBar bar shows the progression of a task.\n */\nexport const ProgressBar: ForwardRefComponent<ProgressBarProps> = React.forwardRef((props, ref) => {\n const state = useProgressBar_unstable(props, ref);\n\n useProgressBarStyles_unstable(state);\n\n useCustomStyleHook_unstable('useProgressBarStyles_unstable')(state);\n\n return renderProgressBar_unstable(state);\n});\n\nProgressBar.displayName = 'ProgressBar';\n"],"names":["React","useProgressBar_unstable","renderProgressBar_unstable","useProgressBarStyles_unstable","useCustomStyleHook_unstable","ProgressBar","forwardRef","props","ref","state","displayName"],"mappings":"AAAA;AAEA,YAAYA,WAAW,QAAQ;AAC/B,SAASC,uBAAuB,QAAQ,mBAAmB;AAC3D,SAASC,0BAA0B,QAAQ,sBAAsB;AACjE,SAASC,6BAA6B,QAAQ,gCAAgC;AAG9E,SAASC,2BAA2B,QAAQ,kCAAkC;AAE9E;;CAEC,GACD,OAAO,MAAMC,4BAAqDL,MAAMM,UAAU,CAAC,CAACC,OAAOC;IACzF,MAAMC,QAAQR,wBAAwBM,OAAOC;IAE7CL,8BAA8BM;IAE9BL,4BAA4B,iCAAiCK;IAE7D,OAAOP,2BAA2BO;AACpC,GAAG;AAEHJ,YAAYK,WAAW,GAAG"}

View File

@@ -0,0 +1,3 @@
/**
* ProgressBar base state — excludes design props (shape, thickness, color).
*/ export { };

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/ProgressBar/ProgressBar.types.ts"],"sourcesContent":["import type { MotionSlotProps } from '@fluentui/react-motion';\nimport type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';\n\nexport type ProgressBarSlots = {\n /**\n * The track behind the ProgressBar bar\n */\n root: NonNullable<Slot<'div'>>;\n /**\n * The filled portion of the ProgressBar bar. Animated in the indeterminate state, when no value is provided.\n */\n bar?: NonNullable<Slot<'div'>>;\n /**\n * Motion slot for the indeterminate animation. Pass `null` to disable the animation.\n */\n indeterminateMotion?: Slot<MotionSlotProps>;\n};\n\n/**\n * ProgressBar Props\n */\nexport type ProgressBarProps = Omit<ComponentProps<ProgressBarSlots>, 'size'> & {\n /**\n * The shape of the bar and track.\n * @default rounded\n */\n shape?: 'rounded' | 'square';\n /**\n * A decimal number between `0` and `1` (or between `0` and `max` if given),\n * which specifies how much of the task has been completed.\n *\n * If `undefined` (default), the ProgressBar will display an **indeterminate** state.\n */\n value?: number;\n /**\n * The maximum value, which indicates the task is complete.\n * The ProgressBar bar will be full when `value` equals `max`.\n * @default 1\n */\n max?: number;\n /**\n * The thickness of the ProgressBar bar\n * @default medium\n */\n thickness?: 'medium' | 'large';\n\n /**\n * The status of the ProgressBar bar. Changes the color of the bar.\n * @default brand\n */\n color?: 'brand' | 'success' | 'warning' | 'error';\n};\n\n/**\n * ProgressBar base props — excludes design props (shape, thickness, color).\n */\nexport type ProgressBarBaseProps = Omit<ProgressBarProps, 'shape' | 'thickness' | 'color' | 'indeterminateMotion'>;\n\n/**\n * State used in rendering ProgressBar\n */\nexport type ProgressBarState = ComponentState<Required<ProgressBarSlots>> &\n Required<Pick<ProgressBarProps, 'max' | 'shape' | 'thickness'>> &\n Pick<ProgressBarProps, 'value' | 'color'>;\n\n/**\n * ProgressBar base state — excludes design props (shape, thickness, color).\n */\nexport type ProgressBarBaseState = Omit<ProgressBarState, 'shape' | 'thickness' | 'color' | 'indeterminateMotion'>;\n"],"names":[],"mappings":"AAiEA;;CAEC,GACD,WAAmH"}

View File

@@ -0,0 +1,4 @@
export { ProgressBar } from './ProgressBar';
export { renderProgressBar_unstable } from './renderProgressBar';
export { useProgressBar_unstable, useProgressBarBase_unstable } from './useProgressBar';
export { progressBarClassNames, useProgressBarStyles_unstable } from './useProgressBarStyles.styles';

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/ProgressBar/index.ts"],"sourcesContent":["export { ProgressBar } from './ProgressBar';\nexport type {\n ProgressBarProps,\n ProgressBarBaseProps,\n ProgressBarSlots,\n ProgressBarState,\n ProgressBarBaseState,\n} from './ProgressBar.types';\nexport { renderProgressBar_unstable } from './renderProgressBar';\nexport { useProgressBar_unstable, useProgressBarBase_unstable } from './useProgressBar';\nexport { progressBarClassNames, useProgressBarStyles_unstable } from './useProgressBarStyles.styles';\n"],"names":["ProgressBar","renderProgressBar_unstable","useProgressBar_unstable","useProgressBarBase_unstable","progressBarClassNames","useProgressBarStyles_unstable"],"mappings":"AAAA,SAASA,WAAW,QAAQ,gBAAgB;AAQ5C,SAASC,0BAA0B,QAAQ,sBAAsB;AACjE,SAASC,uBAAuB,EAAEC,2BAA2B,QAAQ,mBAAmB;AACxF,SAASC,qBAAqB,EAAEC,6BAA6B,QAAQ,gCAAgC"}

View File

@@ -0,0 +1,37 @@
import { createMotionComponent, motionTokens } from '@fluentui/react-motion';
/**
* Motion component for the indeterminate ProgressBar bar:
* a horizontal sliding animation that loops indefinitely.
* In reduced motion mode, the bar pulses opacity instead of sliding.
*/ export const ProgressBarIndeterminateMotion = createMotionComponent({
// translate percentages are relative to the element's own width, not the container's.
// The indeterminate bar is ~33% the width of its container, so:
// translate: '-100%' === left: '-33%' (one bar-width off-screen to the left)
// translate: '300%' === left: '100%' (3 × bar-width ≈ full container width, off-screen to the right)
keyframes: [
{
translate: '-100%'
},
{
translate: '300%'
}
],
duration: 3000,
iterations: Infinity,
easing: motionTokens.curveLinear,
reducedMotion: {
keyframes: [
{
opacity: 0.2
},
{
opacity: 1
},
{
opacity: 0.2
}
],
duration: 3000,
iterations: Infinity
}
});

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/ProgressBar/progressBarMotions.ts"],"sourcesContent":["import { createMotionComponent, motionTokens } from '@fluentui/react-motion';\n\n/**\n * Motion component for the indeterminate ProgressBar bar:\n * a horizontal sliding animation that loops indefinitely.\n * In reduced motion mode, the bar pulses opacity instead of sliding.\n */\nexport const ProgressBarIndeterminateMotion = createMotionComponent({\n // translate percentages are relative to the element's own width, not the container's.\n // The indeterminate bar is ~33% the width of its container, so:\n // translate: '-100%' === left: '-33%' (one bar-width off-screen to the left)\n // translate: '300%' === left: '100%' (3 × bar-width ≈ full container width, off-screen to the right)\n keyframes: [{ translate: '-100%' }, { translate: '300%' }],\n duration: 3000,\n iterations: Infinity,\n easing: motionTokens.curveLinear,\n\n reducedMotion: {\n keyframes: [{ opacity: 0.2 }, { opacity: 1 }, { opacity: 0.2 }],\n duration: 3000,\n iterations: Infinity,\n },\n});\n"],"names":["createMotionComponent","motionTokens","ProgressBarIndeterminateMotion","keyframes","translate","duration","iterations","Infinity","easing","curveLinear","reducedMotion","opacity"],"mappings":"AAAA,SAASA,qBAAqB,EAAEC,YAAY,QAAQ,yBAAyB;AAE7E;;;;CAIC,GACD,OAAO,MAAMC,iCAAiCF,sBAAsB;IAClE,sFAAsF;IACtF,gEAAgE;IAChE,+EAA+E;IAC/E,wGAAwG;IACxGG,WAAW;QAAC;YAAEC,WAAW;QAAQ;QAAG;YAAEA,WAAW;QAAO;KAAE;IAC1DC,UAAU;IACVC,YAAYC;IACZC,QAAQP,aAAaQ,WAAW;IAEhCC,eAAe;QACbP,WAAW;YAAC;gBAAEQ,SAAS;YAAI;YAAG;gBAAEA,SAAS;YAAE;YAAG;gBAAEA,SAAS;YAAI;SAAE;QAC/DN,UAAU;QACVC,YAAYC;IACd;AACF,GAAG"}

View File

@@ -0,0 +1,12 @@
import { jsx as _jsx } from "@fluentui/react-jsx-runtime/jsx-runtime";
import { assertSlots } from '@fluentui/react-utilities';
/**
* Render the final JSX of ProgressBar
*/ export const renderProgressBar_unstable = (state)=>{
assertSlots(state);
return /*#__PURE__*/ _jsx(state.root, {
children: state.bar && (state.indeterminateMotion ? /*#__PURE__*/ _jsx(state.indeterminateMotion, {
children: /*#__PURE__*/ _jsx(state.bar, {})
}) : /*#__PURE__*/ _jsx(state.bar, {}))
});
};

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/ProgressBar/renderProgressBar.tsx"],"sourcesContent":["/** @jsxRuntime automatic */\n/** @jsxImportSource @fluentui/react-jsx-runtime */\n\nimport { assertSlots } from '@fluentui/react-utilities';\nimport type { JSXElement } from '@fluentui/react-utilities';\nimport type { ProgressBarBaseState, ProgressBarSlots } from './ProgressBar.types';\n\n/**\n * Render the final JSX of ProgressBar\n */\nexport const renderProgressBar_unstable = (state: ProgressBarBaseState): JSXElement => {\n assertSlots<ProgressBarSlots>(state);\n return (\n <state.root>\n {state.bar &&\n (state.indeterminateMotion ? (\n <state.indeterminateMotion>\n <state.bar />\n </state.indeterminateMotion>\n ) : (\n <state.bar />\n ))}\n </state.root>\n );\n};\n"],"names":["assertSlots","renderProgressBar_unstable","state","root","bar","indeterminateMotion"],"mappings":"AAAA,0BAA0B,GAC1B,iDAAiD;AAEjD,SAASA,WAAW,QAAQ,4BAA4B;AAIxD;;CAEC,GACD,OAAO,MAAMC,6BAA6B,CAACC;IACzCF,YAA8BE;IAC9B,qBACE,KAACA,MAAMC,IAAI;kBACRD,MAAME,GAAG,IACPF,CAAAA,MAAMG,mBAAmB,iBACxB,KAACH,MAAMG,mBAAmB;sBACxB,cAAA,KAACH,MAAME,GAAG;2BAGZ,KAACF,MAAME,GAAG,KACZ;;AAGR,EAAE"}

View File

@@ -0,0 +1,80 @@
'use client';
import * as React from 'react';
import { useFieldContext_unstable } from '@fluentui/react-field';
import { motionSlot } from '@fluentui/react-motion';
import { getIntrinsicElementProps, slot } from '@fluentui/react-utilities';
import { clampValue, clampMax } from '../../utils/index';
import { ProgressBarIndeterminateMotion } from './progressBarMotions';
/**
* Create the state required to render ProgressBar.
*
* The returned state can be modified with hooks such as useProgressBarStyles_unstable,
* before being passed to renderProgressBar_unstable.
*
* @param props - props from this instance of ProgressBar
* @param ref - reference to root HTMLElement of ProgressBar
*/ export const useProgressBar_unstable = (props, ref)=>{
const field = useFieldContext_unstable();
const fieldState = field === null || field === void 0 ? void 0 : field.validationState;
const { color = fieldState === 'error' || fieldState === 'warning' || fieldState === 'success' ? fieldState : 'brand', shape = 'rounded', thickness = 'medium', indeterminateMotion, ...baseProps } = props;
const state = useProgressBarBase_unstable(baseProps, ref);
return {
...state,
color,
shape,
thickness,
indeterminateMotion: state.value === undefined ? motionSlot(indeterminateMotion, {
elementType: ProgressBarIndeterminateMotion,
defaultProps: {}
}) : undefined
};
};
/**
* Base hook for ProgressBar component. Manages state related to ARIA progressbar attributes
* (role, aria-valuemin, aria-valuemax, aria-valuenow) and field context integration —
* without design props (shape, thickness, color).
*
* @param props - props from this instance of ProgressBar (without shape, thickness, color)
* @param ref - reference to root HTMLElement of ProgressBar
*/ export const useProgressBarBase_unstable = (props, ref)=>{
const field = useFieldContext_unstable();
var _props_max;
const max = clampMax((_props_max = props.max) !== null && _props_max !== void 0 ? _props_max : 1);
const value = clampValue(props.value, max);
const root = slot.always(getIntrinsicElementProps('div', {
// FIXME:
// `ref` is wrongly assigned to be `HTMLElement` instead of `HTMLDivElement`
// but since it would be a breaking change to fix it, we are casting ref to it's proper type
ref: ref,
role: 'progressbar',
'aria-valuemin': value !== undefined ? 0 : undefined,
'aria-valuemax': value !== undefined ? max : undefined,
'aria-valuenow': value,
'aria-labelledby': field === null || field === void 0 ? void 0 : field.labelId,
...props
}), {
elementType: 'div'
});
if (field && (field.validationMessageId || field.hintId)) {
// Prepend the field's validation message and/or hint to the user's aria-describedby
root['aria-describedby'] = [
field === null || field === void 0 ? void 0 : field.validationMessageId,
field === null || field === void 0 ? void 0 : field.hintId,
root['aria-describedby']
].filter(Boolean).join(' ');
}
const bar = slot.always(props.bar, {
elementType: 'div'
});
return {
max,
value,
components: {
root: 'div',
bar: 'div',
indeterminateMotion: ProgressBarIndeterminateMotion
},
root,
bar
};
};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,133 @@
'use client';
import { __styles, mergeClasses } from '@griffel/react';
import { tokens } from '@fluentui/react-theme';
export const progressBarClassNames = {
root: 'fui-ProgressBar',
bar: 'fui-ProgressBar__bar'
};
// If the percentComplete is near 0, don't animate it.
// This prevents animations on reset to 0 scenarios.
const ZERO_THRESHOLD = 0.01;
const barThicknessValues = {
medium: '2px',
large: '4px'
};
/**
* Styles for the root slot
*/
const useRootStyles = /*#__PURE__*/__styles({
root: {
mc9l5x: "ftgm304",
De3pzq: "f18f03hv",
a9b677: "fly5x3f",
B68tc82: 0,
Bmxbyg5: 0,
Bpg54ce: "f1a3p1vp",
Bomf52o: "f1skxd4g"
},
rounded: {
Beyfa6y: 0,
Bbmb7ep: 0,
Btl43ni: 0,
B7oj6ja: 0,
Dimara: "ft85np5"
},
square: {
Beyfa6y: 0,
Bbmb7ep: 0,
Btl43ni: 0,
B7oj6ja: 0,
Dimara: "f1fabniw"
},
medium: {
Bqenvij: "f4t8t6x"
},
large: {
Bqenvij: "f6ywr7j"
}
}, {
d: [".ftgm304{display:block;}", ".f18f03hv{background-color:var(--colorNeutralBackground6);}", ".fly5x3f{width:100%;}", [".f1a3p1vp{overflow:hidden;}", {
p: -1
}], [".ft85np5{border-radius:var(--borderRadiusMedium);}", {
p: -1
}], [".f1fabniw{border-radius:var(--borderRadiusNone);}", {
p: -1
}], ".f4t8t6x{height:2px;}", ".f6ywr7j{height:4px;}"],
m: [["@media screen and (forced-colors: active){.f1skxd4g{background-color:CanvasText;}}", {
m: "screen and (forced-colors: active)"
}]]
});
/**
* Styles for the ProgressBar bar
*/
const useBarStyles = /*#__PURE__*/__styles({
base: {
Bomf52o: "f1tnpuu0",
Beyfa6y: 0,
Bbmb7ep: 0,
Btl43ni: 0,
B7oj6ja: 0,
Dimara: "f12b9xdw",
Bqenvij: "f1l02sjl"
},
nonZeroDeterminate: {
Bmy1vo4: "fjt6zfz",
B3o57yi: "f1wofebd",
Bkqvd7p: "fv71qf3"
},
indeterminate: {
B2u0y6b: "fa0wk36",
qhf8xq: "f10pi13n",
Bcmaq0h: ["fpo0yib", "f1u5hf6c"],
jpy9cc: "f3z2g5w"
},
brand: {
De3pzq: "ftywsgz"
},
error: {
De3pzq: "fdl5y0r"
},
warning: {
De3pzq: "f1s438gw"
},
success: {
De3pzq: "flxk52p"
}
}, {
m: [["@media screen and (forced-colors: active){.f1tnpuu0{background-color:Highlight;}}", {
m: "screen and (forced-colors: active)"
}], ["@media screen and (prefers-reduced-motion: reduce){.f3z2g5w{max-width:100%;}}", {
m: "screen and (prefers-reduced-motion: reduce)"
}]],
d: [[".f12b9xdw{border-radius:inherit;}", {
p: -1
}], ".f1l02sjl{height:100%;}", ".fjt6zfz{transition-property:width;}", ".f1wofebd{transition-duration:0.3s;}", ".fv71qf3{transition-timing-function:ease;}", ".fa0wk36{max-width:33%;}", ".f10pi13n{position:relative;}", ".fpo0yib{background-image:linear-gradient(\n to right,\n var(--colorNeutralBackground6) 0%,\n var(--colorTransparentBackground) 50%,\n var(--colorNeutralBackground6) 100%\n );}", ".f1u5hf6c{background-image:linear-gradient(\n to left,\n var(--colorNeutralBackground6) 0%,\n var(--colorTransparentBackground) 50%,\n var(--colorNeutralBackground6) 100%\n );}", ".ftywsgz{background-color:var(--colorCompoundBrandBackground);}", ".fdl5y0r{background-color:var(--colorPaletteRedBackground3);}", ".f1s438gw{background-color:var(--colorPaletteDarkOrangeBackground3);}", ".flxk52p{background-color:var(--colorPaletteGreenBackground3);}"]
});
/**
* Apply styling to the ProgressBar slots based on the state
*/
export const useProgressBarStyles_unstable = state => {
'use no memo';
const {
color,
max,
shape,
thickness,
value
} = state;
const rootStyles = useRootStyles();
const barStyles = useBarStyles();
state.root.className = mergeClasses(progressBarClassNames.root, rootStyles.root, rootStyles[shape], rootStyles[thickness], state.root.className);
if (state.bar) {
state.bar.className = mergeClasses(progressBarClassNames.bar, barStyles.base, barStyles.brand, value === undefined && barStyles.indeterminate, value !== undefined && value > ZERO_THRESHOLD && barStyles.nonZeroDeterminate, color && value !== undefined && barStyles[color], state.bar.className);
}
if (state.bar && value !== undefined) {
state.bar.style = {
width: Math.min(100, Math.max(0, value / max * 100)) + '%',
...state.bar.style
};
}
return state;
};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,100 @@
'use client';
import { makeStyles, mergeClasses } from '@griffel/react';
import { tokens } from '@fluentui/react-theme';
export const progressBarClassNames = {
root: 'fui-ProgressBar',
bar: 'fui-ProgressBar__bar'
};
// If the percentComplete is near 0, don't animate it.
// This prevents animations on reset to 0 scenarios.
const ZERO_THRESHOLD = 0.01;
const barThicknessValues = {
medium: '2px',
large: '4px'
};
/**
* Styles for the root slot
*/ const useRootStyles = makeStyles({
root: {
display: 'block',
backgroundColor: tokens.colorNeutralBackground6,
width: '100%',
overflow: 'hidden',
'@media screen and (forced-colors: active)': {
backgroundColor: 'CanvasText'
}
},
rounded: {
borderRadius: tokens.borderRadiusMedium
},
square: {
borderRadius: tokens.borderRadiusNone
},
medium: {
height: barThicknessValues.medium
},
large: {
height: barThicknessValues.large
}
});
/**
* Styles for the ProgressBar bar
*/ const useBarStyles = makeStyles({
base: {
'@media screen and (forced-colors: active)': {
backgroundColor: 'Highlight'
},
borderRadius: 'inherit',
height: '100%'
},
nonZeroDeterminate: {
transitionProperty: 'width',
transitionDuration: '0.3s',
transitionTimingFunction: 'ease'
},
indeterminate: {
maxWidth: '33%',
position: 'relative',
backgroundImage: `linear-gradient(
to right,
${tokens.colorNeutralBackground6} 0%,
${tokens.colorTransparentBackground} 50%,
${tokens.colorNeutralBackground6} 100%
)`,
'@media screen and (prefers-reduced-motion: reduce)': {
// Reduced motion: bar is sized here, and the opacity is pulsed by ProgressBarIndeterminateMotion
maxWidth: '100%'
}
},
brand: {
backgroundColor: tokens.colorCompoundBrandBackground
},
error: {
backgroundColor: tokens.colorPaletteRedBackground3
},
warning: {
backgroundColor: tokens.colorPaletteDarkOrangeBackground3
},
success: {
backgroundColor: tokens.colorPaletteGreenBackground3
}
});
/**
* Apply styling to the ProgressBar slots based on the state
*/ export const useProgressBarStyles_unstable = (state)=>{
'use no memo';
const { color, max, shape, thickness, value } = state;
const rootStyles = useRootStyles();
const barStyles = useBarStyles();
state.root.className = mergeClasses(progressBarClassNames.root, rootStyles.root, rootStyles[shape], rootStyles[thickness], state.root.className);
if (state.bar) {
state.bar.className = mergeClasses(progressBarClassNames.bar, barStyles.base, barStyles.brand, value === undefined && barStyles.indeterminate, value !== undefined && value > ZERO_THRESHOLD && barStyles.nonZeroDeterminate, color && value !== undefined && barStyles[color], state.bar.className);
}
if (state.bar && value !== undefined) {
state.bar.style = {
width: Math.min(100, Math.max(0, value / max * 100)) + '%',
...state.bar.style
};
}
return state;
};

File diff suppressed because one or more lines are too long