diff --git a/CHANGELOG.md b/CHANGELOG.md index b0896e292f..b2f3ec4cd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### BREAKING CHANGES - Change font ramp and Text size API @codepretty ([#214](https://github.com/stardust-ui/react/pull/214)) +- Add `ChatItem` component that can be used inside the `Chat` via the Children API or the `items` prop, instead of the `Chat.Message` used directly in the previous `messages` prop @mnajdova ([#255](https://github.com/stardust-ui/react/pull/255)) ### Features - Add embed mode for `FocusZone` and use it in newly added Chat behaviors @tomasiser ([#233](https://github.com/stardust-ui/react/pull/233)) diff --git a/docs/src/examples/components/Chat/Types/ChatExample.shorthand.tsx b/docs/src/examples/components/Chat/Types/ChatExample.shorthand.tsx index 508b1307c1..a1c2431309 100644 --- a/docs/src/examples/components/Chat/Types/ChatExample.shorthand.tsx +++ b/docs/src/examples/components/Chat/Types/ChatExample.shorthand.tsx @@ -1,30 +1,62 @@ import React from 'react' +import { Chat, Divider } from '@stardust-ui/react' -import { Chat } from '@stardust-ui/react' +const janeAvatar = { + image: 'public/images/avatar/small/ade.jpg', + status: { color: 'green', icon: 'check' }, +} -const messages = [ - { key: 1, content: 'Hello', author: 'John Doe', timestamp: 'Yesterday, 10:15 PM', mine: true }, - { key: 2, content: 'Hi', author: 'Jane Doe', timestamp: 'Yesterday, 10:15 PM' }, +const items = [ { - key: 3, content: ( - <> - Let's go get some lunch! I suggest the new Downtown Burgers or Chef's Finest. - + ), - author: 'John Doe', - timestamp: 'Yesterday, 10:15 PM', - mine: true, }, { - key: 4, - content: - 'Sure thing. I was thinking we should try the new place downtown. The name of its chief promises a delicious food being offered.', - author: 'Jane Doe', - timestamp: 'Yesterday, 10:15 PM', + content: ( + + ), + }, + { + content: ( + + ), + }, + { + content: ( + + ), + }, + { + content: , + }, + { + content: ( + + ), }, ] -const ChatExampleShorthand = () => +const ChatExample = () => -export default ChatExampleShorthand +export default ChatExample diff --git a/docs/src/examples/components/Chat/Types/ChatExample.tsx b/docs/src/examples/components/Chat/Types/ChatExample.tsx new file mode 100644 index 0000000000..2ffaf98d08 --- /dev/null +++ b/docs/src/examples/components/Chat/Types/ChatExample.tsx @@ -0,0 +1,52 @@ +import React from 'react' +import { Chat, Divider } from '@stardust-ui/react' + +const janeAvatar = { + image: 'public/images/avatar/small/ade.jpg', + status: { color: 'green', icon: 'check' }, +} + +const ChatExample = () => ( + + + + + + + + + + + + + + + + + + + + +) + +export default ChatExample diff --git a/docs/src/examples/components/Chat/Variations/ChatExampleAvatar.shorthand.tsx b/docs/src/examples/components/Chat/Variations/ChatExampleAvatar.shorthand.tsx deleted file mode 100644 index ff066ff8b8..0000000000 --- a/docs/src/examples/components/Chat/Variations/ChatExampleAvatar.shorthand.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react' -import { Chat } from '@stardust-ui/react' - -const availableStatus = { color: 'green', icon: 'check', title: 'Available' } -const busyStatus = { color: 'red', title: 'Busy' } - -const messages = [ - { - key: 1, - author: 'Matt Doe', - timestamp: 'Yesterday, 10:15 PM', - content: 'Hello', - mine: true, - avatar: { image: 'public/images/avatar/small/matt.jpg', status: availableStatus }, - }, - { - key: 2, - author: 'Jenny Doe', - timestamp: 'Yesterday, 10:17 PM', - content: 'Hi', - avatar: { image: 'public/images/avatar/small/jenny.jpg', status: busyStatus }, - }, - { - key: 3, - author: 'Matt Doe', - timestamp: 'Yesterday, 10:18 PM', - content: "Let's go get some lunch!", - mine: true, - avatar: { image: 'public/images/avatar/small/matt.jpg', status: availableStatus }, - }, - { - key: 4, - author: 'Jenny Doe', - timestamp: 'Yesterday, 10:20 PM', - content: 'Sure thing. I was thinking we should try the new place downtown.', - avatar: { image: 'public/images/avatar/small/jenny.jpg', status: busyStatus }, - }, -] - -const ChatExampleAvatar = () => - -export default ChatExampleAvatar diff --git a/docs/src/examples/components/Chat/Variations/index.tsx b/docs/src/examples/components/Chat/Variations/index.tsx deleted file mode 100644 index f9c4f4532e..0000000000 --- a/docs/src/examples/components/Chat/Variations/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import ComponentExample from 'docs/src/components/ComponentDoc/ComponentExample' -import ExampleSection from 'docs/src/components/ComponentDoc/ExampleSection' - -const Variations = () => ( - - - -) - -export default Variations diff --git a/docs/src/examples/components/Chat/index.tsx b/docs/src/examples/components/Chat/index.tsx index bd1a7330ac..0b24f2fae9 100644 --- a/docs/src/examples/components/Chat/index.tsx +++ b/docs/src/examples/components/Chat/index.tsx @@ -1,12 +1,10 @@ import React from 'react' import Types from './Types' -import Variations from './Variations' -const MessageExamples = () => ( +const ChatExamples = () => (
-
) -export default MessageExamples +export default ChatExamples diff --git a/src/components/Chat/Chat.tsx b/src/components/Chat/Chat.tsx index 1abb69097d..79bcd3c88e 100644 --- a/src/components/Chat/Chat.tsx +++ b/src/components/Chat/Chat.tsx @@ -3,9 +3,10 @@ import * as PropTypes from 'prop-types' import * as React from 'react' import { childrenExist, customPropTypes, UIComponent } from '../../lib' +import ChatItem from './ChatItem' import ChatMessage from './ChatMessage' -import { ComponentVariablesInput, ComponentPartStyle } from '../../../types/theme' -import { Extendable, ReactChildren, ItemShorthand } from '../../../types/utils' +import { ComponentPartStyle, ComponentVariablesInput } from '../../../types/theme' +import { Extendable, ItemShorthand, ReactChildren } from '../../../types/utils' import { Accessibility, AccessibilityActionHandlers } from '../../lib/accessibility/interfaces' import ChatBehavior from '../../lib/accessibility/Behaviors/Chat/ChatBehavior' @@ -14,7 +15,7 @@ export interface IChatProps { as?: any className?: string children?: ReactChildren - messages?: ItemShorthand[] + items?: ItemShorthand[] styles?: ComponentPartStyle variables?: ComponentVariablesInput } @@ -28,15 +29,17 @@ class Chat extends UIComponent, any> { /** Accessibility behavior if overridden by the user. */ accessibility: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + /** An element type to render as (string or function). */ as: customPropTypes.as, /** Additional CSS class name(s) to apply. */ className: PropTypes.string, + /** Child content. */ children: PropTypes.node, - /** Shorthand array of messages. */ - messages: PropTypes.arrayOf(PropTypes.any), + /** Shorthand array of the items inside the chat. */ + items: PropTypes.arrayOf(customPropTypes.itemShorthand), /** Additional CSS styles to apply to the component instance. */ styles: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), @@ -47,6 +50,7 @@ class Chat extends UIComponent, any> { static defaultProps = { accessibility: ChatBehavior as Accessibility, as: 'ul' } + static Item = ChatItem static Message = ChatMessage actionHandlers: AccessibilityActionHandlers = { @@ -54,7 +58,7 @@ class Chat extends UIComponent, any> { } renderComponent({ ElementType, classes, accessibility, rest }) { - const { children, messages } = this.props + const { children, items } = this.props return ( , any> { {...accessibility.keyHandlers.root} {...rest} > - {childrenExist(children) - ? children - : _.map(messages, message => ChatMessage.create(message))} + {childrenExist(children) ? children : _.map(items, item => ChatItem.create(item))} ) } diff --git a/src/components/Chat/ChatItem.tsx b/src/components/Chat/ChatItem.tsx new file mode 100644 index 0000000000..0635664797 --- /dev/null +++ b/src/components/Chat/ChatItem.tsx @@ -0,0 +1,64 @@ +import * as React from 'react' +import * as PropTypes from 'prop-types' + +import { + createShorthandFactory, + customPropTypes, + IRenderResultConfig, + UIComponent, +} from '../../lib' +import { ComponentPartStyle, ComponentVariablesInput } from '../../../types/theme' +import { Extendable, ReactChildren } from '../../../types/utils' +import childrenExist from '../../lib/childrenExist' + +export interface IChatItemProps { + as?: any + content?: React.ReactNode + children?: ReactChildren + className?: string + styles?: ComponentPartStyle + variables?: ComponentVariablesInput +} + +class ChatItem extends UIComponent, any> { + static className = 'ui-chat__item' + + static create: Function + + static displayName = 'ChatItem' + + static propTypes = { + /** An element type to render as (string or function). */ + as: customPropTypes.as, + + /** Child content. */ + children: PropTypes.node, + + /** Additional CSS class name(s) to apply. */ + className: PropTypes.string, + + /** Custom styles to be applied for component. */ + styles: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + + /** Custom variables to be applied for component. */ + variables: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + } + + static defaultProps = { + as: 'li', + } + + renderComponent({ ElementType, classes, rest }: IRenderResultConfig) { + const { children, content } = this.props + + return ( + + {childrenExist(children) ? children : content} + + ) + } +} + +ChatItem.create = createShorthandFactory(ChatItem, content => ({ content })) + +export default ChatItem diff --git a/src/components/Chat/ChatMessage.tsx b/src/components/Chat/ChatMessage.tsx index ef3db222c2..4ed2b1a616 100644 --- a/src/components/Chat/ChatMessage.tsx +++ b/src/components/Chat/ChatMessage.tsx @@ -6,15 +6,15 @@ import { childrenExist, createShorthandFactory, customPropTypes, - UIComponent, IRenderResultConfig, + UIComponent, } from '../../lib' import { - ComponentVariablesInput, ComponentPartStyle, + ComponentVariablesInput, IComponentPartStylesInput, } from '../../../types/theme' -import { Extendable, ReactChildren, ItemShorthand } from '../../../types/utils' +import { Extendable, ItemShorthand, ReactChildren } from '../../../types/utils' import Avatar from '../Avatar' import ChatMessageBehavior from '../../lib/accessibility/Behaviors/Chat/ChatMessageBehavior' import { Accessibility, AccessibilityActionHandlers } from '../../lib/accessibility/interfaces' @@ -46,12 +46,13 @@ class ChatMessage extends UIComponent, any> { /** Accessibility behavior if overridden by the user. */ accessibility: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + /** An element type to render as (string or function). */ as: customPropTypes.as, /** Author of the message. */ author: customPropTypes.itemShorthand, - /** Chat messages can have an avatar */ + /** Chat messages can have an avatar. */ avatar: customPropTypes.itemShorthand, /** Child content. */ @@ -78,7 +79,7 @@ class ChatMessage extends UIComponent, any> { static defaultProps = { accessibility: ChatMessageBehavior as Accessibility, - as: 'li', + as: 'div', } actionHandlers: AccessibilityActionHandlers = { diff --git a/src/components/Chat/index.ts b/src/components/Chat/index.ts index bb2bea9087..d0af733955 100644 --- a/src/components/Chat/index.ts +++ b/src/components/Chat/index.ts @@ -1,2 +1,3 @@ export { default } from './Chat' +export { default as ChatItem } from './ChatItem' export { default as ChatMessage } from './ChatMessage' diff --git a/src/components/Grid/Grid.tsx b/src/components/Grid/Grid.tsx index 9c82028213..26d2b7d547 100644 --- a/src/components/Grid/Grid.tsx +++ b/src/components/Grid/Grid.tsx @@ -1,9 +1,9 @@ import * as PropTypes from 'prop-types' import * as React from 'react' -import ReactNode = React.ReactNode import { UIComponent, childrenExist, customPropTypes, IRenderResultConfig } from '../../lib' import { ComponentVariablesInput, ComponentPartStyle } from '../../../types/theme' import { Extendable, ItemShorthand, ReactChildren } from '../../../types/utils' +import ReactNode = React.ReactNode export interface IGridProps { as?: any diff --git a/src/index.ts b/src/index.ts index 20744af6bb..edaa4f56bf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import * as themes from './themes' + export { themes } export { default as Accordion } from './components/Accordion' @@ -7,6 +8,7 @@ export { default as Avatar } from './components/Avatar' export { default as Button } from './components/Button' export { ButtonGroup } from './components/Button' export { default as Chat } from './components/Chat' +export { ChatItem } from './components/Chat' export { ChatMessage } from './components/Chat' export { default as Divider } from './components/Divider' export { default as Grid } from './components/Grid' diff --git a/src/lib/accessibility/Behaviors/Popup/PopupBehavior.ts b/src/lib/accessibility/Behaviors/Popup/PopupBehavior.ts index 379e76db06..17e5351426 100644 --- a/src/lib/accessibility/Behaviors/Popup/PopupBehavior.ts +++ b/src/lib/accessibility/Behaviors/Popup/PopupBehavior.ts @@ -1,6 +1,5 @@ import { Accessibility } from '../../interfaces' import * as keyboardKey from 'keyboard-key' -import _ from 'lodash' /** * @description diff --git a/src/themes/teams/componentStyles.ts b/src/themes/teams/componentStyles.ts index 3153625033..b136c379f4 100644 --- a/src/themes/teams/componentStyles.ts +++ b/src/themes/teams/componentStyles.ts @@ -10,6 +10,7 @@ export { default as Button } from './components/Button/buttonStyles' export { default as ButtonGroup } from './components/Button/buttonGroupStyles' export { default as Chat } from './components/Chat/chatStyles' +export { default as ChatItem } from './components/Chat/chatItemStyles' export { default as ChatMessage } from './components/Chat/chatMessageStyles' export { default as Divider } from './components/Divider/dividerStyles' diff --git a/src/themes/teams/componentVariables.ts b/src/themes/teams/componentVariables.ts index a056a5e647..df10e0a02a 100644 --- a/src/themes/teams/componentVariables.ts +++ b/src/themes/teams/componentVariables.ts @@ -9,6 +9,8 @@ export { default as ButtonGroup } from './components/Button/buttonVariables' export { default as Chat } from './components/Chat/chatVariables' +export { default as ChatItem } from './components/Chat/chatItemVariables' + export { default as ChatMessage } from './components/Chat/chatMessageVariables' export { default as Divider } from './components/Divider/dividerVariables' diff --git a/src/themes/teams/components/Chat/chatItemStyles.ts b/src/themes/teams/components/Chat/chatItemStyles.ts new file mode 100644 index 0000000000..b5231f2c3d --- /dev/null +++ b/src/themes/teams/components/Chat/chatItemStyles.ts @@ -0,0 +1,7 @@ +import { ICSSInJSStyle } from '../../../../../types/theme' + +const chatItemStyles = { + root: (): ICSSInJSStyle => ({}), +} + +export default chatItemStyles diff --git a/src/themes/teams/components/Chat/chatItemVariables.ts b/src/themes/teams/components/Chat/chatItemVariables.ts new file mode 100644 index 0000000000..a78a27d1e7 --- /dev/null +++ b/src/themes/teams/components/Chat/chatItemVariables.ts @@ -0,0 +1,15 @@ +export interface IChatItemVariables { + messageWidth: string + messageColor: string + messageColorMine: string + avatar: { statusBorderColor: string } +} + +export default (siteVars): IChatItemVariables => ({ + messageWidth: '80%', + messageColor: siteVars.white, + messageColorMine: '#E0E0ED', + avatar: { + statusBorderColor: siteVars.gray10, + }, +}) diff --git a/src/themes/teams/components/Chat/chatMessageStyles.ts b/src/themes/teams/components/Chat/chatMessageStyles.ts index ff9dbd6b3b..c777ac5f80 100644 --- a/src/themes/teams/components/Chat/chatMessageStyles.ts +++ b/src/themes/teams/components/Chat/chatMessageStyles.ts @@ -6,18 +6,16 @@ import { pxToRem } from '../../../../lib' const px10asRem = pxToRem(10) const chatMessageStyles: IComponentPartStylesInput = { root: ({ props: p, variables: v }): ICSSInJSStyle => ({ - display: 'flex', + display: 'inline-block', position: 'relative', marginTop: '1rem', marginBottom: '1rem', - ...(p.mine - ? { - marginLeft: 'auto', - } - : { - marginRight: 'auto', - }), + ...(p.mine && { + float: 'right', + }), maxWidth: v.messageWidth, + wordBreak: 'break-word', + wordWrap: 'break-word', }), avatar: ({ props: p }: { props: IChatMessageProps }): ICSSInJSStyle => ({ diff --git a/test/specs/components/Chat/Chat-test.ts b/test/specs/components/Chat/Chat-test.ts index 29e58b36bc..419c15ff79 100644 --- a/test/specs/components/Chat/Chat-test.ts +++ b/test/specs/components/Chat/Chat-test.ts @@ -1,11 +1,16 @@ -import { isConformant, handlesAccessibility } from 'test/specs/commonTests' +import { handlesAccessibility, isConformant } from 'test/specs/commonTests' import Chat from 'src/components/Chat' +import implementsCollectionShorthandProp from '../../commonTests/implementsCollectionShorthandProp' +import ChatItem from 'src/components/Chat/ChatItem' import ChatBehavior from 'src/lib/accessibility/Behaviors/Chat/ChatBehavior' import { IAccessibilityDefinition } from 'src/lib/accessibility/interfaces' +const chatImplementsCollectionShorthandProp = implementsCollectionShorthandProp(Chat) + describe('Chat', () => { isConformant(Chat) + chatImplementsCollectionShorthandProp('items', ChatItem) describe('accessibility', () => { handlesAccessibility(Chat, { diff --git a/test/specs/components/Chat/ChatItem-test.tsx b/test/specs/components/Chat/ChatItem-test.tsx new file mode 100644 index 0000000000..dd6e78f310 --- /dev/null +++ b/test/specs/components/Chat/ChatItem-test.tsx @@ -0,0 +1,7 @@ +import { isConformant } from 'test/specs/commonTests' + +import ChatItem from 'src/components/Chat/ChatItem' + +describe('ChatItem', () => { + isConformant(ChatItem) +}) diff --git a/test/specs/components/Chat/ChatMessage-test.tsx b/test/specs/components/Chat/ChatMessage-test.tsx index b234b4f12f..84144bc935 100644 --- a/test/specs/components/Chat/ChatMessage-test.tsx +++ b/test/specs/components/Chat/ChatMessage-test.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { isConformant, implementsShorthandProp, handlesAccessibility } from 'test/specs/commonTests' +import { handlesAccessibility, implementsShorthandProp, isConformant } from 'test/specs/commonTests' import { mountWithProvider } from '../../../utils' import ChatMessage from 'src/components/Chat/ChatMessage' @@ -24,9 +24,9 @@ describe('ChatMessage', () => { describe('avatar', () => { it('creates an Avatar component when the avatar shorthand is provided', () => { const name = 'John Doe' - const chatMsg = mountWithProvider() + const chatMessage = mountWithProvider() - expect(chatMsg.find('Avatar').prop('name')).toEqual(name) + expect(chatMessage.find('Avatar').prop('name')).toEqual(name) }) }) })