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
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,27 @@ const items = [
message: {
content: <Chat.Message content="Hi" author="Jane Doe" timestamp="Yesterday, 10:15 PM" />,
},
grouped: 'start',
key: 'message-id-2',
},
{
message: { content: <Chat.Message content="What's up?" /> },
gutter: { content: <Avatar {...janeAvatar} /> },
message: {
content: (
<Chat.Message content="What's up?" author="Jane Doe" timestamp="Yesterday, 10:15 PM" />
),
},
grouped: 'middle',
key: 'message-id-3',
},
{
gutter: { content: <Avatar {...janeAvatar} /> },
message: {
content: (
<Chat.Message content="What's new?" author="Jane Doe" timestamp="Yesterday, 10:15 PM" />
),
},
grouped: 'end',
key: 'message-id-3',
},
{
Expand Down
4 changes: 4 additions & 0 deletions src/components/Chat/ChatItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import Box from '../Box/Box'
import { ComponentSlotStylesPrepared } from '../../themes/types'

export interface ChatItemProps extends UIComponentProps, ChildrenComponentProps {
/** Indicates whether the ChatItem is the part of a batch. */
grouped?: 'start' | 'middle' | 'end'
Copy link
Member

Choose a reason for hiding this comment

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

I remember that we discussed this before, but can you please point me to results?
Why we decided to use grouped instead of SUI's attached={true|'top'|'bottom'}?

https://react.semantic-ui.com/elements/segment/#variations-attached

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My bad for not linking the issue in the PR.. Here is the issue with the discussion around this: #588 We were considering using grouped, consecutive prop or additional component for representing the groups of messages. We decided there to use the grouped prop (which in the issue is only as an boolean, but it turned out that we must have some different words for describing the first, the last message and the middle messages in a batch. The options: start, end and middle were chosen because the start and end are the options we are using across all other components. Please share some additional proposals if you have any :)

Copy link
Member

Choose a reason for hiding this comment

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

  • It's true that we use start and end but that applies for horizontal direction.
  • true seems better that middle (especially if you consider the default value to be false)
  • In [RFC] Chat.Item grouped prop for describing groups of chat items #588, we have not discussed attached. As this term already exists in SUI and seems to be valid among several components, what's the reason for not using that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will add the attached prop

Copy link
Contributor

@kuzhelov kuzhelov Jan 28, 2019

Choose a reason for hiding this comment

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

speaking of the design language choices, there are apps that are using grouped term to express this concept (Telegram as an example).

Thing that I don't like with attached is the following combination of prop name and top value:

<ChatItem attached='start' />

This is really hard to reason about what does this prop mean for the element, especially when it will occur in the following context:

...
<ChatItem attached />
<ChatItem attached='end' />
<ChatItem attached='start' /> // ???
<ChatItem />
...

Copy link
Contributor

@kuzhelov kuzhelov Jan 28, 2019

Choose a reason for hiding this comment

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

to me it seems that original version with grouped is something that reads better:

...
<ChatItem grouped='start' />
<ChatItem grouped />
<ChatItem grouped />
<ChatItem grouped='end' />
<ChatItem />
...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's go with attached: boolean | 'top' | 'bottom' for now, it satisfies the need for three different attached ChatItems which is sufficient for now, and in the future once we will add this prop in other component, it will be much easier for the users to learn to use it... We may consider changing this in the future if we receive complains for it. Can we agree with this?

Copy link
Contributor

Choose a reason for hiding this comment

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

yes, sure


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

Expand All @@ -37,6 +40,7 @@ class ChatItem extends UIComponent<ReactProps<ChatItemProps>, any> {

static propTypes = {
...commonPropTypes.createCommon({ content: false }),
grouped: PropTypes.oneOf(['start', 'middle', 'end']),
gutter: customPropTypes.itemShorthand,
gutterPosition: PropTypes.oneOf(['start', 'end']),
message: customPropTypes.itemShorthand,
Expand Down
15 changes: 13 additions & 2 deletions src/components/Chat/ChatMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,20 @@ class ChatMessage extends UIComponent<ReactProps<ChatMessageProps>, ChatMessageS
) : (
<>
{!mine &&
Text.create(author, { defaultProps: { size: 'small', styles: styles.author } })}
Text.create(author, {
defaultProps: {
size: 'small',
styles: styles.author,
className: `${ChatMessage.className}__author`,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was needed for targeting this className in the chat item for displaying or not the author of the message. If we add this prop on the ChatMessage as well, we won't need this.

},
})}
{Text.create(timestamp, {
defaultProps: { size: 'small', styles: styles.timestamp, timestamp: true },
defaultProps: {
size: 'small',
styles: styles.timestamp,
timestamp: true,
className: `${ChatMessage.className}__timestamp`,
},
})}
{Box.create(content, { defaultProps: { styles: styles.content } })}
</>
Expand Down
81 changes: 66 additions & 15 deletions src/themes/teams/components/Chat/chatItemStyles.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,76 @@
import { ICSSInJSStyle, ComponentSlotStylesInput } from '../../../types'
import { ChatItemVariables } from './chatItemVariables'
import { ChatItemProps } from '../../../../components/Chat/ChatItem'
import { pxToRem } from '../../../../lib'
import ChatMessage from '../../../../components/Chat/ChatMessage'

const chatMessageClassname = `& .${ChatMessage.className}`
Copy link
Collaborator

Choose a reason for hiding this comment

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

can we add these values to a constants file somewhere or export them from ChatMessage.tsx so there's only one place we change them?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Last question, how do we want to export the classNames for the slots? See the author and timestamp classNames in the ChatMessage component.

what about exporting a constants object from ChatMessage.tsx?

export const chatMessageMetadata = {
  authorClassName: `${ChatMessage.className}__author`,
  timestampClassname: `${ChatMessage.className}__timestamp`,
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like this idea of adding metadata objects. Not sure whether we can add this to the UIComponent and use static field for it, as probably every components will have different structure for it, but exporting a constant for now may be enough...

const chatMessageAuthorClassname = `& .${ChatMessage.className}__author`
const chatMessageTimestampClassname = `& .${ChatMessage.className}__timestamp`
Copy link
Member

Choose a reason for hiding this comment

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

nit: it's a selector, not class name 🐤

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, will rename those, although still not sure how to export this slot's classnames from the components... Should we add authorClassName, timestampClassName as static fields in the ChatMesage?! Maybe just a constants exported from the ChatMessage (although it will be not consistent as other class names defined).

Copy link
Member

Choose a reason for hiding this comment

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

My first guess would be a static field, something like ChatMessage.selectors.author.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would like to avoid the name selectors as it indicates that we will have the class selector . at the end of the string. We may add something like ChatMessage.classes.author, but if we decide in future to have some different selectors (other then the classes, then maybe your suggestion would make more sense for the user... Just like to mention that if we decide with the selectors, would like to specify the authors and timestamp there as .ui-chat__item__author and .ui-chat__item__timestamp respectively. @kuzhelov @Bugaa92 @layershifter what is your opinion on this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will add a static field called cssSelectors, and will specify the selectors for the author and timestamp there.

Copy link
Member

@layershifter layershifter Jan 28, 2019

Choose a reason for hiding this comment

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

My opinion:
We call timestamp and author as slots. So may be it can be slotClassNames field be defined as a static field on the matching component

Copy link
Member

Choose a reason for hiding this comment

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

Later we can introduce a test that asserts that all keys that are presented in styles have matching slotClassName

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe slotCssSelectors will be better name, as in future we may have more complex selectors that are not necessarily classNames. And defining the selector will allow the user to always use them consistently without adding the necessary selector for className for example.. What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

  1. As user I want to have CSS class names because I should be able to build custom selectors:
`.${ChatItem.slotCssSelectors.author} + .${Another.slotCssSelectors.slot}`
  1. We have className property on each component, it's strange to have selectors


const chatItemStyles: ComponentSlotStylesInput<ChatItemProps, ChatItemVariables> = {
root: ({ props: p, variables: v }): ICSSInJSStyle => ({
position: 'relative',
marginTop: v.margin,
marginBottom: v.margin,
}),
root: ({ props: p, variables: v }): ICSSInJSStyle => {
const { grouped } = p
return {
position: 'relative',
...((!grouped || grouped === 'start') && { marginTop: pxToRem(16) }),
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
...(grouped &&
grouped !== 'start' && {
marginTop: pxToRem(2),
[chatMessageAuthorClassname]: {
display: 'none',
},
[chatMessageTimestampClassname]: {
display: 'none',
},
}),
marginBottom: 0,
}
},

gutter: ({ props: p, variables: v }): ICSSInJSStyle => ({
position: 'absolute',
marginTop: v.gutterMargin,
[p.gutterPosition === 'end' ? 'right' : 'left']: 0,
}),
gutter: ({ props: p, variables: v }): ICSSInJSStyle => {
const { grouped } = p
return {
position: 'absolute',
marginTop: v.gutterMargin,
[p.gutterPosition === 'end' ? 'right' : 'left']: 0,
...(grouped &&
grouped !== 'start' && {
visibility: 'hidden',
Copy link
Collaborator

Choose a reason for hiding this comment

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

we have display: 'none' on the author and timestamp and visibility: 'hidden' here, any reason we cannot keep consistency? I'd expect display: 'none' to work here as well

}),
}
},

message: ({ props: p, variables: v }): ICSSInJSStyle => ({
position: 'relative',
marginLeft: v.messageMargin,
marginRight: v.messageMargin,
}),
message: ({ props: p, variables: v }): ICSSInJSStyle => {
const { grouped } = p
return {
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
position: 'relative',
marginLeft: v.messageMargin,
marginRight: v.messageMargin,
...(grouped === 'middle' && {
[chatMessageClassname]: {
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
paddingTop: pxToRem(5),
paddingBottom: pxToRem(7),
},
}),
...(grouped === 'start' && {
[chatMessageClassname]: {
borderTopLeftRadius: pxToRem(3),
borderBottomLeftRadius: 0,
},
}),
...(grouped === 'end' && {
[chatMessageClassname]: {
borderTopLeftRadius: 0,
borderBottomLeftRadius: pxToRem(3),
paddingTop: pxToRem(5),
paddingBottom: pxToRem(7),
},
}),
}
},
}

export default chatItemStyles
10 changes: 8 additions & 2 deletions src/themes/teams/components/Chat/chatMessageStyles.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { ComponentSlotStylesInput, ICSSInJSStyle } from '../../../types'
import { ChatMessageProps } from '../../../../components/Chat/ChatMessage'
import { ChatMessageVariables } from './chatMessageVariables'
import { pxToRem } from '../../../../lib'

const chatMessageStyles: ComponentSlotStylesInput<ChatMessageProps, ChatMessageVariables> = {
root: ({ props: p, variables: v }): ICSSInJSStyle => ({
display: 'inline-block',
padding: v.padding,
paddingLeft: v.padding,
Copy link
Collaborator

Choose a reason for hiding this comment

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

@mnajdova this is now causing a problem with Control Messages; the padding values are different there so they need to be updated via the padding prop; the problem with this change is that padding variable only changes the left and right paddings as we're now hardcoding top padding to 8 and bottom padding to 10

fyi @jurokapsiar - Marija, Juraj has more input here and can show you the issue on Embed side

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was change because the paddingsTop and Bottom are different when the messages are grouped, while the left and right padding are always the same. I am not sure why embed are using the ChatMessage component for the Control messages, but sure let's take a look.

paddingRight: v.padding,
paddingTop: pxToRem(8),
paddingBottom: pxToRem(10),
borderRadius: v.borderRadius,
border: v.border,
color: v.color,
Expand All @@ -23,7 +27,9 @@ const chatMessageStyles: ComponentSlotStylesInput<ChatMessageProps, ChatMessageV
}),

author: ({ props: p, variables: v }): ICSSInJSStyle => ({
display: p.mine ? 'none' : undefined,
...(p.mine && {
display: 'none',
}),
marginRight: v.authorMargin,
}),

Expand Down
2 changes: 1 addition & 1 deletion src/themes/teams/components/Chat/chatMessageVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default (siteVars): ChatMessageVariables => ({
backgroundColorMine: '#E5E5F1',
borderRadius: '0.3rem',
color: 'rgb(64, 64, 64)',
padding: pxToRem(14),
padding: pxToRem(16),
authorMargin: pxToRem(10),
contentFocusOutlineColor: siteVars.brand,
border: 'none',
Expand Down