From b27e4495382fd1b2264b99dd7d8dd82aa0f852f0 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Thu, 15 Aug 2024 14:22:06 -0700 Subject: [PATCH] S2 AvatarGroup (#6781) * Add Spectrum 2 docs to storybook * add avatar group * lint * updates * feedback updates * more feedback changes * properly forward ref and add aria label interface * Move stroke to Avatar and switch to pixel-based sizing * Fix TS * Add support for aria labeling --------- Co-authored-by: Jeff Luyau Co-authored-by: Robert Snow Co-authored-by: Yihui Liao <44729383+yihuiliao@users.noreply.github.com> Co-authored-by: Kyle Taborski --- packages/@react-spectrum/s2/src/Avatar.tsx | 49 ++++++--- .../@react-spectrum/s2/src/AvatarGroup.tsx | 100 ++++++++++++++++++ packages/@react-spectrum/s2/src/Modal.tsx | 6 +- packages/@react-spectrum/s2/src/Popover.tsx | 6 +- packages/@react-spectrum/s2/src/Provider.tsx | 16 +-- packages/@react-spectrum/s2/src/TagGroup.tsx | 18 +++- packages/@react-spectrum/s2/src/index.ts | 2 + packages/@react-spectrum/s2/src/page.macro.ts | 7 +- .../@react-spectrum/s2/src/style-utils.ts | 16 +-- .../s2/stories/Avatar.stories.tsx | 26 ++--- .../s2/stories/AvatarGroup.stories.tsx | 60 +++++++++++ .../__snapshots__/avatar.test.ts.snap | 16 +-- .../src/s1-to-s2/__tests__/avatar.test.ts | 2 +- .../src/s1-to-s2/src/codemods/changes.ts | 12 +++ .../src/s1-to-s2/src/codemods/styleProps.ts | 33 +----- .../src/s1-to-s2/src/codemods/transforms.ts | 35 +++++- 16 files changed, 305 insertions(+), 99 deletions(-) create mode 100644 packages/@react-spectrum/s2/src/AvatarGroup.tsx create mode 100644 packages/@react-spectrum/s2/stories/AvatarGroup.stories.tsx diff --git a/packages/@react-spectrum/s2/src/Avatar.tsx b/packages/@react-spectrum/s2/src/Avatar.tsx index 369b9ef0e5f..7e22b43aacd 100644 --- a/packages/@react-spectrum/s2/src/Avatar.tsx +++ b/packages/@react-spectrum/s2/src/Avatar.tsx @@ -14,34 +14,45 @@ import {ContextValue} from 'react-aria-components'; import {createContext, forwardRef} from 'react'; import {DOMProps, DOMRef, DOMRefValue} from '@react-types/shared'; import {filterDOMProps} from '@react-aria/utils'; -import {getAllowedOverrides, StyleProps, StylesPropWithHeight, UnsafeStyles} from './style-utils' with {type: 'macro'}; +import {getAllowedOverrides, StylesPropWithoutWidth, UnsafeStyles} from './style-utils' with {type: 'macro'}; import {style} from '../style/spectrum-theme' with { type: 'macro' }; import {useDOMRef} from '@react-spectrum/utils'; import {useSpectrumContextProps} from './useSpectrumContextProps'; -export interface AvatarProps extends StyleProps, DOMProps { - /** Text description of the avatar. */ - alt?: string, - /** The image URL for the avatar. */ - src?: string -} - -export interface AvatarContextProps extends UnsafeStyles, DOMProps { +export interface AvatarProps extends UnsafeStyles, DOMProps { /** Text description of the avatar. */ alt?: string, /** The image URL for the avatar. */ src?: string, /** Spectrum-defined styles, returned by the `style()` macro. */ - styles?: StylesPropWithHeight + styles?: StylesPropWithoutWidth, + /** + * The size of the avatar. + * @default 24 + */ + size?: 16 | 20 | 24 | 28 | 32 | 36 | 40 | 44 | 48 | 56 | 64 | 80 | 96 | 112 | (number & {}), + /** Whether the avatar is over a color background. */ + isOverBackground?: boolean } const imageStyles = style({ borderRadius: 'full', size: 20, - disableTapHighlight: true -}, getAllowedOverrides({height: true})); + flexShrink: 0, + flexGrow: 0, + disableTapHighlight: true, + outlineStyle: { + default: 'none', + isOverBackground: 'solid' + }, + outlineColor: '--s2-container-bg', + outlineWidth: { + default: 1, + isLarge: 2 + } +}, getAllowedOverrides({width: false})); -export const AvatarContext = createContext>>(null); +export const AvatarContext = createContext>>(null); function Avatar(props: AvatarProps, ref: DOMRef) { [props, ref] = useSpectrumContextProps(props, ref, AvatarContext); @@ -51,17 +62,25 @@ function Avatar(props: AvatarProps, ref: DOMRef) { src, UNSAFE_style, UNSAFE_className = '', + size, + isOverBackground, ...otherProps } = props; const domProps = filterDOMProps(otherProps); + let remSize = size / 16 + 'rem'; + let isLarge = size >= 64; return ( {alt} ); } diff --git a/packages/@react-spectrum/s2/src/AvatarGroup.tsx b/packages/@react-spectrum/s2/src/AvatarGroup.tsx new file mode 100644 index 00000000000..ec54cbb949c --- /dev/null +++ b/packages/@react-spectrum/s2/src/AvatarGroup.tsx @@ -0,0 +1,100 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {AriaLabelingProps, DOMProps, DOMRef, DOMRefValue} from '@react-types/shared'; +import {AvatarContext} from './Avatar'; +import {ContextValue} from 'react-aria-components'; +import {createContext, CSSProperties, forwardRef, ReactNode} from 'react'; +import {filterDOMProps} from '@react-aria/utils'; +import {getAllowedOverrides, StylesPropWithoutWidth, UnsafeStyles} from './style-utils' with {type: 'macro'}; +import {style} from '../style/spectrum-theme' with {type: 'macro'}; +import {useDOMRef} from '@react-spectrum/utils'; +import {useLabel} from 'react-aria'; +import {useSpectrumContextProps} from './useSpectrumContextProps'; + +export interface AvatarGroupProps extends UnsafeStyles, DOMProps, AriaLabelingProps { + /** Avatar children of the avatar group. */ + children: ReactNode, + /** The label for the avatar group. */ + label?: string, + /** + * The size of the avatar group. + * @default 24 + */ + size?: 16 | 20 | 24 | 28 | 32 | 36 | 40, + /** Spectrum-defined styles, returned by the `style()` macro. */ + styles?: StylesPropWithoutWidth +} + +export const AvatarGroupContext = createContext>>(null); + +const avatar = style({ + marginStart: { + default: '[calc(var(--size) / -4)]', + ':first-child': 0 + } +}); + +const text = style({ + marginStart: 8, + truncate: true, + font: { + size: { + 16: 'ui-xs', + 20: 'ui-sm', + 24: 'ui', + 28: 'ui-lg', + 32: 'ui-xl', + 36: 'ui-2xl', + 40: 'ui-3xl' + } + } +}); + +const container = style({ + display: 'flex', + alignItems: 'center' +}, getAllowedOverrides({width: false})); + +function AvatarGroup(props: AvatarGroupProps, ref: DOMRef) { + [props, ref] = useSpectrumContextProps(props, ref, AvatarGroupContext); + let domRef = useDOMRef(ref); + let {children, label, size = 24, styles, UNSAFE_style, UNSAFE_className, ...otherProps} = props; + let {labelProps, fieldProps} = useLabel({ + ...props, + labelElementType: 'span' + }); + + return ( + +
+ {children} + {label && {label}} +
+
+ ); +} + +/** + * An avatar group is a grouping of avatars that are related to each other. + */ +let _AvatarGroup = forwardRef(AvatarGroup); +export {_AvatarGroup as AvatarGroup}; diff --git a/packages/@react-spectrum/s2/src/Modal.tsx b/packages/@react-spectrum/s2/src/Modal.tsx index ae42b07de46..e041568ca82 100644 --- a/packages/@react-spectrum/s2/src/Modal.tsx +++ b/packages/@react-spectrum/s2/src/Modal.tsx @@ -133,7 +133,11 @@ function Modal(props: ModalProps, ref: DOMRef) { L: '[90vh]' } }, - backgroundColor: 'layer-2', + '--s2-container-bg': { + type: 'backgroundColor', + value: 'layer-2' + }, + backgroundColor: '--s2-container-bg', animation: { isEntering: fadeAndSlide, isExiting: fade diff --git a/packages/@react-spectrum/s2/src/Popover.tsx b/packages/@react-spectrum/s2/src/Popover.tsx index b03e68df352..0894c801bf5 100644 --- a/packages/@react-spectrum/s2/src/Popover.tsx +++ b/packages/@react-spectrum/s2/src/Popover.tsx @@ -86,11 +86,11 @@ const slideLeftKeyframes = keyframes(` let popover = style({ ...colorScheme(), - '--popoverBackground': { + '--s2-container-bg': { type: 'backgroundColor', value: 'layer-2' }, - backgroundColor: '--popoverBackground', + backgroundColor: '--s2-container-bg', borderRadius: 'lg', filter: { isArrowShown: 'elevated' @@ -174,7 +174,7 @@ let popover = style({ let arrow = style({ display: 'block', - fill: '--popoverBackground', + fill: '--s2-container-bg', rotate: { default: 180, placement: { diff --git a/packages/@react-spectrum/s2/src/Provider.tsx b/packages/@react-spectrum/s2/src/Provider.tsx index 89a9f0ee723..4e2e31e7999 100644 --- a/packages/@react-spectrum/s2/src/Provider.tsx +++ b/packages/@react-spectrum/s2/src/Provider.tsx @@ -70,13 +70,17 @@ export function Provider(props: ProviderProps) { let providerStyles = style({ ...colorScheme(), - backgroundColor: { - background: { - base: 'base', - 'layer-1': 'layer-1', - 'layer-2': 'layer-2' + '--s2-container-bg': { + type: 'backgroundColor', + value: { + background: { + base: 'base', + 'layer-1': 'layer-1', + 'layer-2': 'layer-2' + } } - } + }, + backgroundColor: '--s2-container-bg' }); function ProviderInner(props: ProviderProps) { diff --git a/packages/@react-spectrum/s2/src/TagGroup.tsx b/packages/@react-spectrum/s2/src/TagGroup.tsx index 24bd7ecfd93..d19b8b6c107 100644 --- a/packages/@react-spectrum/s2/src/TagGroup.tsx +++ b/packages/@react-spectrum/s2/src/TagGroup.tsx @@ -270,6 +270,12 @@ const tagStyles = style({ } }); +const avatarSize = { + S: 16, + M: 20, + L: 24 +} as const; + export function Tag({children, ...props}: TagProps) { let textValue = typeof children === 'string' ? children : undefined; let {size = 'M', isEmphasized} = useSlottedContext(TagGroupContext)!; @@ -302,10 +308,18 @@ export function Tag({children, ...props}: TagProps) { styles: style({size: fontRelative(20), marginStart: '--iconMargin', flexShrink: 0}) }], [AvatarContext, { - styles: style({size: fontRelative(20), flexShrink: 0, order: 0}) + size: avatarSize[size], + styles: style({order: 0}) }], [ImageContext, { - className: style({size: fontRelative(20), flexShrink: 0, order: 0, aspectRatio: 'square', objectFit: 'contain'}) + className: style({ + size: fontRelative(20), + flexShrink: 0, + order: 0, + aspectRatio: 'square', + objectFit: 'contain', + borderRadius: 'sm' + }) }] ]}> {typeof children === 'string' ? {children} : children} diff --git a/packages/@react-spectrum/s2/src/index.ts b/packages/@react-spectrum/s2/src/index.ts index 6c6dce1308a..a28855518d6 100644 --- a/packages/@react-spectrum/s2/src/index.ts +++ b/packages/@react-spectrum/s2/src/index.ts @@ -14,6 +14,7 @@ export {ActionButton, ActionButtonContext} from './ActionButton'; export {ActionMenu, ActionMenuContext} from './ActionMenu'; export {AlertDialog} from './AlertDialog'; export {Avatar, AvatarContext} from './Avatar'; +export {AvatarGroup, AvatarGroupContext} from './AvatarGroup'; export {Badge, BadgeContext} from './Badge'; export {Breadcrumbs, Breadcrumb, BreadcrumbsContext} from './Breadcrumbs'; export {Button, LinkButton, ButtonContext, LinkButtonContext} from './Button'; @@ -64,6 +65,7 @@ export type {ActionButtonProps} from './ActionButton'; export type {ActionMenuProps} from './ActionMenu'; export type {AlertDialogProps} from './AlertDialog'; export type {AvatarProps} from './Avatar'; +export type {AvatarGroupProps} from './AvatarGroup'; export type {BreadcrumbsProps, BreadcrumbProps} from './Breadcrumbs'; export type {BadgeProps} from './Badge'; export type {ButtonProps, LinkButtonProps} from './Button'; diff --git a/packages/@react-spectrum/s2/src/page.macro.ts b/packages/@react-spectrum/s2/src/page.macro.ts index fd58d6b2c00..7f26dbb4685 100644 --- a/packages/@react-spectrum/s2/src/page.macro.ts +++ b/packages/@react-spectrum/s2/src/page.macro.ts @@ -27,7 +27,8 @@ export function generatePageStyles(this: MacroContext | void) { type: 'css', content: `html { color-scheme: light dark; - background: ${colorToken(tokens['background-base-color'])}; + --s2-container-bg: ${colorToken(tokens['background-base-color'])}; + background: var(--s2-container-bg); &[data-color-scheme=light] { color-scheme: light; @@ -38,11 +39,11 @@ export function generatePageStyles(this: MacroContext | void) { } &[data-background=layer-1] { - background: ${colorToken(tokens['background-layer-1-color'])}; + --s2-container-bg: ${colorToken(tokens['background-layer-1-color'])}; } &[data-background=layer-2] { - background: ${weirdColorToken(tokens['background-layer-2-color'])}; + --s2-container-bg: ${weirdColorToken(tokens['background-layer-2-color'])}; } }` }); diff --git a/packages/@react-spectrum/s2/src/style-utils.ts b/packages/@react-spectrum/s2/src/style-utils.ts index db584d69547..26ee2e8cf4c 100644 --- a/packages/@react-spectrum/s2/src/style-utils.ts +++ b/packages/@react-spectrum/s2/src/style-utils.ts @@ -147,9 +147,6 @@ const allowedOverrides = [ 'marginBottom', 'marginX', 'marginY', - 'width', - 'minWidth', - 'maxWidth', 'flex', 'flexGrow', 'flexShrink', @@ -175,6 +172,12 @@ const allowedOverrides = [ 'insetEnd' ] as const; +const widthProperties = [ + 'width', + 'minWidth', + 'maxWidth' +] as const; + const heightProperties = [ 'size', 'height', @@ -182,8 +185,9 @@ const heightProperties = [ 'maxHeight' ] as const; -export type StylesProp = StyleString<(typeof allowedOverrides)[number]>; +export type StylesProp = StyleString<(typeof allowedOverrides)[number] | (typeof widthProperties)[number]>; export type StylesPropWithHeight = StyleString<(typeof allowedOverrides)[number] | (typeof heightProperties)[number]>; +export type StylesPropWithoutWidth = StyleString<(typeof allowedOverrides)[number]>; export interface UnsafeStyles { /** Sets the CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the element. Only use as a **last resort**. Use the `style` macro via the `styles` prop instead. */ UNSAFE_className?: string, @@ -196,6 +200,6 @@ export interface StyleProps extends UnsafeStyles { styles?: StylesProp } -export function getAllowedOverrides({height = false} = {}) { - return (allowedOverrides as unknown as string[]).concat(height ? heightProperties : []); +export function getAllowedOverrides({width = true, height = false} = {}) { + return (allowedOverrides as unknown as string[]).concat(width ? widthProperties : []).concat(height ? heightProperties : []); } diff --git a/packages/@react-spectrum/s2/stories/Avatar.stories.tsx b/packages/@react-spectrum/s2/stories/Avatar.stories.tsx index ab0e0a43a68..e4accdc5f31 100644 --- a/packages/@react-spectrum/s2/stories/Avatar.stories.tsx +++ b/packages/@react-spectrum/s2/stories/Avatar.stories.tsx @@ -20,26 +20,18 @@ const meta: Meta = { parameters: { layout: 'centered' }, - tags: ['autodocs'] + tags: ['autodocs'], + decorators: (children, {args}) => ( + args.isOverBackground ? ( +
+ {children(args)} +
+ ) : children(args) + ) }; export default meta; -const SRC_URL_1 = - 'https://mir-s3-cdn-cf.behance.net/project_modules/disp/690bc6105945313.5f84bfc9de488.png'; -const SRC_URL_2 = 'https://i.imgur.com/xIe7Wlb.png'; - - export const Example = (args: any) => ( - <> - - - -); - -export const UserAppliedSize = (args: any) => ( - <> - - - + ); diff --git a/packages/@react-spectrum/s2/stories/AvatarGroup.stories.tsx b/packages/@react-spectrum/s2/stories/AvatarGroup.stories.tsx new file mode 100644 index 00000000000..76b6095e25f --- /dev/null +++ b/packages/@react-spectrum/s2/stories/AvatarGroup.stories.tsx @@ -0,0 +1,60 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {Avatar, AvatarGroup, Provider} from '../src'; +import type {Meta} from '@storybook/react'; +import {style} from '../style/spectrum-theme' with {type: 'macro'}; + +const meta: Meta = { + component: AvatarGroup, + argTypes: {}, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +const SRC_URL_1 = + 'https://mir-s3-cdn-cf.behance.net/project_modules/disp/690bc6105945313.5f84bfc9de488.png'; +const SRC_URL_2 = 'https://i.imgur.com/xIe7Wlb.png'; + + +export const Example = (args: any) => ( + + + + + + +); + +export const WithLabel = (args: any) => ( + + + + + + +); + +export const WithProviderBackground = (args: any) => ( + + + + + + + + +); diff --git a/packages/dev/codemods/src/s1-to-s2/__tests__/__snapshots__/avatar.test.ts.snap b/packages/dev/codemods/src/s1-to-s2/__tests__/__snapshots__/avatar.test.ts.snap index 03230458c44..6130c179c59 100644 --- a/packages/dev/codemods/src/s1-to-s2/__tests__/__snapshots__/avatar.test.ts.snap +++ b/packages/dev/codemods/src/s1-to-s2/__tests__/__snapshots__/avatar.test.ts.snap @@ -1,8 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Replaces size prop with macro size value 1`] = ` +exports[`Updates size prop to pixel value 1`] = ` "import { Avatar } from "@react-spectrum/s2"; -import { style } from "@react-spectrum/s2/style" with { type: "macro" }; let size = 75; let props = {size: 100}; @@ -10,25 +9,18 @@ let props = {size: 100}; + size={16} /> + size={40} /> + size={50} /> { defineSnapshotTest(transform, {}, input, name); }; -test('Replaces size prop with macro size value', ` +test('Updates size prop to pixel value', ` import {Avatar} from '@adobe/react-spectrum'; let size = 75; let props = {size: 100}; diff --git a/packages/dev/codemods/src/s1-to-s2/src/codemods/changes.ts b/packages/dev/codemods/src/s1-to-s2/src/codemods/changes.ts index b2429925583..b10ee2313fd 100644 --- a/packages/dev/codemods/src/s1-to-s2/src/codemods/changes.ts +++ b/packages/dev/codemods/src/s1-to-s2/src/codemods/changes.ts @@ -68,6 +68,10 @@ type FunctionInfo = | { name: 'commentIfParentCollectionNotDetected', args: {} + } + | { + name: 'updateAvatarSize', + args: {} }; type Change = { @@ -94,6 +98,14 @@ export const changes: ChangesJSON = { name: 'commentOutProp', args: {propToComment: 'isDisabled'} } + }, + { + description: 'Update size prop', + reason: 'Updated naming convention', + function: { + name: 'updateAvatarSize', + args: {} + } } ] }, diff --git a/packages/dev/codemods/src/s1-to-s2/src/codemods/styleProps.ts b/packages/dev/codemods/src/s1-to-s2/src/codemods/styleProps.ts index e083f0867cf..5d8cb29e0f4 100644 --- a/packages/dev/codemods/src/s1-to-s2/src/codemods/styleProps.ts +++ b/packages/dev/codemods/src/s1-to-s2/src/codemods/styleProps.ts @@ -397,38 +397,7 @@ function getStylePropValue(prop: string, value: t.ObjectProperty['value'], eleme break; case 'size': // Try to automatically convert size prop to a macro value for components that supported size. - if (element === 'Avatar') { - const AVATAR_SIZING_RE = /^avatar-size-\d+$/; - if (value.type === 'StringLiteral' && AVATAR_SIZING_RE.test(value.value)) { - const avatarDimensions = { - 'avatar-size-50': 16, - 'avatar-size-75': 18, - 'avatar-size-100': 20, - 'avatar-size-200': 22, - 'avatar-size-300': 26, - 'avatar-size-400': 28, - 'avatar-size-500': 32, - 'avatar-size-600': 36, - 'avatar-size-700': 40 - }; - let val = avatarDimensions[value.value as keyof typeof avatarDimensions]; - if (val != null) { - return { - macroValues: [{key: 'size', value: val}] - }; - } - return null; - } else if (value.type === 'NumericLiteral' || value.type === 'StringLiteral') { - let val = convertDimension(value.value); - if (val != null) { - return { - macroValues: [{key: 'size', value: val}] - }; - } - return null; - } - return null; - } else if (element === 'ColorArea' || element === 'ColorWheel') { + if (element === 'ColorArea' || element === 'ColorWheel') { if (value.type === 'StringLiteral' || value.type === 'NumericLiteral') { let val = convertDimension(value.value); if (val != null) { diff --git a/packages/dev/codemods/src/s1-to-s2/src/codemods/transforms.ts b/packages/dev/codemods/src/s1-to-s2/src/codemods/transforms.ts index f955ff2a976..cc3545fe1ec 100644 --- a/packages/dev/codemods/src/s1-to-s2/src/codemods/transforms.ts +++ b/packages/dev/codemods/src/s1-to-s2/src/codemods/transforms.ts @@ -823,6 +823,38 @@ function removeComponentIfWithinParent( } } +function updateAvatarSize( + path: NodePath +) { + if ( + t.isJSXElement(path.node) && + t.isJSXIdentifier(path.node.openingElement.name) && + getName(path, path.node.openingElement.name) === 'Avatar' + ) { + let sizeAttrPath = path.get('openingElement').get('attributes').find((attr) => t.isJSXAttribute(attr.node) && attr.node.name.name === 'size') as NodePath; + if (sizeAttrPath) { + let value = sizeAttrPath.node.value; + if (value?.type === 'StringLiteral') { + const avatarDimensions = { + 'avatar-size-50': 16, + 'avatar-size-75': 18, + 'avatar-size-100': 20, + 'avatar-size-200': 22, + 'avatar-size-300': 26, + 'avatar-size-400': 28, + 'avatar-size-500': 32, + 'avatar-size-600': 36, + 'avatar-size-700': 40 + }; + let val = avatarDimensions[value.value as keyof typeof avatarDimensions]; + if (val != null) { + sizeAttrPath.node.value = t.jsxExpressionContainer(t.numericLiteral(val)); + } + } + } + } +} + export const functionMap = { updatePropNameAndValue, updatePropValueAndAddNewProp, @@ -840,5 +872,6 @@ export const functionMap = { updateToNewComponent, convertDimensionValueToPx, updatePlacementToSingleValue, - removeComponentIfWithinParent + removeComponentIfWithinParent, + updateAvatarSize };