diff --git a/CHANGELOG.md b/CHANGELOG.md index fc4f09cbf7..4a03cdf5ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,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)) ### 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/Label/Variations/LabelExampleColor.shorthand.tsx b/docs/src/examples/components/Label/Variations/LabelExampleColor.shorthand.tsx new file mode 100644 index 0000000000..cf5b146add --- /dev/null +++ b/docs/src/examples/components/Label/Variations/LabelExampleColor.shorthand.tsx @@ -0,0 +1,21 @@ +import * as React from 'react' +import * as _ from 'lodash' +import { Label, ProviderConsumer, Grid } from '@stardust-ui/react' + +const LabelExampleColor = () => ( + + + _.keys(colorScheme).map(color => ( + +) + +export default LabelExampleColor diff --git a/docs/src/examples/components/Label/Variations/index.tsx b/docs/src/examples/components/Label/Variations/index.tsx index 28aa3f55bd..b84d877a3e 100644 --- a/docs/src/examples/components/Label/Variations/index.tsx +++ b/docs/src/examples/components/Label/Variations/index.tsx @@ -6,9 +6,14 @@ const Variations = () => ( + ) diff --git a/src/components/Label/Label.tsx b/src/components/Label/Label.tsx index 6e6f9517c8..0c09346daa 100644 --- a/src/components/Label/Label.tsx +++ b/src/components/Label/Label.tsx @@ -11,6 +11,7 @@ import { ChildrenComponentProps, ContentComponentProps, commonPropTypes, + ColorComponentProps, } from '../../lib' import Icon from '../Icon/Icon' @@ -18,11 +19,13 @@ 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, ChildrenComponentProps, - ContentComponentProps { + ContentComponentProps, + ColorComponentProps { accessibility?: Accessibility /** A Label can be circular. */ @@ -55,7 +58,7 @@ class Label extends UIComponent, any> { static className = 'ui-label' static propTypes = { - ...commonPropTypes.createCommon(), + ...commonPropTypes.createCommon({ color: 'complex' }), circular: PropTypes.bool, icon: customPropTypes.itemShorthand, iconPosition: PropTypes.oneOf(['start', 'end']), diff --git a/src/lib/colorUtils.ts b/src/lib/colorUtils.ts index 471b27aa54..3530cb2980 100644 --- a/src/lib/colorUtils.ts +++ b/src/lib/colorUtils.ts @@ -1,5 +1,13 @@ import * as _ from 'lodash' -import { SiteVariablesInput, ColorVariants, ColorValues } from '../themes/types' +import { + SiteVariablesInput, + ColorVariants, + ColorValues, + ColorSchemeMapping, + ColorScheme, +} from '../themes/types' +import { Partial } from '../../types/utils' +import { ComplexColorPropType } from './commonPropInterfaces' export const mapColorsToScheme = ( siteVars: SiteVariablesInput, @@ -9,3 +17,60 @@ export const mapColorsToScheme = ( { ...siteVars.emphasisColors, ...siteVars.naturalColors }, 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 getColorSchemeFromObject = ( + colorScheme: ColorValues>, + colors: ComplexColorPropType, +): Partial => + _.mapValues(colors, (color, colorName) => { + // if the color scheme contains the color, then get the value from it, otherwise return the color provided + const colorSchemeValue = _.get(colorScheme, color, colorScheme.default[color]) + return colorSchemeValue ? colorSchemeValue[colorName] : colors[colorName] + }) + +export const getColorSchemeWithCustomDefaults = ( + colorScheme: ColorSchemeMapping, + customDefaultValues: Partial, +) => { + const mergedDefaultValues = { + ...colorScheme.default, + ...customDefaultValues, + } + return { + ...colorScheme, + default: mergedDefaultValues, + } +} + +export const generateColorScheme = ( + colorProp: ComplexColorPropType, + colorScheme: ColorValues>, +): Partial => { + // if both color prop and color scheme are defined, we are merging them + if (colorProp && colorScheme) { + return typeof colorProp === 'string' + ? _.get(colorScheme, colorProp as string, colorScheme.default) + : { ...colorScheme.default, ...getColorSchemeFromObject(colorScheme, colorProp) } + } + + // 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 : {} + } + + // if the color scheme is not defined, then if the color prop is a scheme object we are + // returning it, otherwise we return an empty object, as it means that the component is + // implementing the simple color prop + if (colorProp) { + return typeof colorProp === 'string' ? {} : colorProp + } + + // if neither the color prop, nor the color scheme are defined, we are returning empty object + return {} +} diff --git a/src/lib/commonPropInterfaces.ts b/src/lib/commonPropInterfaces.ts index df04272e05..8dbeb7776f 100644 --- a/src/lib/commonPropInterfaces.ts +++ b/src/lib/commonPropInterfaces.ts @@ -25,21 +25,32 @@ export interface UIComponentProps

