diff --git a/packages/react-components/react-infobutton/etc/react-infobutton.api.md b/packages/react-components/react-infobutton/etc/react-infobutton.api.md index 080037287f1be2..be9d27214804d0 100644 --- a/packages/react-components/react-infobutton/etc/react-infobutton.api.md +++ b/packages/react-components/react-infobutton/etc/react-infobutton.api.md @@ -22,7 +22,9 @@ export const InfoButton: ForwardRefComponent; export const infoButtonClassNames: SlotClassNames; // @public -export type InfoButtonProps = ComponentProps>; +export type InfoButtonProps = Omit>, 'disabled'> & { + size?: 'small' | 'medium' | 'large'; +}; // @public (undocumented) export type InfoButtonSlots = { @@ -32,7 +34,7 @@ export type InfoButtonSlots = { }; // @public -export type InfoButtonState = ComponentState; +export type InfoButtonState = ComponentState & Required>; // @public export const renderInfoButton_unstable: (state: InfoButtonState) => JSX.Element; diff --git a/packages/react-components/react-infobutton/src/components/InfoButton/DefaultInfoButtonIcon.tsx b/packages/react-components/react-infobutton/src/components/InfoButton/DefaultInfoButtonIcon.tsx deleted file mode 100644 index 336ae52755c871..00000000000000 --- a/packages/react-components/react-infobutton/src/components/InfoButton/DefaultInfoButtonIcon.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { Info12Regular, Info12Filled, bundleIcon } from '@fluentui/react-icons'; - -export const DefaultInfoButtonIcon = bundleIcon(Info12Filled, Info12Regular); diff --git a/packages/react-components/react-infobutton/src/components/InfoButton/DefaultInfoButtonIcons.tsx b/packages/react-components/react-infobutton/src/components/InfoButton/DefaultInfoButtonIcons.tsx new file mode 100644 index 00000000000000..463159b2ae8196 --- /dev/null +++ b/packages/react-components/react-infobutton/src/components/InfoButton/DefaultInfoButtonIcons.tsx @@ -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); diff --git a/packages/react-components/react-infobutton/src/components/InfoButton/InfoButton.types.ts b/packages/react-components/react-infobutton/src/components/InfoButton/InfoButton.types.ts index 97083e38847291..b87b35df44d117 100644 --- a/packages/react-components/react-infobutton/src/components/InfoButton/InfoButton.types.ts +++ b/packages/react-components/react-infobutton/src/components/InfoButton/InfoButton.types.ts @@ -18,9 +18,16 @@ export type InfoButtonSlots = { /** * InfoButton Props */ -export type InfoButtonProps = ComponentProps>; +export type InfoButtonProps = Omit>, 'disabled'> & { + /** + * Size of the InfoButton. + * + * @default medium + */ + size?: 'small' | 'medium' | 'large'; +}; /** * State used in rendering InfoButton */ -export type InfoButtonState = ComponentState; +export type InfoButtonState = ComponentState & Required>; diff --git a/packages/react-components/react-infobutton/src/components/InfoButton/useInfoButton.tsx b/packages/react-components/react-infobutton/src/components/InfoButton/useInfoButton.tsx index a998066b66d83a..967f1e51fcfde3 100644 --- a/packages/react-components/react-infobutton/src/components/InfoButton/useInfoButton.tsx +++ b/packages/react-components/react-infobutton/src/components/InfoButton/useInfoButton.tsx @@ -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: , + medium: , + large: , +} as const; + +const popoverSizeMap = { + small: 'small', + medium: 'small', + large: 'medium', +} as const; + /** * Create the state required to render InfoButton. * @@ -15,7 +27,11 @@ import type { InfoButtonProps, InfoButtonState } from './InfoButton.types'; * @param ref - reference to root HTMLElement of InfoButton */ export const useInfoButton_unstable = (props: InfoButtonProps, ref: React.Ref): InfoButtonState => { + const { size = 'medium' } = props; + const state: InfoButtonState = { + size, + components: { root: 'button', popover: Popover, @@ -23,7 +39,7 @@ export const useInfoButton_unstable = (props: InfoButtonProps, ref: React.Ref, + children: infoButtonIconMap[size], type: 'button', ...props, ref, @@ -33,7 +49,7 @@ export const useInfoButton_unstable = (props: InfoButtonProps, ref: React.Ref, positioning: 'above-start', - size: 'small', + size: popoverSizeMap[size], withArrow: true, }, }), diff --git a/packages/react-components/react-infobutton/src/components/InfoButton/useInfoButtonStyles.ts b/packages/react-components/react-infobutton/src/components/InfoButton/useInfoButtonStyles.ts index 4d3ed6908f9cb1..9471f791875b4d 100644 --- a/packages/react-components/react-infobutton/src/components/InfoButton/useInfoButtonStyles.ts +++ b/packages/react-components/react-infobutton/src/components/InfoButton/useInfoButtonStyles.ts @@ -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'; @@ -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', @@ -40,7 +40,7 @@ const useButtonStyles = makeStyles({ display: 'inline-flex', }, - ':enabled:hover': { + ':hover': { backgroundColor: tokens.colorTransparentBackgroundHover, color: tokens.colorNeutralForeground2BrandHover, @@ -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, ); diff --git a/packages/react-components/react-infobutton/src/stories/Infobutton/InfoButtonSize.stories.tsx b/packages/react-components/react-infobutton/src/stories/Infobutton/InfoButtonSize.stories.tsx new file mode 100644 index 00000000000000..030ce8b15c7bc8 --- /dev/null +++ b/packages/react-components/react-infobutton/src/stories/Infobutton/InfoButtonSize.stories.tsx @@ -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 ( +
+ + This is example content for a small InfoButton. Learn more. + + } + /> + + + This is example content for a medium InfoButton. Learn more. + + } + /> + + + This is example content for a large InfoButton. Learn more. + + } + /> +
+ ); +}; + +Size.parameters = { + docs: { + description: { + story: 'An InfoButton supports a range of sizes from small to large. The default is medium.', + }, + }, +}; diff --git a/packages/react-components/react-infobutton/src/stories/Infobutton/index.stories.tsx b/packages/react-components/react-infobutton/src/stories/Infobutton/index.stories.tsx index 4a1a1e5c8041a9..65c5f3afaae1d5 100644 --- a/packages/react-components/react-infobutton/src/stories/Infobutton/index.stories.tsx +++ b/packages/react-components/react-infobutton/src/stories/Infobutton/index.stories.tsx @@ -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',