Skip to content
This repository has been archived by the owner on Mar 4, 2020. It is now read-only.

feat(ChatItem): add attached prop, renamed gutterPosition to contentPosition #767

Merged
merged 16 commits into from
Jan 28, 2019
Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Rename `toggleButton` prop to `toggleIndicator` and make it visible by default @layershifter ([#729](https://github.com/stardust-ui/react/pull/729))
- Remove `props` from variables resolution process @kuzhelov ([#770](https://github.com/stardust-ui/react/pull/770))
- Update Fela and is deps to latest, `10.1.3` is required @layershifter ([#768](https://github.com/stardust-ui/react/pull/768))
- Replaced `gutterPosition` with `contentPosition` in ChatItem (`contentPosition='end'` should be added on the ChatItems containing ChatMessage with `mine` prop for teams theme) @mnajdova ([#767](https://github.com/stardust-ui/react/pull/767))

### Features
- Add `loading` prop for `Dropdown` @layershifter ([#729](https://github.com/stardust-ui/react/pull/729))
- Export `close` icon in Teams theme @alinais ([#774](https://github.com/stardust-ui/react/pull/774))
- Add `attached` prop for ChatItem @mnajdova ([#767](https://github.com/stardust-ui/react/pull/767))

### Fixes
- Make `headerMedia` visible for screen readers in `ListItem` @layershifter ([#772](https://github.com/stardust-ui/react/pull/772))
Expand Down
69 changes: 55 additions & 14 deletions docs/src/examples/components/Chat/Types/ChatExample.shorthand.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,63 @@ const items = [
<Chat.Message content="Hello" author="John Doe" timestamp="Yesterday, 10:15 PM" mine />
),
},
contentPosition: 'end',
attached: 'top',
key: 'message-id-1',
},
{
gutter: { content: <Avatar {...janeAvatar} /> },
message: {
content: <Chat.Message content="Hi" author="Jane Doe" timestamp="Yesterday, 10:15 PM" />,
content: (
<Chat.Message content="I'm back!" author="John Doe" timestamp="Yesterday, 10:15 PM" mine />
),
},
contentPosition: 'end',
attached: true,
key: 'message-id-2',
},
{
message: { content: <Chat.Message content="What's up?" /> },
message: {
content: (
<Chat.Message
content="Thanks for waiting!"
author="John Doe"
timestamp="Yesterday, 10:15 PM"
mine
/>
),
},
contentPosition: 'end',
attached: 'bottom',
key: 'message-id-3',
},
{
gutter: { content: <Avatar {...janeAvatar} /> },
message: {
content: <Chat.Message content="Hi" author="Jane Doe" timestamp="Yesterday, 10:15 PM" />,
},
attached: 'top',
key: 'message-id-4',
},
{
gutter: { content: <Avatar {...janeAvatar} /> },
message: {
content: (
<Chat.Message content="No problem!" author="Jane Doe" timestamp="Yesterday, 10:15 PM" />
),
},
attached: true,
key: 'message-id-5',
},
{
gutter: { content: <Avatar {...janeAvatar} /> },
message: {
content: (
<Chat.Message content="What's up?" author="Jane Doe" timestamp="Yesterday, 10:15 PM" />
),
},
attached: 'bottom',
key: 'message-id-6',
},
{
message: {
content: (
Expand All @@ -37,37 +81,34 @@ const items = [
/>
),
},
key: 'message-id-4',
contentPosition: 'end',
key: 'message-id-7',
},
{
gutter: { content: <Avatar {...janeAvatar} /> },
message: {
content: (
<Chat.Message
content="Sure! Let's try the new place downtown"
content="Sure! Let's try the new place downtown."
author="Jane Doe"
timestamp="Yesterday, 10:15 PM"
/>
),
},
key: 'message-id-5',
key: 'message-id-8',
},
{
children: <Divider content="Today" color="primary" important />,
key: 'message-id-6',
key: 'message-id-9',
},
{
message: {
content: (
<Chat.Message
content="Let's have a call"
author="John Doe"
timestamp="Today, 11:15 PM"
mine
/>
<Chat.Message content="Ok, let's go." author="John Doe" timestamp="Today, 11:15 PM" mine />
),
},
key: 'message-id-7',
contentPosition: 'end',
key: 'message-id-10',
},
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ const [janeAvatar, johnAvatar] = [

const items = [
{
gutterPosition: 'start',
contentPosition: 'start',
gutter: { content: <Avatar {...johnAvatar} /> },
message: {
content: <Chat.Message content="Hello" author="John Doe" timestamp="Yesterday, 10:15 PM" />,
},
key: 'message-id-1',
},
{
gutterPosition: 'end',
contentPosition: 'end',
gutter: { content: <Avatar {...janeAvatar} /> },
message: {
content: <Chat.Message content="Hi" author="Jane Doe" timestamp="Yesterday, 10:15 PM" mine />,
Expand All @@ -28,6 +28,6 @@ const items = [
},
]

const ChatExampleGutterPosition = () => <Chat items={items} />
const ChatExampleContentPosition = () => <Chat items={items} />

export default ChatExampleGutterPosition
export default ChatExampleContentPosition
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const ChatMessageExampleStyled = () => (
/>
),
},
contentPosition: 'end',
key: 'message-id-1',
},
{
Expand Down
6 changes: 3 additions & 3 deletions docs/src/examples/components/Chat/Types/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ const Types = () => (
examplePath="components/Chat/Types/ChatExample"
/>
<ComponentExample
title="Gutter"
description="A Chat can have a gutter positioned at the start or at the end of a message."
examplePath="components/Chat/Types/ChatExampleGutterPosition"
title="Content position"
description="A ChatItem can position it's content at the start or at the end of the container."
examplePath="components/Chat/Types/ChatExampleContentPosition"
/>
<ComponentExample
title="Styled Chat Item"
Expand Down
2 changes: 1 addition & 1 deletion docs/src/prototypes/chatPane/chatPaneContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class ChatPaneContainer extends React.PureComponent<ChatPaneContainerProps> {
return (
<Chat.Item
key={`chat-item-${index}`}
gutterPosition={mine ? 'end' : 'start'}
contentPosition={mine ? 'end' : 'start'}
gutter={gutter && { content: <Avatar {...gutter} /> }}
message={{
content: (
Expand Down
19 changes: 12 additions & 7 deletions src/components/Chat/ChatItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ import Box from '../Box/Box'
import { ComponentSlotStylesPrepared } from '../../themes/types'

export interface ChatItemProps extends UIComponentProps, ChildrenComponentProps {
/** Attach ChatItem to other content. */
Copy link
Contributor

Choose a reason for hiding this comment

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

would suggest to omit component class names in the description - as it should just convey general purpose of the prop. Something like: Controls item's relation to other chat items

attached?: boolean | 'top' | 'bottom'

/** Chat items can have a gutter. */
gutter?: ShorthandValue

/** Indicates whether the gutter is positioned at the start or the end. */
gutterPosition?: 'start' | 'end'
/** Indicates whether the content is positioned at the start or the end. */
contentPosition?: 'start' | 'end'

/** Chat items can have a message. */
message?: ShorthandValue
Expand All @@ -37,14 +40,16 @@ class ChatItem extends UIComponent<ReactProps<ChatItemProps>, any> {

static propTypes = {
...commonPropTypes.createCommon({ content: false }),
attached: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['top', 'bottom'])]),
gutter: customPropTypes.itemShorthand,
gutterPosition: PropTypes.oneOf(['start', 'end']),
contentPosition: PropTypes.oneOf(['start', 'end']),
message: customPropTypes.itemShorthand,
}

static defaultProps = {
as: 'li',
gutterPosition: 'start',
contentPosition: 'start',
attached: false,
}

renderComponent({
Expand All @@ -67,14 +72,14 @@ class ChatItem extends UIComponent<ReactProps<ChatItemProps>, any> {
}

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

return (
<>
{gutterPosition === 'start' && gutterElement}
{contentPosition === 'start' && gutterElement}
{Box.create(message, { defaultProps: { styles: styles.message } })}
{gutterPosition === 'end' && gutterElement}
{contentPosition === 'end' && gutterElement}
</>
)
}
Expand Down
26 changes: 24 additions & 2 deletions src/components/Chat/ChatMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ import { Accessibility, AccessibilityActionHandlers } from '../../lib/accessibil
import Text from '../Text/Text'
import Box from '../Box/Box'

export interface ChatMessageSlotClassNames {
author: string
timestamp: string
}

export interface ChatMessageProps
extends UIComponentProps,
ChildrenComponentProps,
Expand Down Expand Up @@ -62,6 +67,8 @@ class ChatMessage extends UIComponent<ReactProps<ChatMessageProps>, ChatMessageS

static create: Function

static slotClassNames: ChatMessageSlotClassNames

static displayName = 'ChatMessage'

static propTypes = {
Expand Down Expand Up @@ -119,9 +126,20 @@ class ChatMessage extends UIComponent<ReactProps<ChatMessageProps>, ChatMessageS
children
) : (
<>
{Text.create(author, { defaultProps: { size: 'small', styles: styles.author } })}
{Text.create(author, {
defaultProps: {
size: 'small',
styles: styles.author,
className: ChatMessage.slotClassNames.author,
},
})}
{Text.create(timestamp, {
defaultProps: { size: 'small', styles: styles.timestamp, timestamp: true },
defaultProps: {
size: 'small',
styles: styles.timestamp,
timestamp: true,
className: ChatMessage.slotClassNames.timestamp,
},
})}
{Box.create(content, { defaultProps: { styles: styles.content } })}
</>
Expand All @@ -132,5 +150,9 @@ class ChatMessage extends UIComponent<ReactProps<ChatMessageProps>, ChatMessageS
}

ChatMessage.create = createShorthandFactory(ChatMessage, 'content')
ChatMessage.slotClassNames = {
author: `${ChatMessage.className}__author`,
timestamp: `${ChatMessage.className}__timestamp`,
}

export default ChatMessage
6 changes: 5 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ export { default as ButtonGroup, ButtonGroupProps } from './components/Button/Bu

export { default as Chat, ChatProps } from './components/Chat/Chat'
export { default as ChatItem, ChatItemProps } from './components/Chat/ChatItem'
export { default as ChatMessage, ChatMessageProps } from './components/Chat/ChatMessage'
export {
default as ChatMessage,
ChatMessageProps,
ChatMessageSlotClassNames,
} from './components/Chat/ChatMessage'

export {
default as Divider,
Expand Down
53 changes: 50 additions & 3 deletions src/themes/teams/components/Chat/chatItemStyles.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,71 @@
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 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),
},
}),
...(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),
},
}),
})

const chatItemStyles: ComponentSlotStylesInput<ChatItemProps, ChatItemVariables> = {
root: ({ props: p, variables: v }): ICSSInJSStyle => ({
position: 'relative',
marginTop: v.margin,
marginBottom: v.margin,
...((!p.attached || p.attached === 'top') && { marginTop: pxToRem(16) }),
...((p.attached === 'bottom' || p.attached === true) && {
marginTop: pxToRem(2),
[chatMessageAuthorClassNameSelector]: screenReaderContainerStyles,
[chatMessageTimestampClassNameSelector]: screenReaderContainerStyles,
}),
marginBottom: 0,
}),

gutter: ({ props: p, variables: v }): ICSSInJSStyle => ({
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
position: 'absolute',
marginTop: v.gutterMargin,
[p.gutterPosition === 'end' ? 'right' : 'left']: 0,
[p.contentPosition === 'end' ? 'right' : 'left']: 0,
...((p.attached === 'bottom' || p.attached === true) && {
display: 'none',
}),
}),

message: ({ props: p, variables: v }): ICSSInJSStyle => ({
position: 'relative',
marginLeft: v.messageMargin,
marginRight: v.messageMargin,
...getChatMessageEvaluatedStyles(p),
}),
}

Expand Down
Loading