diff --git a/packages/shared-types/src/Colors.ts b/packages/shared-types/src/Colors.ts index 1e916366a1..337ebbb8e1 100644 --- a/packages/shared-types/src/Colors.ts +++ b/packages/shared-types/src/Colors.ts @@ -63,6 +63,28 @@ type Primitives = { red57: string red70: string red82: string + + sea12: string + sea30: string + sea45: string + sea70: string + sea110: string + sea35: string + sea40: string + sea50: string + sea57: string + sea90: string + + violet12: string + violet30: string + violet45: string + violet70: string + violet110: string + violet35: string + violet40: string + violet50: string + violet57: string + violet90: string } type AdditionalPrimitives = { @@ -219,6 +241,12 @@ type Contrasts = { red1212: Primitives['red12'] red4570: Primitives['red45'] | Primitives['red70'] red5782: Primitives['red57'] | Primitives['red82'] + + violet1212: Primitives['violet12'] + violet4570: Primitives['violet45'] | Primitives['violet70'] + violet5790: Primitives['violet57'] | Primitives['violet90'] + sea4570: Primitives['sea45'] | Primitives['sea70'] + sea5790: Primitives['sea57'] | Primitives['sea90'] } type UI = { diff --git a/packages/shared-types/src/ComponentThemeVariables.ts b/packages/shared-types/src/ComponentThemeVariables.ts index 1ebafa179d..8a225cd590 100644 --- a/packages/shared-types/src/ComponentThemeVariables.ts +++ b/packages/shared-types/src/ComponentThemeVariables.ts @@ -66,21 +66,24 @@ export type AlertTheme = { } export type AvatarTheme = { - background: Colors['contrasts']['white1010'] + background: string borderWidthSmall: Border['widthSmall'] borderWidthMedium: Border['widthMedium'] - borderColor: Colors['contrasts']['grey1214'] + borderColor: string boxShadowColor: string boxShadowBlur: string fontFamily: Typography['fontFamily'] fontWeight: Typography['fontWeightBold'] - color: Colors['contrasts']['blue4570'] - colorShamrock: Colors['contrasts']['green4570'] - colorBarney: Colors['contrasts']['blue4570'] - colorCrimson: Colors['contrasts']['orange4570'] - colorFire: Colors['contrasts']['red4570'] - colorLicorice: Colors['contrasts']['grey125125'] - colorAsh: Colors['contrasts']['grey4570'] + color: string + colorShamrock: string + colorBarney: string + colorCrimson: string + colorFire: string + colorLicorice: string + colorAsh: string + + aiTopGradientColor: string + aiBottomGradientColor: string } export type BadgeTheme = { @@ -1386,15 +1389,18 @@ export type TagTheme = { } export type TextTheme = Typography & { - primaryInverseColor: Colors['contrasts']['white1010'] - primaryColor: Colors['contrasts']['grey125125'] - secondaryColor: Colors['contrasts']['grey4570'] - secondaryInverseColor: Colors['contrasts']['grey1111'] - brandColor: Colors['contrasts']['blue4570'] - dangerColor: Colors['contrasts']['red4570'] - successColor: Colors['contrasts']['green4570'] - alertColor: Colors['contrasts']['blue4570'] - warningColor: Colors['contrasts']['orange5782'] + primaryInverseColor: string + primaryColor: string + secondaryColor: string + secondaryInverseColor: string + brandColor: string + dangerColor: string + successColor: string + alertColor: string + warningColor: string + aiColor: string + + aiBackgroundColor: string paragraphMargin: string | 0 } diff --git a/packages/ui-avatar/src/Avatar/README.md b/packages/ui-avatar/src/Avatar/README.md index a030d951c7..32a4ca84e4 100644 --- a/packages/ui-avatar/src/Avatar/README.md +++ b/packages/ui-avatar/src/Avatar/README.md @@ -4,6 +4,34 @@ describes: Avatar The avatar component can be used to display a user's avatar. When an image src is not supplied the user's initials will display. +## Variant + +Avatar has a variant prop, which currently only toggles it between `ai` and `default` behavior. `ai` is a preset where you can only change the `size` and `margin` visual props, all others are preset. In the following example, there are the `ai` variants. + +### ai + +```js +--- +type: example +readonly: true +--- +
+ + + + + + + + + +
+``` + +### default + +Most avatar's features are accessible through the `default` variant + Instead of the initials, an SVG icon can be displayed with the `renderIcon` property. The avatar can be `circle` _(default)_ or `rectangle`. Use the `margin` prop to add space between Avatar and other content. diff --git a/packages/ui-avatar/src/Avatar/index.tsx b/packages/ui-avatar/src/Avatar/index.tsx index 1dc7a969f1..8dff95f36d 100644 --- a/packages/ui-avatar/src/Avatar/index.tsx +++ b/packages/ui-avatar/src/Avatar/index.tsx @@ -32,6 +32,7 @@ import { } from 'react' import { View } from '@instructure/ui-view' +import { IconAiSolid } from '@instructure/ui-icons' import { callRenderProp, passthroughProps } from '@instructure/ui-react-utils' import type { AvatarProps } from './props' @@ -52,7 +53,8 @@ const Avatar = forwardRef( showBorder = 'auto', shape = 'circle', display = 'inline-block', - onImageLoaded = (_event: SyntheticEvent) => {}, + variant = 'default', + onImageLoaded = (_event: SyntheticEvent) => { }, src, name, renderIcon, @@ -78,7 +80,8 @@ const Avatar = forwardRef( shape, src, showBorder, - themeOverride + themeOverride, + variant }, componentId: 'Avatar', displayName: 'Avatar' @@ -122,11 +125,13 @@ const Avatar = forwardRef( } const renderContent = () => { - if (!renderIcon) { + const calcedRenderIcon = variant === 'ai' ? : renderIcon + + if (!calcedRenderIcon) { return renderInitials() } - return {callRenderProp(renderIcon)} + return {callRenderProp(calcedRenderIcon)} } return ( diff --git a/packages/ui-avatar/src/Avatar/props.ts b/packages/ui-avatar/src/Avatar/props.ts index 2029bf44d9..6510ba5770 100644 --- a/packages/ui-avatar/src/Avatar/props.ts +++ b/packages/ui-avatar/src/Avatar/props.ts @@ -100,6 +100,11 @@ type AvatarOwnProps = { * An icon, or function that returns an icon that gets displayed. If the `src` prop is provided, `src` will have priority. */ renderIcon?: Renderable + + /** + * if "ai", it will ignore most props (e.g.: shape, showBorder, onImageLoaded, hasInverseColor, color, name) and render an "ai avatar" + */ + variant?: 'default' | 'ai' } export type AvatarState = { @@ -149,7 +154,8 @@ const propTypes: PropValidators = { onImageLoaded: PropTypes.func, as: PropTypes.elementType, elementRef: PropTypes.func, - renderIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]) + renderIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), + variant: PropTypes.oneOf(['default', 'ai']) } const allowedProps: AllowedPropKeys = [ diff --git a/packages/ui-avatar/src/Avatar/styles.ts b/packages/ui-avatar/src/Avatar/styles.ts index bad4f54386..f5f162c616 100644 --- a/packages/ui-avatar/src/Avatar/styles.ts +++ b/packages/ui-avatar/src/Avatar/styles.ts @@ -34,6 +34,7 @@ type StyleParams = { src: AvatarProps['src'] showBorder: AvatarProps['showBorder'] themeOverride: AvatarProps['themeOverride'] + variant: AvatarProps['variant'] } /** * --- @@ -48,47 +49,108 @@ const generateStyle = ( componentTheme: AvatarTheme, params: StyleParams ): AvatarStyle => { - const { loaded, size, color, hasInverseColor, shape, src, showBorder } = - params + const { + loaded, + size, + color, + hasInverseColor, + shape, + src, + showBorder, + variant + } = params + + // NOTE: this is needed due to design changes. The size of the component is calculated from "em" which means it is + // tied to the fontSize. The font sizes changed for the icons, which meant that the container (component) size would've + // changed too without additional calculations + const calcNewScaler = ( + originalFontSize: number, + newFontSize: number, + originalScaler: number + ) => { + return `${(originalFontSize * originalScaler) / newFontSize}em` + } const sizeStyles = { auto: { fontSize: 'inherit', - borderWidth: componentTheme.borderWidthSmall + borderWidth: componentTheme.borderWidthSmall, + width: '2.5em', + height: '2.5em' }, 'xx-small': { - fontSize: '0.5rem', - borderWidth: componentTheme.borderWidthSmall + fontSize: '0.625rem', + borderWidth: componentTheme.borderWidthSmall, + width: calcNewScaler(0.5, 0.625, shape === 'circle' ? 2.5 : 3), + height: calcNewScaler(0.5, 0.625, 2.5) }, 'x-small': { - fontSize: '0.75rem', - borderWidth: componentTheme.borderWidthSmall + fontSize: '0.875rem', + borderWidth: componentTheme.borderWidthSmall, + width: calcNewScaler(0.75, 0.875, shape === 'circle' ? 2.5 : 3), + height: calcNewScaler(0.75, 0.875, 2.5) }, small: { - fontSize: '1rem', - borderWidth: componentTheme.borderWidthSmall + fontSize: '1.25rem', + borderWidth: componentTheme.borderWidthSmall, + width: calcNewScaler(1, 1.25, shape === 'circle' ? 2.5 : 3), + height: calcNewScaler(1, 1.25, 2.5) }, medium: { - fontSize: '1.25rem', - borderWidth: componentTheme.borderWidthMedium + fontSize: '1.5rem', + borderWidth: componentTheme.borderWidthMedium, + width: calcNewScaler(1.25, 1.5, shape === 'circle' ? 2.5 : 3), + height: calcNewScaler(1.25, 1.5, 2.5) }, large: { - fontSize: '1.5rem', - borderWidth: componentTheme.borderWidthMedium + fontSize: '1.75rem', + borderWidth: componentTheme.borderWidthMedium, + width: calcNewScaler(1.5, 1.75, shape === 'circle' ? 2.5 : 3), + height: calcNewScaler(1.5, 1.75, 2.5) }, 'x-large': { - fontSize: '1.75rem', - borderWidth: componentTheme.borderWidthMedium + fontSize: '2rem', + borderWidth: componentTheme.borderWidthMedium, + width: calcNewScaler(1.75, 2, shape === 'circle' ? 2.5 : 3), + height: calcNewScaler(1.75, 2, 2.5) }, 'xx-large': { - fontSize: '2rem', - borderWidth: componentTheme.borderWidthMedium + fontSize: '2.25rem', + borderWidth: componentTheme.borderWidthMedium, + width: calcNewScaler(2, 2.25, shape === 'circle' ? 2.5 : 3), + height: calcNewScaler(2, 2.25, 2.5) } } - const variantStyles = { + const initialSizeStyles = { + auto: { + fontSize: 'inherit' + }, + 'xx-small': { + fontSize: '0.5rem' + }, + 'x-small': { + fontSize: '0.75rem' + }, + small: { + fontSize: '1rem' + }, + medium: { + fontSize: '1.25rem' + }, + large: { + fontSize: '1.5rem' + }, + 'x-large': { + fontSize: '1.75rem' + }, + 'xx-large': { + fontSize: '2rem' + } + } + + const shapeStyles = { circle: { - width: '2.5em', position: 'relative', borderRadius: '100%', overflow: 'hidden' @@ -112,27 +174,23 @@ const generateStyle = ( ? colorVariants[color!] : componentTheme.background - const contentColor = hasInverseColor - ? componentTheme.background - : colorVariants[color!] + const contentColor = + variant === 'ai' + ? 'white' + : hasInverseColor + ? componentTheme.background + : colorVariants[color!] - return { - avatar: { - label: 'avatar', - height: '2.5em', - boxSizing: 'border-box', - backgroundColor: backgroundColor, - backgroundPosition: 'center', - backgroundSize: 'cover', - backgroundClip: 'content-box', - backgroundRepeat: 'no-repeat', - overflow: 'hidden', - lineHeight: 0, - textAlign: 'center', - borderStyle: 'solid', - borderColor: componentTheme.borderColor, - ...sizeStyles[size!], - ...variantStyles[shape!], + const variantStyles = { + ai: { + background: ` + linear-gradient(to bottom, ${componentTheme.aiTopGradientColor} 0%, ${componentTheme.aiBottomGradientColor} 100%) padding-box, + linear-gradient(to bottom right, ${componentTheme.aiTopGradientColor} 0%, ${componentTheme.aiBottomGradientColor} 100%) border-box`, + border: 'solid transparent', + ...shapeStyles['circle'] + }, + default: { + ...shapeStyles[shape!], ...(loaded ? { backgroundImage: `url('${src}')`, @@ -152,6 +210,25 @@ const generateStyle = ( ...(showBorder === 'never' && { border: 0 }) + } + } + + return { + avatar: { + label: 'avatar', + boxSizing: 'border-box', + backgroundColor: backgroundColor, + backgroundPosition: 'center', + backgroundSize: 'cover', + backgroundClip: 'content-box', + backgroundRepeat: 'no-repeat', + overflow: 'hidden', + lineHeight: 0, + textAlign: 'center', + borderStyle: 'solid', + borderColor: componentTheme.borderColor, + ...sizeStyles[size!], + ...variantStyles[variant!] }, initials: { label: 'avatar__initials', @@ -159,7 +236,8 @@ const generateStyle = ( lineHeight: '2.375em', fontFamily: componentTheme.fontFamily, fontWeight: componentTheme.fontWeight, - letterSpacing: '0.0313em' + letterSpacing: '0.0313em', + ...initialSizeStyles[size!] }, loadImage: { label: 'avatar__loadImage', diff --git a/packages/ui-avatar/src/Avatar/theme.ts b/packages/ui-avatar/src/Avatar/theme.ts index 8bef04e26b..0f06c61239 100644 --- a/packages/ui-avatar/src/Avatar/theme.ts +++ b/packages/ui-avatar/src/Avatar/theme.ts @@ -52,7 +52,10 @@ const generateComponentTheme = (theme: Theme): AvatarTheme => { colorCrimson: colors?.contrasts?.red4570, colorFire: colors?.contrasts?.orange4570, colorLicorice: colors?.contrasts?.grey125125, - colorAsh: colors?.contrasts?.grey4570 + colorAsh: colors?.contrasts?.grey4570, + + aiTopGradientColor: colors?.contrasts?.violet4570, + aiBottomGradientColor: colors?.contrasts?.sea4570 } return { diff --git a/packages/ui-text/src/Text/README.md b/packages/ui-text/src/Text/README.md index f6efd45167..9adf3adf25 100644 --- a/packages/ui-text/src/Text/README.md +++ b/packages/ui-text/src/Text/README.md @@ -26,6 +26,24 @@ type: example ``` +### Text colors + +```js +--- +type: example +--- +
+ I'm primary text
+ I'm secondary text
+ I'm brand text
+ I'm success text
+ I'm warning text
+ I'm danger text
+ I'm a highlighted text (by AI)
+ I'm alert text - DEPRECATED - DO NOT USE +
+``` + ### Font sizes ```js @@ -134,23 +152,6 @@ type: example ``` -### Text colors - -```js ---- -type: example ---- -
- I'm primary text
- I'm secondary text
- I'm brand text
- I'm success text
- I'm warning text
- I'm danger text
- I'm alert text - DEPRECATED - DO NOT USE -
-``` - ```js --- type: example diff --git a/packages/ui-text/src/Text/props.ts b/packages/ui-text/src/Text/props.ts index 426028ca4c..547ea36df2 100644 --- a/packages/ui-text/src/Text/props.ts +++ b/packages/ui-text/src/Text/props.ts @@ -52,6 +52,7 @@ type TextOwnProps = { | 'warning' | 'primary-inverse' | 'secondary-inverse' + | 'ai-highlight' /** * Provides a reference to the underlying HTML element */ @@ -119,7 +120,8 @@ const propTypes: PropValidators = { 'warning', 'alert', 'primary-inverse', - 'secondary-inverse' + 'secondary-inverse', + 'ai-highlight' ]), elementRef: PropTypes.func, fontStyle: PropTypes.oneOf(['italic', 'normal']), diff --git a/packages/ui-text/src/Text/styles.ts b/packages/ui-text/src/Text/styles.ts index dc40fbacd9..4d19aa8c37 100644 --- a/packages/ui-text/src/Text/styles.ts +++ b/packages/ui-text/src/Text/styles.ts @@ -105,7 +105,11 @@ const generateStyle = ( brand: { color: componentTheme.brandColor }, danger: { color: componentTheme.dangerColor }, alert: { color: componentTheme.alertColor }, - warning: { color: componentTheme.warningColor } + warning: { color: componentTheme.warningColor }, + 'ai-highlight': { + color: componentTheme.aiColor, + background: componentTheme.aiBackgroundColor + } } const wrapStyle = { diff --git a/packages/ui-text/src/Text/theme.ts b/packages/ui-text/src/Text/theme.ts index 370627df47..40364070c1 100644 --- a/packages/ui-text/src/Text/theme.ts +++ b/packages/ui-text/src/Text/theme.ts @@ -54,6 +54,9 @@ const generateComponentTheme = (theme: Theme): TextTheme => { successColor: colors?.contrasts?.green5782, alertColor: colors?.contrasts?.blue5782, warningColor: colors?.contrasts?.orange5782, + aiColor: colors.contrasts?.violet5790, + + aiBackgroundColor: colors.contrasts?.violet1212, paragraphMargin: `${spacing.medium} 0` } diff --git a/packages/ui-themes/src/sharedThemeTokens/colors/primitives.ts b/packages/ui-themes/src/sharedThemeTokens/colors/primitives.ts index 6476cd9a4b..b7e30d5311 100644 --- a/packages/ui-themes/src/sharedThemeTokens/colors/primitives.ts +++ b/packages/ui-themes/src/sharedThemeTokens/colors/primitives.ts @@ -64,7 +64,29 @@ export const primitives: Primitives = { red45: '#E62429', red57: '#C71F23', red70: '#AE1B1F', - red82: '#9B181C' + red82: '#9B181C', + + sea12: '#DAEEEF', + sea30: '#37A1AA', + sea35: '#1E95A0', + sea40: '#0A8C97', + sea45: '#00828E', + sea50: '#007B86', + sea57: '#00717B', + sea70: '#00626B', + sea90: '#005158', + sea110: '#004349', + + violet12: '#F1E6F5', + violet30: '#B57FCC', + violet35: '#AC6FC6', + violet40: '#A564C2', + violet45: '#9E58BD', + violet50: '#994FB9', + violet57: '#9242B4', + violet70: '#7F399E', + violet90: '#682F82', + violet110: '#56276B' } export const additionalPrimitives: AdditionalPrimitives = { diff --git a/packages/ui-themes/src/themes/canvas/colors.ts b/packages/ui-themes/src/themes/canvas/colors.ts index 713bb71cca..26da842d21 100644 --- a/packages/ui-themes/src/themes/canvas/colors.ts +++ b/packages/ui-themes/src/themes/canvas/colors.ts @@ -61,7 +61,13 @@ const contrasts: Contrasts = { red1212: primitives.red12, red4570: primitives.red45, - red5782: primitives.red57 + red5782: primitives.red57, + + violet1212: primitives.violet12, + violet4570: primitives.violet45, + violet5790: primitives.violet57, + sea4570: primitives.sea45, + sea5790: primitives.sea57 } const ui: UI = getUIColors(contrasts) diff --git a/packages/ui-themes/src/themes/canvasHighContrast/colors.ts b/packages/ui-themes/src/themes/canvasHighContrast/colors.ts index f27b18220a..a678f458e2 100644 --- a/packages/ui-themes/src/themes/canvasHighContrast/colors.ts +++ b/packages/ui-themes/src/themes/canvasHighContrast/colors.ts @@ -61,7 +61,13 @@ const contrasts: Contrasts = { red1212: primitives.red12, red4570: primitives.red70, - red5782: primitives.red82 + red5782: primitives.red82, + + violet1212: primitives.violet12, + violet4570: primitives.violet70, + violet5790: primitives.violet90, + sea4570: primitives.sea70, + sea5790: primitives.sea90 } const ui: UI = getUIColors(contrasts)