Private
Public Access
1
0
Files

275 lines
10 KiB
JavaScript

'use client';
import { createFocusOutlineStyle } from '@fluentui/react-tabster';
import { tokens } from '@fluentui/react-theme';
import { makeResetStyles, makeStyles, mergeClasses } from '@griffel/react';
export const switchClassNames = {
root: 'fui-Switch',
indicator: 'fui-Switch__indicator',
input: 'fui-Switch__input',
label: 'fui-Switch__label'
};
/**
* @deprecated Use `switchClassNames.root` instead.
*/ export const switchClassName = switchClassNames.root;
// Thumb and track sizes used by the component.
const spaceBetweenThumbAndTrack = 2;
// Medium size dimensions
const trackHeightMedium = 20;
const trackWidthMedium = 40;
const thumbSizeMedium = trackHeightMedium - spaceBetweenThumbAndTrack;
// Small size dimensions (from design mockup)
const trackHeightSmall = 16;
const trackWidthSmall = 32;
const thumbSizeSmall = trackHeightSmall - spaceBetweenThumbAndTrack;
const useRootBaseClassName = makeResetStyles({
alignItems: 'flex-start',
boxSizing: 'border-box',
display: 'inline-flex',
position: 'relative',
...createFocusOutlineStyle({
style: {},
selector: 'focus-within'
})
});
const useRootStyles = makeStyles({
vertical: {
flexDirection: 'column'
}
});
const useIndicatorBaseClassName = makeResetStyles({
borderRadius: tokens.borderRadiusCircular,
border: '1px solid',
lineHeight: 0,
boxSizing: 'border-box',
fill: 'currentColor',
flexShrink: 0,
fontSize: `${thumbSizeMedium}px`,
height: `${trackHeightMedium}px`,
margin: tokens.spacingVerticalS + ' ' + tokens.spacingHorizontalS,
pointerEvents: 'none',
transitionDuration: tokens.durationNormal,
transitionTimingFunction: tokens.curveEasyEase,
transitionProperty: 'background, border, color',
width: `${trackWidthMedium}px`,
'@media screen and (prefers-reduced-motion: reduce)': {
transitionDuration: '0.01ms'
},
'@media (forced-colors: active)': {
color: 'CanvasText',
'> i': {
forcedColorAdjust: 'none'
}
},
'> *': {
transitionDuration: tokens.durationNormal,
transitionTimingFunction: tokens.curveEasyEase,
transitionProperty: 'transform',
'@media screen and (prefers-reduced-motion: reduce)': {
transitionDuration: '0.01ms'
}
}
});
const useIndicatorStyles = makeStyles({
labelAbove: {
marginTop: 0
},
sizeSmall: {
fontSize: `${thumbSizeSmall}px`,
height: `${trackHeightSmall}px`,
width: `${trackWidthSmall}px`
}
});
const useInputBaseClassName = makeResetStyles({
boxSizing: 'border-box',
cursor: 'pointer',
height: '100%',
margin: 0,
opacity: 0,
position: 'absolute',
// Calculate the width of the hidden input by taking into account the size of the indicator + the padding around it.
// This is done so that clicking on that "empty space" still toggles the switch.
width: `calc(${trackWidthMedium}px + 2 * ${tokens.spacingHorizontalS})`,
// Checked (both enabled and disabled)
':checked': {
[`& ~ .${switchClassNames.indicator}`]: {
'> *': {
transform: `translateX(${trackWidthMedium - thumbSizeMedium - spaceBetweenThumbAndTrack}px)`
}
}
},
// Disabled (both checked and unchecked)
':disabled, &[aria-disabled="true"]': {
cursor: 'default',
[`& ~ .${switchClassNames.indicator}`]: {
color: tokens.colorNeutralForegroundDisabled
},
[`& ~ .${switchClassNames.label}`]: {
cursor: 'default',
color: tokens.colorNeutralForegroundDisabled
}
},
// Enabled and unchecked
':enabled:not(:checked):not([aria-disabled="true"])': {
[`& ~ .${switchClassNames.indicator}`]: {
color: tokens.colorNeutralStrokeAccessible,
borderColor: tokens.colorNeutralStrokeAccessible
},
[`& ~ .${switchClassNames.label}`]: {
color: tokens.colorNeutralForeground1
},
':hover': {
[`& ~ .${switchClassNames.indicator}`]: {
color: tokens.colorNeutralStrokeAccessibleHover,
borderColor: tokens.colorNeutralStrokeAccessibleHover
}
},
':hover:active': {
[`& ~ .${switchClassNames.indicator}`]: {
color: tokens.colorNeutralStrokeAccessiblePressed,
borderColor: tokens.colorNeutralStrokeAccessiblePressed
}
}
},
// Enabled and checked
':enabled:checked:not([aria-disabled="true"])': {
[`& ~ .${switchClassNames.indicator}`]: {
backgroundColor: tokens.colorCompoundBrandBackground,
color: tokens.colorNeutralForegroundInverted,
borderColor: tokens.colorTransparentStroke
},
':hover': {
[`& ~ .${switchClassNames.indicator}`]: {
backgroundColor: tokens.colorCompoundBrandBackgroundHover,
borderColor: tokens.colorTransparentStrokeInteractive
}
},
':hover:active': {
[`& ~ .${switchClassNames.indicator}`]: {
backgroundColor: tokens.colorCompoundBrandBackgroundPressed,
borderColor: tokens.colorTransparentStrokeInteractive
}
}
},
// Disabled and unchecked
':disabled:not(:checked), &[aria-disabled="true"]:not(:checked)': {
[`& ~ .${switchClassNames.indicator}`]: {
borderColor: tokens.colorNeutralStrokeDisabled
}
},
// Disabled and checked
':disabled:checked, &[aria-disabled="true"]:checked': {
[`& ~ .${switchClassNames.indicator}`]: {
backgroundColor: tokens.colorNeutralBackgroundDisabled,
borderColor: tokens.colorTransparentStrokeDisabled
}
},
'@media (forced-colors: active)': {
':disabled, &[aria-disabled="true"]': {
[`& ~ .${switchClassNames.indicator}`]: {
color: 'GrayText',
borderColor: 'GrayText'
},
[`& ~ .${switchClassNames.label}`]: {
color: 'GrayText'
}
},
':hover': {
color: 'CanvasText'
},
':hover:active': {
color: 'CanvasText'
},
':enabled:checked:not([aria-disabled="true"])': {
':hover': {
[`& ~ .${switchClassNames.indicator}`]: {
backgroundColor: 'Highlight',
color: 'Canvas'
}
},
':hover:active': {
[`& ~ .${switchClassNames.indicator}`]: {
backgroundColor: 'Highlight',
color: 'Canvas'
}
},
[`& ~ .${switchClassNames.indicator}`]: {
backgroundColor: 'Highlight',
color: 'Canvas'
}
}
}
});
const useInputStyles = makeStyles({
before: {
right: 0,
top: 0
},
after: {
left: 0,
top: 0
},
above: {
bottom: 0,
height: `calc(${trackHeightMedium}px + ${tokens.spacingVerticalS})`,
width: '100%'
},
sizeSmall: {
width: `calc(${trackWidthSmall}px + 2 * ${tokens.spacingHorizontalS})`,
':checked': {
[`& ~ .${switchClassNames.indicator}`]: {
'> *': {
transform: `translateX(${trackWidthSmall - thumbSizeSmall - spaceBetweenThumbAndTrack}px)`
}
}
}
}
});
// Can't use makeResetStyles here because Label is a component that may itself use makeResetStyles.
const useLabelStyles = makeStyles({
base: {
cursor: 'pointer',
// Use a (negative) margin to account for the difference between the track's height and the label's line height.
// This prevents the label from expanding the height of the switch, but preserves line height if the label wraps.
marginBottom: `calc((${trackHeightMedium}px - ${tokens.lineHeightBase300}) / 2)`,
marginTop: `calc((${trackHeightMedium}px - ${tokens.lineHeightBase300}) / 2)`,
padding: `${tokens.spacingVerticalS} ${tokens.spacingHorizontalS}`
},
sizeSmall: {
fontSize: tokens.fontSizeBase200,
lineHeight: tokens.lineHeightBase200,
marginBottom: `calc((${trackHeightSmall}px - ${tokens.lineHeightBase200}) / 2)`,
marginTop: `calc((${trackHeightSmall}px - ${tokens.lineHeightBase200}) / 2)`
},
above: {
paddingTop: tokens.spacingVerticalXS,
paddingBottom: tokens.spacingVerticalXS,
width: '100%'
},
after: {
paddingLeft: tokens.spacingHorizontalXS
},
before: {
paddingRight: tokens.spacingHorizontalXS
}
});
/**
* Apply styling to the Switch slots based on the state
*/ export const useSwitchStyles_unstable = (state)=>{
'use no memo';
const rootBaseClassName = useRootBaseClassName();
const rootStyles = useRootStyles();
const indicatorBaseClassName = useIndicatorBaseClassName();
const indicatorStyles = useIndicatorStyles();
const inputBaseClassName = useInputBaseClassName();
const inputStyles = useInputStyles();
const labelStyles = useLabelStyles();
const { label, labelPosition, size } = state;
state.root.className = mergeClasses(switchClassNames.root, rootBaseClassName, labelPosition === 'above' && rootStyles.vertical, state.root.className);
state.indicator.className = mergeClasses(switchClassNames.indicator, indicatorBaseClassName, label && labelPosition === 'above' && indicatorStyles.labelAbove, size === 'small' && indicatorStyles.sizeSmall, state.indicator.className);
state.input.className = mergeClasses(switchClassNames.input, inputBaseClassName, label && inputStyles[labelPosition], size === 'small' && inputStyles.sizeSmall, state.input.className);
if (state.label) {
state.label.className = mergeClasses(switchClassNames.label, labelStyles.base, labelStyles[labelPosition], size === 'small' && labelStyles.sizeSmall, state.label.className);
}
return state;
};