diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a03cdf5ba..67fa9f48d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Features - Add `Loader` component @layershifter ([#685](https://github.com/stardust-ui/react/pull/685)) - Add `color` prop to `Label` component @Bugaa92 ([#647](https://github.com/stardust-ui/react/pull/647)) +- Add `color` prop to `Menu` component @Bugaa92 ([#712](https://github.com/stardust-ui/react/pull/712)) ### Fixes - Fix focus outline visible only during keyboard navigation @kolaps33 ([#689] https://github.com/stardust-ui/react/pull/689) diff --git a/docs/src/examples/components/Menu/Variations/MenuExampleColor.shorthand.tsx b/docs/src/examples/components/Menu/Variations/MenuExampleColor.shorthand.tsx new file mode 100644 index 0000000000..fbc925e64a --- /dev/null +++ b/docs/src/examples/components/Menu/Variations/MenuExampleColor.shorthand.tsx @@ -0,0 +1,61 @@ +import * as React from 'react' +import * as _ from 'lodash' +import { Menu, ProviderConsumer, Grid, Text } from '@stardust-ui/react' + +const items = [ + { key: 'editorials', content: 'Editorials' }, + { key: 'review', content: 'Reviews' }, + { key: 'events', content: 'Upcoming Events' }, +] + +const iconItems = [ + { key: 'home', content: 'Home', icon: 'home' }, + { key: 'users', content: 'Users', icon: 'users' }, + { key: 'search', icon: 'search' }, +] + +const MenuExampleColor = () => ( + { + const colorsArr = _.keys(colorScheme) + const colors = _.times(7, num => colorsArr[num % colorsArr.length]) + + return ( + + + + + + + + + + + + + + + _.pick(item, ['key', 'icon']))} + /> + + + + ) + }} + /> +) + +export default MenuExampleColor diff --git a/docs/src/examples/components/Menu/Variations/MenuExampleVerticalPointing.shorthand.tsx b/docs/src/examples/components/Menu/Variations/MenuExampleVerticalPointing.shorthand.tsx index 1ef312f098..80f921c0de 100644 --- a/docs/src/examples/components/Menu/Variations/MenuExampleVerticalPointing.shorthand.tsx +++ b/docs/src/examples/components/Menu/Variations/MenuExampleVerticalPointing.shorthand.tsx @@ -8,7 +8,7 @@ const items = [ ] const MenuExampleVerticalPointing = () => ( - + ) export default MenuExampleVerticalPointing diff --git a/docs/src/examples/components/Menu/Variations/MenuExampleVerticalPointingEnd.shorthand.tsx b/docs/src/examples/components/Menu/Variations/MenuExampleVerticalPointingEnd.shorthand.tsx index 4d5c3dda53..a3fba99b7f 100644 --- a/docs/src/examples/components/Menu/Variations/MenuExampleVerticalPointingEnd.shorthand.tsx +++ b/docs/src/examples/components/Menu/Variations/MenuExampleVerticalPointingEnd.shorthand.tsx @@ -8,7 +8,7 @@ const items = [ ] const MenuExampleVerticalPointingEnd = () => ( - + ) export default MenuExampleVerticalPointingEnd diff --git a/docs/src/examples/components/Menu/Variations/index.tsx b/docs/src/examples/components/Menu/Variations/index.tsx index 32d2f205fe..5ad5f29627 100644 --- a/docs/src/examples/components/Menu/Variations/index.tsx +++ b/docs/src/examples/components/Menu/Variations/index.tsx @@ -114,6 +114,11 @@ const Variations = () => ( description="A menu with Toolbar accessibility behavior." examplePath="components/Menu/Variations/MenuExampleToolbar" /> + ) diff --git a/src/components/Label/Label.tsx b/src/components/Label/Label.tsx index 0c09346daa..1ef04bd0d2 100644 --- a/src/components/Label/Label.tsx +++ b/src/components/Label/Label.tsx @@ -12,6 +12,7 @@ import { ContentComponentProps, commonPropTypes, ColorComponentProps, + ComplexColorPropType, } from '../../lib' import Icon from '../Icon/Icon' @@ -19,7 +20,6 @@ import Image from '../Image/Image' import Layout from '../Layout/Layout' import { Accessibility } from '../../lib/accessibility/types' import { ReactProps, ShorthandValue } from '../../../types/utils' -import { ComplexColorPropType } from '../../lib/commonPropInterfaces' export interface LabelProps extends UIComponentProps, diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx index 414b76b00a..a174b42d4a 100644 --- a/src/components/Menu/Menu.tsx +++ b/src/components/Menu/Menu.tsx @@ -11,6 +11,8 @@ import { ChildrenComponentProps, commonPropTypes, getKindProp, + ColorComponentProps, + ComplexColorPropType, } from '../../lib' import MenuItem from './MenuItem' import { menuBehavior } from '../../lib/accessibility' @@ -22,7 +24,10 @@ import MenuDivider from './MenuDivider' export type MenuShorthandKinds = 'divider' | 'item' -export interface MenuProps extends UIComponentProps, ChildrenComponentProps { +export interface MenuProps + extends UIComponentProps, + ChildrenComponentProps, + ColorComponentProps { /** * Accessibility behavior if overridden by the user. * @default menuBehavior @@ -89,6 +94,7 @@ class Menu extends AutoControlledComponent, MenuState> { static propTypes = { ...commonPropTypes.createCommon({ content: false, + color: 'complex', }), accessibility: PropTypes.func, activeIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), @@ -136,6 +142,7 @@ class Menu extends AutoControlledComponent, MenuState> { renderItems = (variables: ComponentVariablesObject) => { const { + color, iconOnly, items, pills, @@ -166,6 +173,7 @@ class Menu extends AutoControlledComponent, MenuState> { return MenuItem.create(item, { defaultProps: { + color, iconOnly, pills, pointing, diff --git a/src/components/Menu/MenuItem.tsx b/src/components/Menu/MenuItem.tsx index 24d505e5fa..74d210c7fa 100644 --- a/src/components/Menu/MenuItem.tsx +++ b/src/components/Menu/MenuItem.tsx @@ -15,6 +15,8 @@ import { commonPropTypes, isFromKeyboard, EventStack, + ColorComponentProps, + ComplexColorPropType, } from '../../lib' import Icon from '../Icon/Icon' import Menu from '../Menu/Menu' @@ -28,7 +30,8 @@ import Ref from '../Ref/Ref' export interface MenuItemProps extends UIComponentProps, ChildrenComponentProps, - ContentComponentProps { + ContentComponentProps, + ColorComponentProps { /** * Accessibility behavior if overridden by the user. * @default menuItemBehavior @@ -123,7 +126,7 @@ class MenuItem extends AutoControlledComponent, MenuIt static create: Function static propTypes = { - ...commonPropTypes.createCommon(), + ...commonPropTypes.createCommon({ color: 'complex' }), accessibility: PropTypes.func, active: PropTypes.bool, disabled: PropTypes.bool, @@ -172,7 +175,7 @@ class MenuItem extends AutoControlledComponent, MenuIt } renderComponent({ ElementType, classes, accessibility, unhandledProps, styles }) { - const { children, content, icon, wrapper, menu, primary, secondary, active } = this.props + const { children, color, content, icon, wrapper, menu, primary, secondary, active } = this.props const { menuOpen } = this.state @@ -202,6 +205,7 @@ class MenuItem extends AutoControlledComponent, MenuIt {Menu.create(menu, { defaultProps: { accessibility: submenuBehavior, + color, vertical: true, primary, secondary, diff --git a/src/lib/colorUtils.ts b/src/lib/colorUtils.ts index 3530cb2980..7b503f706d 100644 --- a/src/lib/colorUtils.ts +++ b/src/lib/colorUtils.ts @@ -18,10 +18,11 @@ export const mapColorsToScheme = ( typeof mapper === 'number' ? String(mapper) : (mapper as any), ) as ColorValues -export const getColorSchemeFn = (colorProp: string, colorScheme: ColorValues) => { - const colors = _.get(colorScheme, colorProp) - return (area: keyof T, defaultColor: string) => (colors ? colors[area] : defaultColor) -} +export const getColorFromScheme = ( + colorScheme: T, + area: keyof T, + defaultColor: string, +) => _.get(colorScheme, area, defaultColor) export const getColorSchemeFromObject = ( colorScheme: ColorValues>, @@ -61,7 +62,7 @@ export const generateColorScheme = ( // if the color prop is not defined, but the the color scheme is defined, then we are returning // the defaults from the color scheme if they exists if (colorScheme) { - return colorScheme && colorScheme.default ? colorScheme.default : {} + return colorScheme.default || {} } // if the color scheme is not defined, then if the color prop is a scheme object we are diff --git a/src/lib/commonPropInterfaces.ts b/src/lib/commonPropInterfaces.ts index 8dbeb7776f..4351b3fe78 100644 --- a/src/lib/commonPropInterfaces.ts +++ b/src/lib/commonPropInterfaces.ts @@ -40,13 +40,10 @@ export type ColorValue = | string export type ComplexColorPropType = - | { - foreground?: ColorValue - background?: ColorValue - border?: ColorValue - shadow?: ColorValue - } | ColorValue + | Partial< + Record<'foreground' | 'background' | 'border' | 'shadow' | 'lighterBackground', ColorValue> + > export interface ColorComponentProps { /** A component can have a color. */ diff --git a/src/themes/base/colors.ts b/src/themes/base/colors.ts index e49ca7af62..a4deee1cfd 100644 --- a/src/themes/base/colors.ts +++ b/src/themes/base/colors.ts @@ -169,11 +169,13 @@ export const colorScheme: ColorSchemeMapping = _.mapValues( border: foreground, shadow: foreground, background: colorVariants[500], + lighterBackground: colorVariants[300], default: { foreground: colors.grey[600], border: colors.grey[600], shadow: colors.grey[600], background: colors.grey[100], + lighterBackground: colors.grey[300], }, } }, diff --git a/src/themes/teams/colors.ts b/src/themes/teams/colors.ts index 199794c570..657dc5dbb5 100644 --- a/src/themes/teams/colors.ts +++ b/src/themes/teams/colors.ts @@ -177,11 +177,13 @@ export const colorScheme: ColorSchemeMapping = _.mapValues( border: foreground, shadow: foreground, background: colorVariants[500], + lighterBackground: colorVariants[300], default: { foreground: colors.grey[600], border: colors.grey[600], shadow: colors.grey[600], background: colors.grey[100], + lighterBackground: colors.grey[300], }, } }, diff --git a/src/themes/teams/components/Menu/menuItemStyles.ts b/src/themes/teams/components/Menu/menuItemStyles.ts index f0553f481a..9d5f8e2e34 100644 --- a/src/themes/teams/components/Menu/menuItemStyles.ts +++ b/src/themes/teams/components/Menu/menuItemStyles.ts @@ -1,6 +1,13 @@ +import * as _ from 'lodash' + import { getSideArrow } from '../../utils' -import { pxToRem } from '../../../../lib' -import { ComponentSlotStyleFunction, ComponentSlotStylesInput, ICSSInJSStyle } from '../../../types' +import { pxToRem, getColorFromScheme } from '../../../../lib' +import { + ComponentSlotStyleFunction, + ComponentSlotStylesInput, + ICSSInJSStyle, + ColorScheme, +} from '../../../types' import { MenuVariables } from './menuVariables' import { MenuItemProps, MenuItemState } from '../../../../components/Menu/MenuItem' @@ -13,61 +20,71 @@ const underlinedItem = (color: string): ICSSInJSStyle => ({ }) const getActionStyles = ({ - props: { primary, underlined, iconOnly, isFromKeyboard }, + props: { primary, underlined, iconOnly, pointing, vertical }, variables: v, - color, + colorVariable, + colors, }: { props: MenuItemPropsAndState variables: MenuVariables - color: string + colorVariable: string + colors: Partial }): ICSSInJSStyle => underlined || iconOnly ? { - color, + color: colorVariable, background: v.backgroundColor, } + : pointing && vertical + ? { background: v.activeBackgroundColor } : primary ? { - color: v.primaryActiveColor, - background: v.primaryActiveBackgroundColor, + color: getColorFromScheme(colors, 'foreground', v.primaryActiveColor), + background: getColorFromScheme(colors, 'background', v.primaryActiveBackgroundColor), } : { - color, - background: v.activeBackgroundColor, + color: getColorFromScheme(colors, 'foreground', colorVariable), + background: getColorFromScheme(colors, 'background', v.activeBackgroundColor), } const getFocusedStyles = ({ props, variables: v, - color, + colors, }: { props: MenuItemPropsAndState variables: MenuVariables - color: string + colors: Partial }): ICSSInJSStyle => { - const { primary, underlined, iconOnly, isFromKeyboard, active } = props + const { primary, underlined, iconOnly, isFromKeyboard, active, pointing, vertical } = props if (active && !underlined) return {} - return { - ...((underlined && !isFromKeyboard) || iconOnly - ? { - color, - background: v.backgroundColor, - } - : primary - ? { - color: v.primaryFocusedColor, - background: v.primaryFocusedBackgroundColor, - } - : { - color, - background: v.focusedBackgroundColor, - }), - } + + return (underlined && !isFromKeyboard) || iconOnly + ? { + color: v.activeColor, + background: v.backgroundColor, + } + : pointing && vertical + ? { background: v.focusedBackgroundColor } + : primary + ? { + color: getColorFromScheme(colors, 'foreground', v.primaryFocusedColor), + background: getColorFromScheme( + colors, + 'lighterBackground', + v.primaryFocusedBackgroundColor, + ), + } + : { + color: getColorFromScheme(colors, 'foreground', v.activeColor), + background: getColorFromScheme(colors, 'lighterBackground', v.focusedBackgroundColor), + } } const itemSeparator: ComponentSlotStyleFunction = ({ props, variables: v, + colors, }): ICSSInJSStyle => { const { iconOnly, pointing, pills, primary, underlined, vertical } = props @@ -81,8 +98,12 @@ const itemSeparator: ComponentSlotStyleFunction = ({ props, variables: v, + colors, }): ICSSInJSStyle => { const { pointing, primary } = props - - let backgroundColor: string - let borderColor: string - let top: string - let borders: ICSSInJSStyle - - if (primary) { - backgroundColor = v.primaryActiveBackgroundColor - borderColor = v.primaryBorderColor - } else { - backgroundColor = v.activeBackgroundColor - borderColor = v.borderColor - } - - if (pointing === 'start') { - borders = { - borderTop: `1px solid ${borderColor}`, - borderLeft: `1px solid ${borderColor}`, - } - top = '-1px' // 1px for the border - } else { - borders = { - borderBottom: `1px solid ${borderColor}`, - borderRight: `1px solid ${borderColor}`, - } - top = '100%' - } + const backgroundColor = getColorFromScheme( + colors, + 'background', + primary ? v.primaryActiveBackgroundColor : v.activeBackgroundColor, + ) + const borderValue = `1px solid ${getColorFromScheme( + colors, + 'background', + primary ? v.primaryBorderColor : v.borderColor, + )}` + + const { top, borders }: { top: string; borders: ICSSInJSStyle } = + pointing === 'start' + ? { + top: '-1px', // 1px for the border + borders: { + borderTop: borderValue, + borderLeft: borderValue, + }, + } + : { + top: '100%', + borders: { + borderBottom: borderValue, + borderRight: borderValue, + }, + } return { '::after': { @@ -149,6 +171,7 @@ const menuItemStyles: ComponentSlotStylesInput { + root: ({ props, variables: v, theme, colors }): ICSSInJSStyle => { const { active, iconOnly, isFromKeyboard, pointing, primary, underlined, vertical } = props const { arrowDown } = theme.siteVariables const sideArrow = getSideArrow(theme) + const iconOnlyColor = getColorFromScheme(colors, 'background', v.activeColor) + const iconOnlyColorPrimary = getColorFromScheme( + colors, + 'background', + v.primaryActiveBorderColor, + ) return { color: 'inherit', @@ -280,16 +315,18 @@ const menuItemStyles: ComponentSlotStylesInput ({ }) export default { - root: ({ props, variables }): ICSSInJSStyle => { + root: ({ props, variables: v, colors }): ICSSInJSStyle => { const { iconOnly, fluid, pointing, pills, primary, underlined, vertical } = props + return { display: 'flex', ...(iconOnly && { alignItems: 'center' }), @@ -27,14 +30,21 @@ export default { !iconOnly && !(pointing && vertical) && !underlined && { - ...solidBorder(variables.borderColor), - ...(primary && { - ...solidBorder(variables.primaryBorderColor), - }), + ...solidBorder( + getColorFromScheme( + colors, + 'background', + primary ? v.primaryBorderColor : v.borderColor, + ), + ), borderRadius: pxToRem(4), }), ...(underlined && { - borderBottom: `2px solid ${variables.primaryUnderlinedBorderColor}`, + borderBottom: `2px solid ${getColorFromScheme( + colors, + 'background', + v.primaryUnderlinedBorderColor, + )}`, }), minHeight: pxToRem(24), margin: 0, diff --git a/src/themes/teams/components/Menu/menuVariables.ts b/src/themes/teams/components/Menu/menuVariables.ts index 895dac925e..93678e4097 100644 --- a/src/themes/teams/components/Menu/menuVariables.ts +++ b/src/themes/teams/components/Menu/menuVariables.ts @@ -1,6 +1,14 @@ -import { pxToRem } from '../../../../lib' +import { pxToRem, getColorSchemeWithCustomDefaults } from '../../../../lib' +import { ColorValues, ColorScheme, SiteVariablesPrepared } from '../../../types' + +export type MenuColorScheme = Pick< + ColorScheme, + 'foreground' | 'background' | 'border' | 'lighterBackground' +> export interface MenuVariables { + colorScheme: ColorValues + color: string backgroundColor: string @@ -24,8 +32,14 @@ export interface MenuVariables { lineHeightBase: string } -export default (siteVars: any): MenuVariables => { +export default (siteVars: SiteVariablesPrepared): MenuVariables => { return { + colorScheme: getColorSchemeWithCustomDefaults(siteVars.colorScheme, { + foreground: undefined, + background: undefined, + border: undefined, + }), + color: siteVars.gray02, backgroundColor: siteVars.white, diff --git a/src/themes/types.ts b/src/themes/types.ts index 3882596ce5..a7d3145eff 100644 --- a/src/themes/types.ts +++ b/src/themes/types.ts @@ -99,12 +99,10 @@ export type ColorPalette = ExtendablePalette< /** * A type for the generic color scheme of a component based on CSS property names */ -export type ColorScheme = { - foreground: string - background: string - border: string - shadow: string -} +export type ColorScheme = Record< + 'foreground' | 'background' | 'border' | 'shadow' | 'lighterBackground', + string +> export type ColorSchemeMapping = ColorValues & { default?: ColorScheme }