className?: string } -export interface ColorComponentProps { +export type ColorValue = + | 'primary' + | 'secondary' + | 'blue' + | 'green' + | 'grey' + | 'orange' + | 'pink' + | 'purple' + | 'teal' + | 'red' + | 'yellow' + | string + +export type ComplexColorPropType = + | { + foreground?: ColorValue + background?: ColorValue + border?: ColorValue + shadow?: ColorValue + } + | ColorValue + +export interface ColorComponentProps { /** A component can have a color. */ - color?: - | 'primary' - | 'secondary' - | 'blue' - | 'green' - | 'grey' - | 'orange' - | 'pink' - | 'purple' - | 'teal' - | 'red' - | 'yellow' - | string + color?: TColor } export interface ContentComponentProps { diff --git a/src/lib/commonPropTypes.ts b/src/lib/commonPropTypes.ts index 0dab2712a4..699e023cc7 100644 --- a/src/lib/commonPropTypes.ts +++ b/src/lib/commonPropTypes.ts @@ -6,11 +6,38 @@ export interface CreateCommonConfig { children?: boolean | 'node' | 'element' as?: boolean className?: boolean - color?: boolean + color?: boolean | 'simple' | 'complex' content?: boolean | 'node' | 'shorthand' styled?: boolean } +const colorPropType = PropTypes.oneOfType([ + PropTypes.oneOf([ + 'primary', + 'secondary', + 'blue', + 'green', + 'grey', + 'orange', + 'pink', + 'purple', + 'teal', + 'red', + 'yellow', + ]), + PropTypes.string, +]) + +export const complexColorPropType = PropTypes.oneOfType([ + PropTypes.shape({ + foreground: colorPropType, + background: colorPropType, + border: colorPropType, + shadow: colorPropType, + }), + colorPropType, +]) + export const createCommon = (config: CreateCommonConfig = {}) => { const { animated = true, @@ -35,22 +62,7 @@ export const createCommon = (config: CreateCommonConfig = {}) => { className: PropTypes.string, }), ...(color && { - color: PropTypes.oneOfType([ - PropTypes.oneOf([ - 'primary', - 'secondary', - 'blue', - 'green', - 'grey', - 'orange', - 'pink', - 'purple', - 'teal', - 'red', - 'yellow', - ]), - PropTypes.string, - ]), + color: color === true || color === 'simple' ? colorPropType : complexColorPropType, }), ...(content && { content: diff --git a/src/lib/index.ts b/src/lib/index.ts index ae64df0ebb..bff791ebbd 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -3,7 +3,7 @@ import * as commonPropTypes from './commonPropTypes' export { default as AutoControlledComponent } from './AutoControlledComponent' export { default as childrenExist } from './childrenExist' -export { mapColorsToScheme } from './colorUtils' +export * from './colorUtils' export { default as UIComponent } from './UIComponent' export { EventStack } from './eventStack' export { felaRenderer, felaRtlRenderer } from './felaRenderer' diff --git a/src/lib/renderComponent.tsx b/src/lib/renderComponent.tsx index eb8bce6c37..ee859554b0 100644 --- a/src/lib/renderComponent.tsx +++ b/src/lib/renderComponent.tsx @@ -32,6 +32,7 @@ import { mergeComponentStyles, mergeComponentVariables } from './mergeThemes' import { FocusZoneProps, FocusZone, FocusZone as FabricFocusZone } from './accessibility/FocusZone' import { FOCUSZONE_WRAP_ATTRIBUTE } from './accessibility/FocusZone/focusUtilities' import createAnimationStyles from './createAnimationStyles' +import { generateColorScheme } from './index' export interface RenderResultConfig

{ // TODO: Switch back to React.ReactType after issue will be resolved @@ -159,6 +160,7 @@ const renderComponent =

(config: RenderConfig

): React.ReactElem const { siteVariables = { + colorScheme: {}, colors: {}, contextualColors: {}, emphasisColors: {}, @@ -202,10 +204,14 @@ const renderComponent =

(config: RenderConfig

): React.ReactElem { handledProps: [...handledProps, ...accessibility.handledProps] }, props, ) + + const colors = generateColorScheme(stateAndProps.color, resolvedVariables.colorScheme) + const styleParam: ComponentStyleFunctionParam = { props: stateAndProps, variables: resolvedVariables, theme, + colors, } mergedStyles.root = { diff --git a/src/themes/base/colors.ts b/src/themes/base/colors.ts index 640888e214..e49ca7af62 100644 --- a/src/themes/base/colors.ts +++ b/src/themes/base/colors.ts @@ -1,4 +1,12 @@ -import { ColorPalette, ContextualColors, EmphasisColors, NaturalColors } from '../types' +import * as _ from 'lodash' + +import { + ColorPalette, + ContextualColors, + EmphasisColors, + NaturalColors, + ColorSchemeMapping, +} from '../types' export const naturalColors: NaturalColors = { blue: { @@ -135,11 +143,38 @@ export const contextualColors: ContextualColors = { warning: naturalColors.yellow, } -export const colors: ColorPalette = { - ...contextualColors, +const emphasisAndNaturalColors: EmphasisColors & NaturalColors = { ...emphasisColors, ...naturalColors, +} + +const lightBackgroundColors = ['teal', 'yellow'] +const isLightBackground = (colorName: string) => _.includes(lightBackgroundColors, colorName) + +export const colors: ColorPalette = { + ...emphasisAndNaturalColors, + ...contextualColors, black: '#000', white: '#fff', } + +export const colorScheme: ColorSchemeMapping = _.mapValues( + emphasisAndNaturalColors, + (colorVariants, colorName) => { + const foreground = isLightBackground(colorName) ? colors.black : colorVariants[50] + + return { + foreground, + border: foreground, + shadow: foreground, + background: colorVariants[500], + default: { + foreground: colors.grey[600], + border: colors.grey[600], + shadow: colors.grey[600], + background: colors.grey[100], + }, + } + }, +) diff --git a/src/themes/base/siteVariables.ts b/src/themes/base/siteVariables.ts index 45a44de555..81ced90456 100644 --- a/src/themes/base/siteVariables.ts +++ b/src/themes/base/siteVariables.ts @@ -2,7 +2,7 @@ // COLORS // -export { colors, contextualColors, emphasisColors, naturalColors } from './colors' +export { colors, contextualColors, emphasisColors, naturalColors, colorScheme } from './colors' // // FONT SIZES diff --git a/src/themes/teams/colors.ts b/src/themes/teams/colors.ts index d342ea9d54..199794c570 100644 --- a/src/themes/teams/colors.ts +++ b/src/themes/teams/colors.ts @@ -1,4 +1,12 @@ -import { ColorPalette, ContextualColors, EmphasisColors, NaturalColors } from '../types' +import * as _ from 'lodash' + +import { + ColorPalette, + ContextualColors, + EmphasisColors, + NaturalColors, + ColorSchemeMapping, +} from '../types' export const emphasisColors: EmphasisColors = { primary: { @@ -142,12 +150,39 @@ export const contextualColors: ContextualColors = { text: naturalColors.grey, } -export const colors: ColorPalette = { - ...contextualColors, +const emphasisAndNaturalColors: EmphasisColors & NaturalColors = { ...emphasisColors, ...naturalColors, +} + +const lightBackgroundColors = ['orange', 'yellow', 'lightGreen', 'postOrange'] +const isLightBackground = (colorName: string) => _.includes(lightBackgroundColors, colorName) + +export const colors: ColorPalette = { + ...emphasisAndNaturalColors, + ...contextualColors, // Primitive colors black: naturalColors.grey[900], white: naturalColors.grey[50], } + +export const colorScheme: ColorSchemeMapping = _.mapValues( + emphasisAndNaturalColors, + (colorVariants, colorName) => { + const foreground = isLightBackground(colorName) ? colors.black : colorVariants[50] + + return { + foreground, + border: foreground, + shadow: foreground, + background: colorVariants[500], + default: { + foreground: colors.grey[600], + border: colors.grey[600], + shadow: colors.grey[600], + background: colors.grey[100], + }, + } + }, +) diff --git a/src/themes/teams/components/Label/labelStyles.ts b/src/themes/teams/components/Label/labelStyles.ts index 20aef2a6be..a188c2dd21 100644 --- a/src/themes/teams/components/Label/labelStyles.ts +++ b/src/themes/teams/components/Label/labelStyles.ts @@ -1,42 +1,44 @@ +import * as _ from 'lodash' + import { pxToRem } from '../../../../lib' import { ComponentSlotStylesInput, ICSSInJSStyle } from '../../../types' import { LabelProps } from '../../../../components/Label/Label' +import { LabelVariables } from './labelVariables' -const labelStyles: ComponentSlotStylesInput = { - root: ({ props: { image, imagePosition, circular }, variables }): ICSSInJSStyle => ({ - padding: variables.padding, - ...(image && - imagePosition === 'start' && { - paddingLeft: variables.startPaddingLeft, - }), - ...(image && - imagePosition === 'end' && { - paddingRight: variables.endPaddingRight, - }), - display: 'inline-flex', - alignItems: 'center', - height: variables.height, - fontSize: pxToRem(14), - lineHeight: variables.height, - backgroundColor: variables.backgroundColor, - color: variables.color, - borderRadius: pxToRem(3), - ...(circular && { - borderRadius: variables.circularRadius, - }), - overflow: 'hidden', - }), - image: ({ variables }): ICSSInJSStyle => ({ - height: variables.height, - width: variables.height, - }), - icon: ({ props }): ICSSInJSStyle => ({ - ...(props.icon && - typeof props.icon === 'object' && - (props.icon as any).onClick && { - cursor: 'pointer', +const labelStyles: ComponentSlotStylesInput = { + root: ({ props: p, variables: v, colors }): ICSSInJSStyle => { + return { + display: 'inline-flex', + alignItems: 'center', + overflow: 'hidden', + height: v.height, + lineHeight: v.height, + color: colors.foreground, + backgroundColor: colors.background, + fontSize: pxToRem(14), + borderRadius: pxToRem(3), + padding: v.padding, + ...(p.image && + (p.imagePosition === 'start' + ? { paddingLeft: v.startPaddingLeft } + : { paddingRight: v.endPaddingRight })), + ...(p.circular && { + borderRadius: v.circularRadius, }), + } + }, + + image: ({ variables: v }): ICSSInJSStyle => ({ + height: v.height, + width: v.height, }), + + icon: ({ props: p }): ICSSInJSStyle => + p.icon && + typeof p.icon === 'object' && + (p.icon as any).onClick && { + cursor: 'pointer', + }, } export default labelStyles diff --git a/src/themes/teams/components/Label/labelVariables.ts b/src/themes/teams/components/Label/labelVariables.ts index 4391b63417..8f9256fc05 100644 --- a/src/themes/teams/components/Label/labelVariables.ts +++ b/src/themes/teams/components/Label/labelVariables.ts @@ -1,20 +1,33 @@ -import { pxToRem } from '../../../../lib' +import { pxToRem, getColorSchemeWithCustomDefaults } from '../../../../lib' +import { ColorValues, ColorScheme, SiteVariablesPrepared } from '../../../types' -export default () => { +type LabelColorScheme = Pick + +export interface LabelVariables { + colorScheme: ColorValues + circularRadius: string + padding: string + startPaddingLeft: string + endPaddingRight: string + height: string + iconColor: string +} + +export default (siteVars: SiteVariablesPrepared): LabelVariables => { const color = 'rgba(0, 0, 0, 0.6)' return { + colorScheme: getColorSchemeWithCustomDefaults(siteVars.colorScheme, { + foreground: color, + background: 'rgb(232, 232, 232)', + }), circularRadius: pxToRem(9999), padding: `0 ${pxToRem(4)} 0 ${pxToRem(4)}`, - color, - backgroundColor: 'rgb(232, 232, 232)', startPaddingLeft: '0px', endPaddingRight: '0px', height: pxToRem(20), // variables for 'icon' part - icon: { - color, - }, + iconColor: color, } } diff --git a/src/themes/teams/components/Menu/menuItemStyles.ts b/src/themes/teams/components/Menu/menuItemStyles.ts index ee58466747..f0553f481a 100644 --- a/src/themes/teams/components/Menu/menuItemStyles.ts +++ b/src/themes/teams/components/Menu/menuItemStyles.ts @@ -142,7 +142,7 @@ const pointingBeak: ComponentSlotStyleFunction = { - wrapper: ({ props, variables: v, theme }): ICSSInJSStyle => { + wrapper: ({ props, variables: v, theme, colors }): ICSSInJSStyle => { const { active, iconOnly, @@ -198,7 +198,7 @@ const menuItemStyles: ComponentSlotStylesInput /** * A type for extracting the color names. */ -type ColorNames = keyof (EmphasisColorsStrict & NaturalColorsStrict) +export type ColorNames = keyof (EmphasisColorsStrict & NaturalColorsStrict) /** * A type for an extendable set of ColorNames properties of type T @@ -96,6 +96,18 @@ export type ColorPalette = ExtendablePalette< EmphasisColorsStrict & ContextualColorsStrict & NaturalColorsStrict & PrimitiveColors > +/** + * 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 ColorSchemeMapping = ColorValues & { default?: ColorScheme } + // ======================================================== // Props // ======================================================== @@ -117,6 +129,7 @@ export type State = ObjectOf export interface SiteVariablesInput extends ObjectOf { colors?: ColorPalette + colorScheme?: ColorSchemeMapping contextualColors?: ContextualColors emphasisColors?: EmphasisColors naturalColors?: NaturalColorsStrict @@ -124,13 +137,7 @@ export interface SiteVariablesInput extends ObjectOf { htmlFontSize?: string } -export interface SiteVariablesPrepared extends ObjectOf { - colors?: ColorPalette - contextualColors?: ContextualColors - emphasisColors?: EmphasisColors - naturalColors?: NaturalColorsStrict - brand?: string - htmlFontSize?: string +export interface SiteVariablesPrepared extends SiteVariablesInput { fontSizes: ObjectOf } @@ -190,6 +197,7 @@ export interface ComponentStyleFunctionParam< props: State & TProps variables: TVars theme: ThemePrepared + colors: Partial } export type ComponentSlotStyleFunction = ((