Skip to content
This repository was archived by the owner on Mar 4, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

### Fixes
- Fix narration for `Menu` @miroslavstastny ([#1105](https://github.com/stardust-ui/react/pull/1105))
- Fix `timestamp` to be shown if the `reactionGroup` prop is applied on the `ChatMessage` component in Teams theme @mnajdova ([#1100](https://github.com/stardust-ui/react/pull/1100))

### Features
- Add `attached` prop on the `ChatMessage` component, which is automatically set by the `ChatItem` component @mnajdova ([#1100](https://github.com/stardust-ui/react/pull/1100))

<!--------------------------------[ v0.25.0 ]------------------------------- -->
## [v0.25.0](https://github.com/stardust-ui/react/tree/v0.25.0) (2019-03-26)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ const items = [
content: (
<Chat.Message
reactionGroup={[{ icon: 'thumbs up', content: '8' }]}
reactionGroupPosition={'end'}
content="I'm back!"
author="John Doe"
timestamp="Yesterday, 10:15 PM"
Expand Down
46 changes: 42 additions & 4 deletions packages/react/src/components/Chat/ChatItem.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react'
import * as PropTypes from 'prop-types'
import * as _ from 'lodash'

import { ReactProps, ShorthandValue } from '../../types'
import {
Expand All @@ -12,11 +13,13 @@ import {
commonPropTypes,
customPropTypes,
rtlTextContainer,
getElementProp,
} from '../../lib'
import Box from '../Box/Box'
import { Accessibility } from '../../lib/accessibility/types'
import { defaultBehavior } from '../../lib/accessibility'
import { ComponentSlotStylesPrepared } from '../../themes/types'
import ChatMessage from './ChatMessage'

export interface ChatItemSlotClassNames {
message: string
Expand Down Expand Up @@ -89,23 +92,58 @@ class ChatItem extends UIComponent<ReactProps<ChatItemProps>, any> {
}

private renderChatItem(styles: ComponentSlotStylesPrepared) {
const { message, gutter, contentPosition } = this.props
const { gutter, contentPosition } = this.props
const gutterElement =
gutter &&
Box.create(gutter, {
defaultProps: { className: ChatItem.slotClassNames.gutter, styles: styles.gutter },
})

const messageElement = this.setAttachedPropValueForChatMessage(styles)

return (
<>
{contentPosition === 'start' && gutterElement}
{Box.create(message, {
defaultProps: { className: ChatItem.slotClassNames.message, styles: styles.message },
})}
{messageElement}
{contentPosition === 'end' && gutterElement}
</>
)
}

setAttachedPropValueForChatMessage = styles => {
const { message, attached } = this.props
const messageElement = Box.create(message, {
defaultProps: {
className: ChatItem.slotClassNames.message,
styles: styles.message,
},
})

// the element is ChatMessage
if (ChatMessage.isTypeOfElement(messageElement)) {
return this.cloneElementWithCustomProps(messageElement, { attached })
}

// the children is ChatMessage
if (ChatMessage.isTypeOfElement(getElementProp(messageElement, 'children'))) {
return this.cloneElementWithCustomProps(messageElement, { attached }, 'children')
}

// the content is ChatMessage
if (ChatMessage.isTypeOfElement(getElementProp(messageElement, 'content'))) {
return this.cloneElementWithCustomProps(messageElement, { attached }, 'content')
}
Copy link
Member

@layershifter layershifter Mar 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please clarify this for me? Will we add attached prop only is element/children/content are ChatItem?
But, any HOC will break this: https://codesandbox.io/s/n4mm1ny55l 💣

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, by default we will propagate the attached in these three use cases (which we see as the most common) - the attached prop can always be applied on the ChatMessage if it is wrapped... I am open for refactoring it if we have some better ideas.. (once the template mechanism will be available to the user, we can safely omit these changes)

return messageElement
}

cloneElementWithCustomProps = (element, props, prop?) => {
if (!prop) {
return React.cloneElement(element, props)
}
return React.cloneElement(element, {
[prop]: React.cloneElement(getElementProp(element, prop), props),
})
}
}

ChatItem.create = createShorthandFactory({ Component: ChatItem, mappedProp: 'message' })
Expand Down
8 changes: 8 additions & 0 deletions packages/react/src/components/Chat/ChatMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ export interface ChatMessageProps
/** Menu with actions of the message. */
actionMenu?: ShorthandValue

/** Controls messages's relation to other chat messages. Is automatically set by the ChatItem. */
attached?: boolean | 'top' | 'bottom'

/** Author of the message. */
author?: ShorthandValue

Expand Down Expand Up @@ -103,9 +106,14 @@ class ChatMessage extends UIComponent<ReactProps<ChatMessageProps>, ChatMessageS

static displayName = 'ChatMessage'

static __isChatMessage = true

static isTypeOfElement = element => _.get(element, `type.__isChatMessage`)

static propTypes = {
...commonPropTypes.createCommon({ content: 'shorthand' }),
actionMenu: customPropTypes.itemShorthand,
attached: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['top', 'bottom'])]),
author: customPropTypes.itemShorthand,
badge: customPropTypes.itemShorthand,
badgePosition: PropTypes.oneOf(['start', 'end']),
Expand Down
7 changes: 7 additions & 0 deletions packages/react/src/lib/getElementProp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as _ from 'lodash'

const getElementProp = (element, propName) => {
return _.get(element, `props.${propName}`)
}

export default getElementProp
1 change: 1 addition & 0 deletions packages/react/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export { default as getElementType } from './getElementType'
export { default as getUnhandledProps } from './getUnhandledProps'
export { default as mergeThemes } from './mergeThemes'
export { default as renderComponent, RenderResultConfig } from './renderComponent'
export { default as getElementProp } from './getElementProp'

export { default as handleRef } from './handleRef'
export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,13 @@ import { ICSSInJSStyle, ComponentSlotStylesInput } from '../../../types'
import { ChatItemVariables } from './chatItemVariables'
import { ChatItemProps } from '../../../../components/Chat/ChatItem'
import { pxToRem } from '../../../../lib'
import { default as ChatMessage } from '../../../../components/Chat/ChatMessage'
import { screenReaderContainerStyles } from '../../../../lib/accessibility/Styles/accessibilityStyles'

const chatMessageClassNameSelector = `& .${ChatMessage.className}`
const chatMessageAuthorClassNameSelector = `& .${ChatMessage.slotClassNames.author}`
const chatMessageTimestampClassNameSelector = `& .${ChatMessage.slotClassNames.timestamp}`
const chatMessageContentClassNameSelector = `& .${ChatMessage.slotClassNames.content}`

const getPositionStyles = (props: ChatItemProps) => ({
float: props.contentPosition === 'end' ? 'right' : 'left',
})

const getChatMessageEvaluatedStyles = (p: ChatItemProps) => ({
...(!p.attached && { [chatMessageClassNameSelector]: getPositionStyles(p) }),
...(p.attached === true && {
[chatMessageClassNameSelector]: {
[p.contentPosition === 'end' ? 'borderTopRightRadius' : 'borderTopLeftRadius']: 0,
[p.contentPosition === 'end' ? 'borderBottomRightRadius' : 'borderBottomLeftRadius']: 0,
paddingTop: pxToRem(5),
paddingBottom: pxToRem(7),
...getPositionStyles(p),
[chatMessageContentClassNameSelector]: {
display: 'inline-block',
},
},
}),
...(p.attached === 'top' && {
[chatMessageClassNameSelector]: {
[p.contentPosition === 'end' ? 'borderBottomRightRadius' : 'borderBottomLeftRadius']: 0,
...getPositionStyles(p),
},
}),
...(p.attached === 'bottom' && {
[chatMessageClassNameSelector]: {
[p.contentPosition === 'end' ? 'borderTopRightRadius' : 'borderTopLeftRadius']: 0,
paddingTop: pxToRem(5),
paddingBottom: pxToRem(7),
...getPositionStyles(p),
[chatMessageContentClassNameSelector]: {
display: 'inline-block',
},
},
}),
})

const chatItemStyles: ComponentSlotStylesInput<ChatItemProps, ChatItemVariables> = {
root: ({ props: p, variables: v }): ICSSInJSStyle => ({
position: 'relative',
...((!p.attached || p.attached === 'top') && { paddingTop: pxToRem(16) }),
...((p.attached === 'bottom' || p.attached === true) && {
paddingTop: pxToRem(2),
[chatMessageAuthorClassNameSelector]: screenReaderContainerStyles,
[chatMessageTimestampClassNameSelector]: screenReaderContainerStyles,
}),
paddingBottom: 0,
}),
Expand All @@ -70,9 +24,9 @@ const chatItemStyles: ComponentSlotStylesInput<ChatItemProps, ChatItemVariables>

message: ({ props: p, variables: v }): ICSSInJSStyle => ({
position: 'relative',
float: p.contentPosition === 'end' ? 'right' : 'left',
marginLeft: v.messageMargin,
marginRight: v.messageMargin,
...getChatMessageEvaluatedStyles(p),
}),
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@ const chatMessageStyles: ComponentSlotStylesInput<
width: 'auto',
},
},
...(p.attached === true && {
[p.mine ? 'borderTopRightRadius' : 'borderTopLeftRadius']: 0,
[p.mine ? 'borderBottomRightRadius' : 'borderBottomLeftRadius']: 0,
paddingTop: pxToRem(5),
paddingBottom: pxToRem(7),
}),
...(p.attached === 'top' && {
[p.mine ? 'borderBottomRightRadius' : 'borderBottomLeftRadius']: 0,
}),
...(p.attached === 'bottom' && {
[p.mine ? 'borderTopRightRadius' : 'borderTopLeftRadius']: 0,
paddingTop: pxToRem(5),
paddingBottom: pxToRem(7),
}),
}),

actionMenu: ({ props: p, variables: v }): ICSSInJSStyle => ({
Expand All @@ -78,7 +92,7 @@ const chatMessageStyles: ComponentSlotStylesInput<
}),

author: ({ props: p, variables: v }): ICSSInJSStyle => ({
...(p.mine && screenReaderContainerStyles),
...((p.mine || p.attached === 'bottom' || p.attached === true) && screenReaderContainerStyles),
marginRight: v.authorMarginRight,
marginBottom: v.headerMarginBottom,
fontWeight: v.authorFontWeight,
Expand All @@ -89,6 +103,9 @@ const chatMessageStyles: ComponentSlotStylesInput<
...(p.mine && {
color: v.timestampColorMine,
}),
...((p.attached === 'bottom' || p.attached === true) &&
!p.reactionGroup &&
screenReaderContainerStyles),
}),

content: ({ props: p, variables: v }): ICSSInJSStyle => ({
Expand Down