diff --git a/change/@fluentui-react-native-button-3ad6e9f3-05fd-4f0d-9816-ea9a45250a68.json b/change/@fluentui-react-native-button-3ad6e9f3-05fd-4f0d-9816-ea9a45250a68.json new file mode 100644 index 0000000000..41852c2431 --- /dev/null +++ b/change/@fluentui-react-native-button-3ad6e9f3-05fd-4f0d-9816-ea9a45250a68.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Add highlight prop / highlight ring to Button, Tab, and MenuItem", + "packageName": "@fluentui-react-native/button", + "email": "winlarry@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-native-menu-5df85b37-4fe4-4c96-81ec-598b079e6c6b.json b/change/@fluentui-react-native-menu-5df85b37-4fe4-4c96-81ec-598b079e6c6b.json new file mode 100644 index 0000000000..e12a13d261 --- /dev/null +++ b/change/@fluentui-react-native-menu-5df85b37-4fe4-4c96-81ec-598b079e6c6b.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Add highlight prop / highlight ring to Button, Tab, and MenuItem", + "packageName": "@fluentui-react-native/menu", + "email": "winlarry@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-native-tablist-bc23b307-a612-4206-8c51-e78d2abf5d35.json b/change/@fluentui-react-native-tablist-bc23b307-a612-4206-8c51-e78d2abf5d35.json new file mode 100644 index 0000000000..8d679e9d44 --- /dev/null +++ b/change/@fluentui-react-native-tablist-bc23b307-a612-4206-8c51-e78d2abf5d35.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Add highlight prop / highlight ring to Button, Tab, and MenuItem", + "packageName": "@fluentui-react-native/tablist", + "email": "winlarry@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/components/Button/src/Button.styling.ts b/packages/components/Button/src/Button.styling.ts index 38284146be..47c2097e8b 100644 --- a/packages/components/Button/src/Button.styling.ts +++ b/packages/components/Button/src/Button.styling.ts @@ -29,6 +29,7 @@ export const buttonStates: (keyof ButtonTokens)[] = [ 'square', 'hovered', 'focused', + 'highlighted', 'pressed', 'disabled', ]; diff --git a/packages/components/Button/src/Button.types.ts b/packages/components/Button/src/Button.types.ts index 78ac37f033..311536fad9 100644 --- a/packages/components/Button/src/Button.types.ts +++ b/packages/components/Button/src/Button.types.ts @@ -74,6 +74,7 @@ export interface ButtonTokens extends ButtonCoreTokens { */ hovered?: ButtonTokens; focused?: ButtonTokens; + highlighted?: ButtonTokens; pressed?: ButtonTokens; disabled?: ButtonTokens; hasContent?: ButtonTokens; @@ -164,9 +165,15 @@ export interface ButtonProps extends ButtonCoreProps { * @default false */ loading?: boolean; + + /** + * Whether the button should render a highlighted ring. + */ + highlighted?: boolean; } interface ButtonState extends PressableState { + highlighted?: boolean; measuredHeight?: number; measuredWidth?: number; diff --git a/packages/components/Button/src/ButtonColorTokens.macos.ts b/packages/components/Button/src/ButtonColorTokens.macos.ts index 9e5d3e069f..e51ec161bc 100644 --- a/packages/components/Button/src/ButtonColorTokens.macos.ts +++ b/packages/components/Button/src/ButtonColorTokens.macos.ts @@ -39,6 +39,12 @@ export const defaultButtonColorTokens: TokenSettings = (t: borderColor: t.colors.neutralStroke2, icon: t.colors.neutralForeground2, }, + highlighted: { + backgroundColor: t.colors.neutralBackground2, + color: t.colors.neutralForeground2, + borderColor: t.colors.neutralStroke2, + icon: t.colors.neutralForeground2, + }, primary: { backgroundColor: t.colors.brandBackground, color: t.colors.neutralForegroundOnBrand, @@ -71,6 +77,12 @@ export const defaultButtonColorTokens: TokenSettings = (t: borderColor: t.colors.brandBackground, iconColor: t.colors.neutralForegroundOnBrand, }, + highlighted: { + backgroundColor: t.colors.brandBackground, + color: t.colors.neutralForegroundOnBrand, + borderColor: t.colors.brandBackground, + iconColor: t.colors.neutralForegroundOnBrand, + }, }, // https://github.com/microsoft/fluentui-react-native/issues/3781 // Subtle Button should match Titlebar buttons on macOS, which: @@ -106,5 +118,11 @@ export const defaultButtonColorTokens: TokenSettings = (t: borderColor: t.colors.transparentStroke, iconColor: t.colors.brandForeground1, }, + highlighted: { + backgroundColor: t.colors.subtleBackground, + color: t.colors.brandForeground1, + borderColor: t.colors.transparentStroke, + iconColor: t.colors.brandForeground1, + }, }, } as ButtonTokens); diff --git a/packages/components/Button/src/ButtonColorTokens.win32.ts b/packages/components/Button/src/ButtonColorTokens.win32.ts index 2a40e43d04..c18200eae5 100644 --- a/packages/components/Button/src/ButtonColorTokens.win32.ts +++ b/packages/components/Button/src/ButtonColorTokens.win32.ts @@ -40,6 +40,12 @@ export const defaultButtonColorTokens: TokenSettings = (t: borderColor: t.colors.transparentStroke, iconColor: t.colors.neutralForeground1Hover, }, + highlighted: { + backgroundColor: t.colors.neutralBackground1Hover, + color: t.colors.neutralForeground1Hover, + borderColor: t.colors.neutralForeground1, + iconColor: t.colors.neutralForeground1Hover, + }, primary: { backgroundColor: t.colors.brandBackground, color: t.colors.neutralForegroundOnBrand, @@ -70,6 +76,12 @@ export const defaultButtonColorTokens: TokenSettings = (t: borderInnerColor: t.colors.strokeFocus1, iconColor: t.colors.neutralForegroundOnBrandHover, }, + highlighted: { + backgroundColor: t.colors.brandBackgroundHover, + color: t.colors.neutralForegroundOnBrandHover, + borderColor: t.colors.neutralForeground1, // Trying to render a 2 tone border sometime causes a crash for unknown reasons, use a single color for now + iconColor: t.colors.neutralForegroundOnBrandHover, + }, }, subtle: { backgroundColor: t.colors.subtleBackground, @@ -100,6 +112,12 @@ export const defaultButtonColorTokens: TokenSettings = (t: borderColor: t.colors.transparentStroke, iconColor: t.colors.neutralForeground1Hover, }, + highlighted: { + backgroundColor: t.colors.subtleBackgroundHover, + color: t.colors.neutralForeground1Hover, + borderColor: t.colors.neutralForeground1, + iconColor: t.colors.neutralForeground1Hover, + }, }, } as ButtonTokens; }; @@ -130,4 +148,9 @@ const highContrastColors = { color: PlatformColor('HighlightText'), iconColor: PlatformColor('HighlightText'), }, + highlighted: { + backgroundColor: PlatformColor('Highlight'), + color: PlatformColor('HighlightText'), + iconColor: PlatformColor('HighlightText'), + }, }; diff --git a/packages/components/Button/src/ButtonTokens.ts b/packages/components/Button/src/ButtonTokens.ts index fe6b6e7d35..e1d98f636c 100644 --- a/packages/components/Button/src/ButtonTokens.ts +++ b/packages/components/Button/src/ButtonTokens.ts @@ -17,6 +17,10 @@ export const defaultButtonTokens: TokenSettings = () => borderWidth: 0, padding: globalTokens.size60, }, + highlighted: { + borderWidth: globalTokens.stroke.width20, + padding: globalTokens.size60 - globalTokens.stroke.width20, + }, hasContent: { minWidth: 96, paddingHorizontal: globalTokens.size120 - globalTokens.stroke.width10, @@ -29,6 +33,9 @@ export const defaultButtonTokens: TokenSettings = () => focused: { paddingHorizontal: globalTokens.size120, }, + highlighted: { + paddingHorizontal: globalTokens.size120 - globalTokens.stroke.width20, + }, }, }, small: { @@ -39,6 +46,10 @@ export const defaultButtonTokens: TokenSettings = () => borderWidth: 0, padding: globalTokens.size40, }, + highlighted: { + borderWidth: globalTokens.stroke.width20, + padding: globalTokens.size40 - globalTokens.stroke.width20, + }, hasContent: { minWidth: 64, minHeight: 24, @@ -52,6 +63,9 @@ export const defaultButtonTokens: TokenSettings = () => focused: { paddingHorizontal: globalTokens.size80, }, + highlighted: { + paddingHorizontal: globalTokens.size80 - globalTokens.stroke.width20, + }, }, }, large: { @@ -62,6 +76,10 @@ export const defaultButtonTokens: TokenSettings = () => borderWidth: 0, padding: globalTokens.size80, }, + highlighted: { + borderWidth: globalTokens.stroke.width20, + padding: globalTokens.size80 - globalTokens.stroke.width20, + }, hasContent: { minWidth: 96, paddingHorizontal: globalTokens.size160 - globalTokens.stroke.width10, @@ -74,6 +92,9 @@ export const defaultButtonTokens: TokenSettings = () => focused: { paddingHorizontal: globalTokens.size160, }, + highlighted: { + paddingHorizontal: globalTokens.size160 - globalTokens.stroke.width20, + }, }, }, rounded: { diff --git a/packages/components/Button/src/ButtonTokens.win32.ts b/packages/components/Button/src/ButtonTokens.win32.ts index fcb96da30b..3fc4cb2d82 100644 --- a/packages/components/Button/src/ButtonTokens.win32.ts +++ b/packages/components/Button/src/ButtonTokens.win32.ts @@ -19,16 +19,28 @@ export const defaultButtonTokens: TokenSettings = (theme: T borderWidth: 0, padding: globalTokens.size80, }, + highlighted: { + borderWidth: globalTokens.stroke.width20, + padding: globalTokens.size80 - globalTokens.stroke.width20, + }, primary: !isHighContrast(theme) && { focused: { borderWidth: globalTokens.stroke.width20, padding: globalTokens.size80 - globalTokens.stroke.width20, }, + highlighted: { + borderWidth: globalTokens.stroke.width20, + padding: globalTokens.size80 - globalTokens.stroke.width20, + }, square: { focused: { borderWidth: globalTokens.stroke.width10, padding: globalTokens.size80 - globalTokens.stroke.width10, }, + highlighted: { + borderWidth: globalTokens.stroke.width20, + padding: globalTokens.size80 - globalTokens.stroke.width20, + }, }, }, hasContent: { @@ -45,16 +57,28 @@ export const defaultButtonTokens: TokenSettings = (theme: T padding: globalTokens.size60, paddingHorizontal: globalTokens.size120, }, + highlighted: { + padding: globalTokens.size60 - globalTokens.stroke.width20, + paddingHorizontal: globalTokens.size120 - globalTokens.stroke.width20, + }, primary: !isHighContrast(theme) && { focused: { padding: globalTokens.size60 - globalTokens.stroke.width20, paddingHorizontal: globalTokens.size120 - globalTokens.stroke.width20, }, + highlighted: { + padding: globalTokens.size60 - globalTokens.stroke.width20, + paddingHorizontal: globalTokens.size120 - globalTokens.stroke.width20, + }, square: { focused: { padding: globalTokens.size60 - globalTokens.stroke.width10, paddingHorizontal: globalTokens.size120 - globalTokens.stroke.width10, }, + highlighted: { + padding: globalTokens.size60 - globalTokens.stroke.width20, + paddingHorizontal: globalTokens.size120 - globalTokens.stroke.width20, + }, }, }, }, @@ -66,16 +90,28 @@ export const defaultButtonTokens: TokenSettings = (theme: T borderWidth: 0, padding: globalTokens.size40, }, + highlighted: { + borderWidth: globalTokens.stroke.width20, + padding: globalTokens.size40 - globalTokens.stroke.width20, + }, primary: !isHighContrast(theme) && { focused: { borderWidth: globalTokens.stroke.width20, padding: globalTokens.size40 - globalTokens.stroke.width20, }, + highlighted: { + borderWidth: globalTokens.stroke.width20, + padding: globalTokens.size40 - globalTokens.stroke.width20, + }, square: { focused: { borderWidth: globalTokens.stroke.width10, padding: globalTokens.size40 - globalTokens.stroke.width10, }, + highlighted: { + borderWidth: globalTokens.stroke.width20, + padding: globalTokens.size40 - globalTokens.stroke.width20, + }, }, }, hasContent: { @@ -91,14 +127,23 @@ export const defaultButtonTokens: TokenSettings = (theme: T focused: { paddingHorizontal: globalTokens.size80, }, + highlighted: { + paddingHorizontal: globalTokens.size80 - globalTokens.stroke.width20, + }, primary: !isHighContrast(theme) && { focused: { paddingHorizontal: globalTokens.size80 - globalTokens.stroke.width20, }, + highlighted: { + paddingHorizontal: globalTokens.size80 - globalTokens.stroke.width20, + }, square: { focused: { paddingHorizontal: globalTokens.size80 - globalTokens.stroke.width10, }, + highlighted: { + paddingHorizontal: globalTokens.size80 - globalTokens.stroke.width20, + }, }, }, }, @@ -110,16 +155,28 @@ export const defaultButtonTokens: TokenSettings = (theme: T borderWidth: 0, padding: globalTokens.size100, }, + highlighted: { + borderWidth: globalTokens.stroke.width20, + padding: globalTokens.size100 - globalTokens.stroke.width20, + }, primary: !isHighContrast(theme) && { focused: { borderWidth: globalTokens.stroke.width20, padding: globalTokens.size100 - globalTokens.stroke.width20, }, + highlighted: { + borderWidth: globalTokens.stroke.width20, + padding: globalTokens.size100 - globalTokens.stroke.width20, + }, square: { focused: { borderWidth: globalTokens.stroke.width10, padding: globalTokens.size100 - globalTokens.stroke.width10, }, + highlighted: { + borderWidth: globalTokens.stroke.width20, + padding: globalTokens.size100 - globalTokens.stroke.width20, + }, }, }, hasContent: { @@ -137,16 +194,28 @@ export const defaultButtonTokens: TokenSettings = (theme: T padding: globalTokens.size80, paddingHorizontal: globalTokens.size160, }, + highlighted: { + padding: globalTokens.size80 - globalTokens.stroke.width20, + paddingHorizontal: globalTokens.size160 - globalTokens.stroke.width20, + }, primary: !isHighContrast(theme) && { focused: { padding: globalTokens.size80 - globalTokens.stroke.width20, paddingHorizontal: globalTokens.size160 - globalTokens.stroke.width20, }, + highlighted: { + padding: globalTokens.size80 - globalTokens.stroke.width20, + paddingHorizontal: globalTokens.size160 - globalTokens.stroke.width20, + }, square: { focused: { padding: globalTokens.size80 - globalTokens.stroke.width10, paddingHorizontal: globalTokens.size160 - globalTokens.stroke.width10, }, + highlighted: { + padding: globalTokens.size80 - globalTokens.stroke.width20, + paddingHorizontal: globalTokens.size160 - globalTokens.stroke.width20, + }, }, }, }, diff --git a/packages/components/Menu/src/MenuItem/MenuItem.styling.ts b/packages/components/Menu/src/MenuItem/MenuItem.styling.ts index 50a4a303e0..6f8ecfc56d 100644 --- a/packages/components/Menu/src/MenuItem/MenuItem.styling.ts +++ b/packages/components/Menu/src/MenuItem/MenuItem.styling.ts @@ -8,7 +8,7 @@ import type { MenuItemProps, MenuItemTokens, MenuItemSlotProps } from './MenuIte import { menuItemName } from './MenuItem.types'; import { defaultMenuItemTokens } from './MenuItemTokens'; -export const menuItemStates: (keyof MenuItemTokens)[] = ['hovered', 'focused', 'pressed', 'disabled']; +export const menuItemStates: (keyof MenuItemTokens)[] = ['hovered', 'focused', 'highlighted', 'pressed', 'disabled']; export const stylingSettings: UseStylingOptions = { tokens: [defaultMenuItemTokens, menuItemName], diff --git a/packages/components/Menu/src/MenuItem/MenuItem.types.ts b/packages/components/Menu/src/MenuItem/MenuItem.types.ts index 324eec0640..15b0083020 100644 --- a/packages/components/Menu/src/MenuItem/MenuItem.types.ts +++ b/packages/components/Menu/src/MenuItem/MenuItem.types.ts @@ -59,6 +59,7 @@ export interface MenuItemTokens extends LayoutTokens, FontTokens, IBorderTokens, focused?: MenuItemTokens; hovered?: MenuItemTokens; pressed?: MenuItemTokens; + highlighted?: MenuItemTokens; } export interface MenuItemProps extends Omit { @@ -81,6 +82,11 @@ export interface MenuItemProps extends Omit { * Do not dismiss the menu when a menu item is clicked */ persistOnClick?: boolean; + + /** + * Whether the menu item should render a highlight ring + */ + highlighted?: boolean; } export interface MenuItemState extends PressableState { diff --git a/packages/components/Menu/src/MenuItem/MenuItemTokens.macos.ts b/packages/components/Menu/src/MenuItem/MenuItemTokens.macos.ts index 8d9487e437..bc17b10c9b 100644 --- a/packages/components/Menu/src/MenuItem/MenuItemTokens.macos.ts +++ b/packages/components/Menu/src/MenuItem/MenuItemTokens.macos.ts @@ -26,6 +26,12 @@ export const defaultMenuItemTokens: TokenSettings = (t: T iconColor: t.colors.menuItemTextHovered, submenuIndicatorColor: t.colors.menuItemTextHovered, }, + highlighted: { + backgroundColor: t.colors.menuItemBackgroundHovered, + color: t.colors.menuItemTextHovered, + iconColor: t.colors.menuItemTextHovered, + submenuIndicatorColor: t.colors.menuItemTextHovered, + }, pressed: { backgroundColor: t.colors.menuItemBackgroundPressed, color: t.colors.menuItemTextHovered, diff --git a/packages/components/Menu/src/MenuItem/MenuItemTokens.win32.ts b/packages/components/Menu/src/MenuItem/MenuItemTokens.win32.ts index be265d1d6a..b563515fcc 100644 --- a/packages/components/Menu/src/MenuItem/MenuItemTokens.win32.ts +++ b/packages/components/Menu/src/MenuItem/MenuItemTokens.win32.ts @@ -1,5 +1,6 @@ import type { FontWeightValue, Theme } from '@fluentui-react-native/framework'; import { globalTokens } from '@fluentui-react-native/theme-tokens'; +import { isHighContrast } from '@fluentui-react-native/theming-utils'; import type { TokenSettings } from '@fluentui-react-native/use-styling'; import type { MenuItemTokens } from './MenuItem.types'; @@ -41,4 +42,14 @@ export const defaultMenuItemTokens: TokenSettings = (t: T iconColor: t.colors.neutralForeground1Hover, submenuIndicatorColor: t.colors.neutralForeground1Hover, }, + highlighted: { + backgroundColor: t.colors.neutralBackground1Hover, + color: t.colors.neutralForeground1Hover, + iconColor: t.colors.neutralForeground1Hover, + submenuIndicatorColor: t.colors.neutralForeground1Hover, + borderColor: isHighContrast(t) ? t.colors.compoundBrandStroke1 : t.colors.neutralForeground1, + borderWidth: globalTokens.stroke.width20, + padding: globalTokens.size40 - globalTokens.stroke.width20, + paddingHorizontal: globalTokens.size80 - globalTokens.stroke.width20, + }, }); diff --git a/packages/components/TabList/src/Tab/Tab.types.ts b/packages/components/TabList/src/Tab/Tab.types.ts index 12d7ed41d5..5e378e7475 100644 --- a/packages/components/TabList/src/Tab/Tab.types.ts +++ b/packages/components/TabList/src/Tab/Tab.types.ts @@ -92,6 +92,7 @@ export interface TabTokens extends FontTokens, IBorderTokens, IForegroundColorTo subtle?: TabTokens; hovered?: TabTokens; focused?: TabTokens; + highlighted?: TabTokens; pressed?: TabTokens; disabled?: TabTokens; selected?: TabTokens; @@ -109,6 +110,11 @@ export interface TabProps extends Omit { */ disabled?: boolean; + /** + * Whether the tab should render a highlight ring. + */ + highlighted?: boolean; + /** * Source URL or name of the icon to show on the Button. */ diff --git a/packages/components/TabList/src/Tab/TabColorTokens.ts b/packages/components/TabList/src/Tab/TabColorTokens.ts index 523aa60666..45f9c11f8b 100644 --- a/packages/components/TabList/src/Tab/TabColorTokens.ts +++ b/packages/components/TabList/src/Tab/TabColorTokens.ts @@ -86,4 +86,7 @@ export const defaultTabColorTokens: TokenSettings = (t: Theme) focused: { borderColor: t.colors.neutralForeground1, }, + highlighted: { + borderColor: t.colors.neutralForeground1, + }, } as TabTokens); diff --git a/packages/components/TabList/src/Tab/TabColorTokens.win32.ts b/packages/components/TabList/src/Tab/TabColorTokens.win32.ts index d68d6cd9b3..d8fba38f76 100644 --- a/packages/components/TabList/src/Tab/TabColorTokens.win32.ts +++ b/packages/components/TabList/src/Tab/TabColorTokens.win32.ts @@ -87,4 +87,7 @@ export const defaultTabColorTokens: TokenSettings = (t: Theme) focused: { borderColor: isHighContrast(t) ? t.colors.compoundBrandStroke1 : t.colors.neutralForeground1, }, + highlighted: { + borderColor: isHighContrast(t) ? t.colors.compoundBrandStroke1 : t.colors.neutralForeground1, + }, } as TabTokens); diff --git a/packages/components/TabList/src/Tab/TabTokens.ts b/packages/components/TabList/src/Tab/TabTokens.ts index 45166b6d43..59c50d9efd 100644 --- a/packages/components/TabList/src/Tab/TabTokens.ts +++ b/packages/components/TabList/src/Tab/TabTokens.ts @@ -15,6 +15,7 @@ export const tabStates: (keyof TabTokens)[] = [ 'disabled', 'selected', 'focused', + 'highlighted', 'pressed', 'transparent', 'subtle',