Skip to content

Commit

Permalink
feat: added new message content types
Browse files Browse the repository at this point in the history
Message.HtmlContent
Message.TextContent
Message.ImageContent
Message.CustomContent
  • Loading branch information
supersnager committed Dec 20, 2020
1 parent c70e3f4 commit 32439f7
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 13 deletions.
136 changes: 123 additions & 13 deletions src/components/Message/Message.jsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,55 @@
import React from "react";
import PropTypes from "prop-types";
import classNames from "classnames";
import { allowedChildren, getChildren } from "../utils";
import { allowedChildren, getChildren, getComponentName } from "../utils";
import { prefix } from "../settings";
import Avatar from "../Avatar";
import MessageHeader from "./MessageHeader";
import MessageFooter from "./MessageFooter";
import MessageCustomContent from "./MessageCustomContent";
import MessageImageContent from "./MessageImageContent";
import MessageHtmlContent from "./MessageHtmlContent";
import MessageTextContent from "./MessageTextContent";

/**
* Chat message
*/
export const Message = ({
model: { message, sentTime, sender, direction, position },
model: {
message,
sentTime,
sender,
direction,
position,
type: modelType,
payload: modelPayload,
},
avatarSpacer,
avatarPosition,
type,
payload: argPayload,
children,
className,
...rest
}) => {
const cName = `${prefix}-message`;
const createMarkup = () => ({ __html: message });

const [avatar, header, footer] = getChildren(children, [
const [
avatar,
header,
footer,
htmlContent,
textContent,
imageContent,
customContent,
] = getChildren(children, [
Avatar,
MessageHeader,
MessageFooter,
MessageHtmlContent,
MessageTextContent,
MessageImageContent,
MessageCustomContent,
]);

const directionClass = (() => {
Expand Down Expand Up @@ -98,6 +123,34 @@ export const Message = ({
}
})();

const childContent =
htmlContent ?? textContent ?? imageContent ?? customContent;

const messageContent =
childContent ??
(() => {
const messageType = modelType ?? type;

const payloadFromModel = modelPayload ?? message;
const payload = payloadFromModel ?? argPayload;

const payloadName =
typeof payload === "object" ? getComponentName(payload) : "";

if (messageType === "html" && payloadName !== "Message.CustomContent") {
return <MessageHtmlContent html={payload} />;
} else if (messageType === "text") {
return <MessageTextContent text={payload} />;
} else if (messageType === "image") {
return <MessageImageContent {...payload} />;
} else if (
messageType === "custom" ||
payloadName === "Message.CustomContent"
) {
return payload;
}
})();

return (
<section
{...rest}
Expand All @@ -117,10 +170,7 @@ export const Message = ({
)}
<div className={`${cName}__content-wrapper`}>
{header}
<div
className={`${cName}__content`}
dangerouslySetInnerHTML={createMarkup()}
></div>
<div className={`${cName}__content`}>{messageContent}</div>
{footer}
</div>
</section>
Expand All @@ -130,11 +180,12 @@ export const Message = ({
Message.propTypes = {
/**
* Model object
* **message:** string - Message to send
* **message**: string - Message to send
* **sentTime**: string - Message sent time
* **sender**: string - Sender name
* **direction**: "incoming" | "outgoing" | 0 | 1 - Message direction
* **position**: "single" | "first" | "normal" | "last" | 0 | 1 | 2 | 3 - Message position in feed
* **type**: "html" | "text" | "image" | "custom"
*/
model: PropTypes.shape({
/** Chat message to display - content. */
Expand All @@ -154,6 +205,29 @@ Message.propTypes = {
2,
3,
]),

/**
* Message type
* This property can also be added directly to component, but property from model has precedence.
* Each type can have payload defined in model.payload or in payload property.
* Allowed payloads for different message are described in payload property.
*/
type: PropTypes.oneOf(["html", "text", "image", "custom"]),

/**
* Message payload.
* Must be adequate to message type.
* Allowed payloads for different message types:
* html: String - Html string to render,
* text: String - Text to render,
* image: Object - for object properties please see **&lt;Message.ImageContent /&gt** properties,
* custom: **Message.CustomContent** - Component
*/
payload: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
allowedChildren([MessageCustomContent]),
]),
}),
avatarSpacer: PropTypes.bool,
avatarPosition: PropTypes.oneOf([
Expand All @@ -173,19 +247,50 @@ Message.propTypes = {

/**
* Primary content.
* Content from payload has precedence over Message.*Content properties.
* Whe
* Allowed components:
*
* * &lt;Avatar /&gt;
* * &lt;Message.Header /&gt;
* * &lt;Message.Footer /&gt;
* * &lt;Message.HtmlContent /&gt;
* * &lt;Message.TextContent /&gt;
* * &lt;Message.ImageContent /&gt;
* * &lt;Message.CustomContent /&gt;
*/
children: allowedChildren(
[Avatar, MessageHeader, MessageFooter],
"Allowed types: Message.Header, Message.Footer"
),
children: allowedChildren([
Avatar,
MessageHeader,
MessageFooter,
MessageHtmlContent,
MessageTextContent,
MessageImageContent,
MessageCustomContent,
]),

/** Additional classes. */
className: PropTypes.string,

/**
* Message type
* This property can also exists in model. In that case value from model has precedence.
**/
type: PropTypes.oneOf(["html", "text", "image", "custom"]),

/**
* Message payload.
* Must be adequate to message type.
* Allowed payloads for different message types:
* html: String - Html string to render,
* text: String - Text to render,
* image: Object - for object properties please see **&lt;Message.ImageContent &gt/>** properties,
* custom: **Message.CustomContent** - Component
*/
payload: PropTypes.oneOfType([
PropTypes.string,
allowedChildren([MessageCustomContent]),
]),
};

Message.defaultProps = {
Expand All @@ -197,9 +302,14 @@ Message.defaultProps = {
},
avatarSpacer: false,
avatarPosition: undefined,
type: "html",
};

Message.Header = MessageHeader;
Message.HtmlContent = MessageHtmlContent;
Message.TextContent = MessageTextContent;
Message.ImageContent = MessageImageContent;
Message.CustomContent = MessageCustomContent;
Message.Footer = MessageFooter;

export default Message;
24 changes: 24 additions & 0 deletions src/components/Message/MessageCustomContent.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from "react";
import PropTypes from "prop-types";
import classNames from "classnames";
import { prefix } from "../settings";

export const MessageCustomContent = ({ children, className }) => {
const cName = `${prefix}-message__custom-content`;

return <div className={classNames(cName, className)}>{children}</div>;
};

MessageCustomContent.displayName = "Message.CustomContent";

MessageCustomContent.propTypes = {
/** Primary content. */
children: PropTypes.node,

/** Additional classes. */
className: PropTypes.string,
};

MessageCustomContent.defaultProps = {};

export default MessageCustomContent;
33 changes: 33 additions & 0 deletions src/components/Message/MessageHtmlContent.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from "react";
import PropTypes from "prop-types";
import classNames from "classnames";
import { prefix } from "../settings";

export const MessageHtmlContent = ({ html, className }) => {
const cName = `${prefix}-message__html-content`;

const createMarkup = () => ({ __html: html });

return (
<div
className={classNames(cName, className)}
dangerouslySetInnerHTML={createMarkup()}
/>
);
};

MessageHtmlContent.displayName = "Message.HtmlContent";

MessageHtmlContent.propTypes = {
/**
* Html string will be rendered in component using dangerouslySetInnerHTML
*/
html: PropTypes.string,

/** Additional classes. */
className: PropTypes.string,
};

MessageHtmlContent.defaultProps = {};

export default MessageHtmlContent;
52 changes: 52 additions & 0 deletions src/components/Message/MessageImageContent.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from "react";
import PropTypes from "prop-types";
import classNames from "classnames";
import { prefix } from "../settings";

export const MessageImageContent = ({ src, width, height, alt, className }) => {
const cName = `${prefix}-message__image-content`;

const style = {
width:
typeof width === "number"
? `${width}px`
: typeof width === "string"
? width
: undefined,
height:
typeof height === "number"
? `${height}px`
: typeof height === "string"
? height
: undefined,
};

return (
<div className={classNames(cName, className)}>
<img src={src} style={style} alt={alt} />
</div>
);
};

MessageImageContent.displayName = "Message.ImageContent";

MessageImageContent.propTypes = {
/** Image source*/
src: PropTypes.string,

/** Image width */
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

/** Image height */
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

/** Alternate text for image */
alt: PropTypes.string,

/** Additional classes. */
className: PropTypes.string,
};

MessageImageContent.defaultProps = {};

export default MessageImageContent;
29 changes: 29 additions & 0 deletions src/components/Message/MessageTextContent.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from "react";
import PropTypes from "prop-types";
import classNames from "classnames";
import { prefix } from "../settings";

export const MessageTextContent = ({ text, className, children }) => {
const cName = `${prefix}-message__text-content`;

const content = children ?? text;

return <div className={classNames(cName, className)}>{content}</div>;
};

MessageTextContent.displayName = "Message.TextContent";

MessageTextContent.propTypes = {
/** Primary content - message text */
children: PropTypes.string,

/** Message text. Property has precedence over children */
text: PropTypes.string,

/** Additional classes. */
className: PropTypes.string,
};

MessageTextContent.defaultProps = {};

export default MessageTextContent;
3 changes: 3 additions & 0 deletions src/components/enums.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ export const StatusEnum = [

export const SizeEnum = ["xs", "sm", "md", "lg", "fluid"];

export const MessageTypeEnum = ["html", "text", "image", "custom"];

export default {
SizeEnum,
StatusEnum,
MessageTypeEnum,
};

0 comments on commit 32439f7

Please sign in to comment.