Skip to content

Commit

Permalink
feat(react-infobutton): Adding size prop, HCM styles, and updating st…
Browse files Browse the repository at this point in the history
…yles to match design spec (microsoft#25519)

* updating styles and adding size prop

* updating comment

* adding requested changes
  • Loading branch information
sopranopillow authored and NotWoods committed Nov 18, 2022
1 parent d6d71b5 commit 7cd1361
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ export const InfoButton: ForwardRefComponent<InfoButtonProps>;
export const infoButtonClassNames: SlotClassNames<InfoButtonSlots>;

// @public
export type InfoButtonProps = ComponentProps<Partial<InfoButtonSlots>>;
export type InfoButtonProps = Omit<ComponentProps<Partial<InfoButtonSlots>>, 'disabled'> & {
size?: 'small' | 'medium' | 'large';
};

// @public (undocumented)
export type InfoButtonSlots = {
Expand All @@ -32,7 +34,7 @@ export type InfoButtonSlots = {
};

// @public
export type InfoButtonState = ComponentState<InfoButtonSlots>;
export type InfoButtonState = ComponentState<InfoButtonSlots> & Required<Pick<InfoButtonProps, 'size'>>;

// @public
export const renderInfoButton_unstable: (state: InfoButtonState) => JSX.Element;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {
Info12Regular,
Info12Filled,
Info16Regular,
Info16Filled,
Info20Regular,
Info20Filled,
bundleIcon,
} from '@fluentui/react-icons';

export const DefaultInfoButtonIcon12 = bundleIcon(Info12Filled, Info12Regular);
export const DefaultInfoButtonIcon16 = bundleIcon(Info16Filled, Info16Regular);
export const DefaultInfoButtonIcon20 = bundleIcon(Info20Filled, Info20Regular);
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,16 @@ export type InfoButtonSlots = {
/**
* InfoButton Props
*/
export type InfoButtonProps = ComponentProps<Partial<InfoButtonSlots>>;
export type InfoButtonProps = Omit<ComponentProps<Partial<InfoButtonSlots>>, 'disabled'> & {
/**
* Size of the InfoButton.
*
* @default medium
*/
size?: 'small' | 'medium' | 'large';
};

/**
* State used in rendering InfoButton
*/
export type InfoButtonState = ComponentState<InfoButtonSlots>;
export type InfoButtonState = ComponentState<InfoButtonSlots> & Required<Pick<InfoButtonProps, 'size'>>;
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import * as React from 'react';
import { DefaultInfoButtonIcon } from './DefaultInfoButtonIcon';
import { DefaultInfoButtonIcon12, DefaultInfoButtonIcon16, DefaultInfoButtonIcon20 } from './DefaultInfoButtonIcons';
import { getNativeElementProps, mergeCallbacks, resolveShorthand } from '@fluentui/react-utilities';
import { Popover, PopoverSurface } from '@fluentui/react-popover';
import { useControllableState } from '@fluentui/react-utilities';
import type { InfoButtonProps, InfoButtonState } from './InfoButton.types';

const infoButtonIconMap = {
small: <DefaultInfoButtonIcon12 />,
medium: <DefaultInfoButtonIcon16 />,
large: <DefaultInfoButtonIcon20 />,
} as const;

const popoverSizeMap = {
small: 'small',
medium: 'small',
large: 'medium',
} as const;

/**
* Create the state required to render InfoButton.
*
Expand All @@ -15,15 +27,19 @@ import type { InfoButtonProps, InfoButtonState } from './InfoButton.types';
* @param ref - reference to root HTMLElement of InfoButton
*/
export const useInfoButton_unstable = (props: InfoButtonProps, ref: React.Ref<HTMLElement>): InfoButtonState => {
const { size = 'medium' } = props;

const state: InfoButtonState = {
size,

components: {
root: 'button',
popover: Popover,
content: PopoverSurface,
},

root: getNativeElementProps('button', {
children: <DefaultInfoButtonIcon />,
children: infoButtonIconMap[size],
type: 'button',
...props,
ref,
Expand All @@ -33,7 +49,7 @@ export const useInfoButton_unstable = (props: InfoButtonProps, ref: React.Ref<HT
defaultProps: {
children: <></>,
positioning: 'above-start',
size: 'small',
size: popoverSizeMap[size],
withArrow: true,
},
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createCustomFocusIndicatorStyle } from '@fluentui/react-tabster';
import { iconFilledClassName, iconRegularClassName } from '@fluentui/react-icons';
import { makeStyles, mergeClasses, shorthands } from '@griffel/react';
import { tokens } from '@fluentui/react-theme';
import { tokens, typographyStyles } from '@fluentui/react-theme';
import type { InfoButtonSlots, InfoButtonState } from './InfoButton.types';
import type { SlotClassNames } from '@fluentui/react-utilities';

Expand All @@ -26,12 +26,12 @@ const useButtonStyles = makeStyles({

backgroundColor: tokens.colorTransparentBackground,
color: tokens.colorNeutralForeground2,
fontFamily: tokens.fontFamilyBase,

...shorthands.overflow('hidden'),
...shorthands.border(tokens.strokeWidthThin, 'solid', tokens.colorTransparentStroke),
...shorthands.padding(tokens.spacingVerticalXS, tokens.spacingHorizontalXS),
...shorthands.borderRadius(tokens.borderRadiusMedium),
...shorthands.margin(0),
...shorthands.padding(tokens.spacingVerticalXS, tokens.spacingHorizontalXS),

[`& .${iconFilledClassName}`]: {
display: 'none',
Expand All @@ -40,7 +40,7 @@ const useButtonStyles = makeStyles({
display: 'inline-flex',
},

':enabled:hover': {
':hover': {
backgroundColor: tokens.colorTransparentBackgroundHover,
color: tokens.colorNeutralForeground2BrandHover,

Expand All @@ -51,55 +51,89 @@ const useButtonStyles = makeStyles({
display: 'none',
},
},
':enabled:hover:active': {
':hover:active': {
backgroundColor: tokens.colorTransparentBackgroundPressed,
color: tokens.colorNeutralForeground2BrandPressed,
},
':disabled': {
cursor: 'not-allowed',
color: tokens.colorNeutralForegroundDisabled,
},

selected: {
backgroundColor: tokens.colorTransparentBackgroundSelected,
color: tokens.colorNeutralForeground2BrandSelected,

[`& .${iconFilledClassName}`]: {
display: 'inline-flex',
},
[`& .${iconRegularClassName}`]: {
display: 'none',
},

'@media (forced-colors: active)': {
backgroundColor: 'Highlight',
...shorthands.borderColor('Canvas'),
color: 'Canvas',
},
},

highContrast: {
'@media (forced-colors: active)': {
...shorthands.borderColor('Canvas'),
color: 'CanvasText',

':hover, :hover:active': {
forcedColorAdjust: 'none',
backgroundColor: 'Highlight',
...shorthands.borderColor('Canvas'),
color: 'Canvas',
},
},
},

focusIndicator: createCustomFocusIndicatorStyle({
...shorthands.borderRadius(tokens.borderRadiusSmall),
...shorthands.borderRadius(tokens.borderRadiusMedium),
...shorthands.borderColor(tokens.colorTransparentStroke),
outlineColor: tokens.colorTransparentStroke,
outlineWidth: tokens.strokeWidthThick,
outlineStyle: 'solid',
boxShadow: `
${tokens.shadow4},
0 0 0 ${tokens.borderRadiusSmall} ${tokens.colorStrokeFocus2}
0 0 0 2px ${tokens.colorStrokeFocus2}
`,
zIndex: 1,
}),

selected: {
backgroundColor: tokens.colorTransparentBackgroundSelected,
color: tokens.colorNeutralForeground2BrandSelected,

[`& .${iconFilledClassName}`]: {
display: 'inline-flex',
},
[`& .${iconRegularClassName}`]: {
display: 'none',
},
large: {
...shorthands.padding(tokens.spacingVerticalXXS, tokens.spacingVerticalXXS),
},
});

const usePopoverSurfaceStyles = makeStyles({
smallMedium: typographyStyles.caption1,
large: typographyStyles.body1,
});

/**
* Apply styling to the InfoButton slots based on the state
*/
export const useInfoButtonStyles_unstable = (state: InfoButtonState): InfoButtonState => {
const { size } = state;
const { open } = state.popover;
const buttonStyles = useButtonStyles();
const popoverSurfaceStyles = usePopoverSurfaceStyles();

state.content.className = mergeClasses(
infoButtonClassNames.content,
size === 'large' && popoverSurfaceStyles.large,
state.content.className,
);

state.content.className = mergeClasses(infoButtonClassNames.content, state.content.className);
state.root.className = mergeClasses(
infoButtonClassNames.root,
buttonStyles.base,
buttonStyles.highContrast,
buttonStyles.focusIndicator,
open && buttonStyles.selected,
size === 'large' && buttonStyles.large,
state.root.className,
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as React from 'react';
import { InfoButton } from '@fluentui/react-infobutton';
import { Link, makeStyles, shorthands } from '@fluentui/react-components';

const useStyles = makeStyles({
base: {
alignItems: 'start',
display: 'flex',
flexDirection: 'column',
...shorthands.gap('80px'),
...shorthands.padding('20px'),
},
});

export const Size = () => {
const styles = useStyles();

return (
<div className={styles.base}>
<InfoButton
size="small"
content={
<>
This is example content for a small InfoButton. <Link href="https://react.fluentui.dev">Learn more</Link>.
</>
}
/>

<InfoButton
size="medium"
content={
<>
This is example content for a medium InfoButton. <Link href="https://react.fluentui.dev">Learn more</Link>.
</>
}
/>

<InfoButton
size="large"
content={
<>
This is example content for a large InfoButton. <Link href="https://react.fluentui.dev">Learn more</Link>.
</>
}
/>
</div>
);
};

Size.parameters = {
docs: {
description: {
story: 'An InfoButton supports a range of sizes from small to large. The default is medium.',
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import descriptionMd from './InfoButtonDescription.md';
import bestPracticesMd from './InfoButtonBestPractices.md';

export { Default } from './InfoButtonDefault.stories';
export { Size } from './InfoButtonSize.stories';

export default {
title: 'Preview Components/InfoButton',
Expand Down

0 comments on commit 7cd1361

Please sign in to comment.