diff --git a/packages/yoga/src/Avatar/native/Avatar.jsx b/packages/yoga/src/Avatar/native/Avatar.jsx index e2a186a76e..f0d0653fd9 100644 --- a/packages/yoga/src/Avatar/native/Avatar.jsx +++ b/packages/yoga/src/Avatar/native/Avatar.jsx @@ -50,6 +50,7 @@ const Avatar = forwardRef( borderRadius, width, height, + onLayout, ...props }, ref, @@ -63,6 +64,7 @@ const Avatar = forwardRef( justifyContent="center" width={width} height={height} + onLayout={onLayout} overflow="hidden" borderRadius={borderRadius} {...props} diff --git a/packages/yoga/src/Result/native/Result/TextWithBadge/__snapshots__/index.test.tsx.snap b/packages/yoga/src/Result/native/Result/TextWithBadge/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..266b506ce2 --- /dev/null +++ b/packages/yoga/src/Result/native/Result/TextWithBadge/__snapshots__/index.test.tsx.snap @@ -0,0 +1,370 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TextWithBadge should match snapshot 1`] = ` + + + Text with badge + + + + + + + +`; + +exports[`TextWithBadge should match snapshot with long title 1`] = ` + + + This is an example of a very long title that should be truncated + + + + + + + +`; + +exports[`TextWithBadge should match snapshot without badgeIcon 1`] = ` + + + Title without Badge + + + + + +`; diff --git a/packages/yoga/src/Result/native/Result/TextWithBadge/index.test.tsx b/packages/yoga/src/Result/native/Result/TextWithBadge/index.test.tsx new file mode 100644 index 0000000000..d968d35222 --- /dev/null +++ b/packages/yoga/src/Result/native/Result/TextWithBadge/index.test.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { render } from '@testing-library/react-native'; +import { WellhubIcon } from '@gympass/yoga-icons'; + +import { ThemeProvider } from '../../../../index'; +import TextWithBadge from '.'; + +describe('TextWithBadge', () => { + it('should match snapshot', () => { + const { toJSON } = render( + + + , + ); + + expect(toJSON()).toMatchSnapshot(); + }); + + it('should match snapshot with long title', () => { + const { toJSON } = render( + + + , + ); + + expect(toJSON()).toMatchSnapshot(); + }); + + it('should match snapshot without badgeIcon', () => { + const { toJSON } = render( + + + , + ); + + expect(toJSON()).toMatchSnapshot(); + }); +}); diff --git a/packages/yoga/src/Result/native/Result/TextWithBadge/index.tsx b/packages/yoga/src/Result/native/Result/TextWithBadge/index.tsx new file mode 100644 index 0000000000..285320efec --- /dev/null +++ b/packages/yoga/src/Result/native/Result/TextWithBadge/index.tsx @@ -0,0 +1,70 @@ +import React, { ReactNode, useCallback, useState } from 'react'; +import { useWindowDimensions } from 'react-native'; + +import Badge from '../../Badge'; + +import { StyledBoxContainer, StyledText } from './styles'; + +interface TextWithBadgeProps { + avatarWidth: number; + badgeIcon: ReactNode; + title: string; +} + +const SCREEN_PADDINGS = 20; +const CONTENT_MARGINS = 20; +const AVATAR_CONTENT_MARGINS = 16; +const BADGE_LIMIT = 20; + +const TextWithBadge = ({ + avatarWidth, + badgeIcon, + title, +}: TextWithBadgeProps) => { + const [textSize, setTextSize] = useState(0); + const { width: windowWidth } = useWindowDimensions(); + + const textMaxSize = + windowWidth - + (SCREEN_PADDINGS + CONTENT_MARGINS + AVATAR_CONTENT_MARGINS + avatarWidth); + const shouldTruncate = textSize >= textMaxSize - BADGE_LIMIT; + const containerWidth = shouldTruncate ? null : textSize; + const textWidth = shouldTruncate ? '100%' : null; + + const onTextLayout = useCallback( + ({ + nativeEvent: { + layout: { width }, + }, + }) => { + setTextSize(width); + }, + [], + ); + + return ( + + + {title} + + + + ); +}; + +export default TextWithBadge; diff --git a/packages/yoga/src/Result/native/Result/TextWithBadge/styles.ts b/packages/yoga/src/Result/native/Result/TextWithBadge/styles.ts new file mode 100644 index 0000000000..34b852e693 --- /dev/null +++ b/packages/yoga/src/Result/native/Result/TextWithBadge/styles.ts @@ -0,0 +1,42 @@ +import React, { ReactNode } from 'react'; +import styled, { css } from 'styled-components'; +import Text from '../../../../Text'; +import Box from '../../../../Box'; + +export const StyledBoxContainer = styled(Box)<{ + containerWidth?: number | null; + children: ReactNode; +}>` + ${({ containerWidth }) => + css` + flex-direction: row; + align-items: center; + justify-content: flex-end; + position: relative; + width: ${containerWidth}px; + `} +`; + +export const StyledText = styled(Text.Body1)<{ + onLayout: ({ nativeEvent: { layout } }) => void; + textWidth?: string | null; + numberOfLines: number; + bold: boolean; + children: React.ReactNode; +}>` + ${({ + textWidth, + theme: { + yoga: { + spacing: { medium }, + }, + }, + }) => + css` + position: absolute; + left: 0; + padding-right: ${medium}; + flex: 1; + width: ${textWidth}; + `} +`; diff --git a/packages/yoga/src/Result/native/Result/__snapshots__/index.test.jsx.snap b/packages/yoga/src/Result/native/Result/__snapshots__/index.test.jsx.snap index 7802f9cb15..ce7ef18058 100644 --- a/packages/yoga/src/Result/native/Result/__snapshots__/index.test.jsx.snap +++ b/packages/yoga/src/Result/native/Result/__snapshots__/index.test.jsx.snap @@ -24,6 +24,7 @@ exports[` should match snapshot 1`] = ` display="flex" height={48} justifyContent="center" + onLayout={[Function]} overflow="hidden" style={ [ @@ -533,16 +534,9 @@ exports[` should match snapshot 1`] = ` @@ -1056,6 +1050,7 @@ exports[` should match snapshot without attendence 1`] = ` display="flex" height={48} justifyContent="center" + onLayout={[Function]} overflow="hidden" style={ [ @@ -1184,16 +1179,9 @@ exports[` should match snapshot without attendence 1`] = ` } > @@ -1573,6 +1561,7 @@ exports[` should match snapshot without limitLabel prop 1`] = ` display="flex" height={48} justifyContent="center" + onLayout={[Function]} overflow="hidden" style={ [ @@ -2082,16 +2071,9 @@ exports[` should match snapshot without limitLabel prop 1`] = ` diff --git a/packages/yoga/src/Result/native/Result/index.jsx b/packages/yoga/src/Result/native/Result/index.jsx index 4cadaec5a9..2d06358ae1 100644 --- a/packages/yoga/src/Result/native/Result/index.jsx +++ b/packages/yoga/src/Result/native/Result/index.jsx @@ -1,13 +1,12 @@ -import React, { isValidElement } from 'react'; - +import React, { isValidElement, useCallback, useState } from 'react'; import { arrayOf, string, shape, func, bool, node } from 'prop-types'; import Text from '../../../Text'; import Box from '../../../Box'; import Attendances from '../Attendances'; -import Badge from '../Badge'; import { Content, StyledBox } from './styles'; +import TextWithBadge from './TextWithBadge'; /** * The Result component is used when you have a list to show. It is applied to @@ -23,48 +22,62 @@ const Result = ({ children, attendancesColor, badgeIcon, -}) => ( - - {Avatar && <>{isValidElement(Avatar) ? Avatar : }} - - {!!attendances?.length && ( - +}) => { + const [avatarWidth, setAvatarWidth] = useState(0); + + const onAvatarLayout = useCallback( + ({ + nativeEvent: { + layout: { width }, + }, + }) => { + setAvatarWidth(width); + }, + [], + ); + + return ( + + {Avatar && ( + <> + {isValidElement(Avatar) ? ( + React.cloneElement(Avatar, { onLayout: onAvatarLayout }) + ) : ( + + )} + )} - - - {title} - - {badgeIcon && ( - + {!!attendances?.length && ( + )} - - {subTitle && subTitle !== '' && ( - - {subTitle} - - )} - {children} - - -); + {badgeIcon ? ( + + ) : ( + + + {title} + + + )} + {subTitle && subTitle !== '' && ( + + {subTitle} + + )} + {children} + + + ); +}; Result.propTypes = { /** The component Avatar */