UNPKG

@sendbird/uikit-react

Version:

Sendbird UIKit for React: A feature-rich and customizable chat UI kit with messaging, channel management, and user authentication.

986 lines (972 loc) 116 kB
import React__default, { createContext, useState, useCallback, useContext, useRef, useMemo, useLayoutEffect, useEffect } from 'react'; var ComponentType; (function (ComponentType) { ComponentType["Box"] = "box"; ComponentType["Text"] = "text"; ComponentType["Image"] = "image"; ComponentType["TextButton"] = "textButton"; ComponentType["ImageButton"] = "imageButton"; ComponentType["Carousel"] = "carouselView"; ComponentType["Cascade"] = "cascadeView"; })(ComponentType || (ComponentType = {})); const outSingle$1 = (key, obj) => { if (typeof obj[key] !== 'string') return {}; return { [key]: argbToRgba(obj[key]) }; }; function argbToRgba(string) { if (!string.startsWith('#')) { return string; } if (string.length === 9) { return `#${string.slice(3)}${string[1]}${string[2]}`; } if (string.length === 5) { return `#${string.slice(2)}${string[1]}`; } return string; } const outViewStyle$1 = (viewStyle) => { if (!viewStyle) return {}; return { viewStyle: Object.assign(Object.assign(Object.assign({}, viewStyle), outSingle$1('backgroundColor', viewStyle)), outSingle$1('borderColor', viewStyle)), }; }; const outTextStyle$1 = (textStyle) => { if (!textStyle) return {}; return { textStyle: Object.assign(Object.assign({}, textStyle), outSingle$1('color', textStyle)), }; }; const outImageStyle = (imageStyle) => { if (!imageStyle) return {}; return { imageStyle: Object.assign(Object.assign({}, imageStyle), outSingle$1('tintColor', imageStyle)), }; }; const colorTransform = { run(prop) { if (prop.type === ComponentType.Text || prop.type === ComponentType.TextButton) { return Object.assign(Object.assign(Object.assign({}, prop), outViewStyle$1(prop.viewStyle)), outTextStyle$1(prop.textStyle)); } if (prop.type === ComponentType.ImageButton || prop.type === ComponentType.Image) { return Object.assign(Object.assign(Object.assign({}, prop), outViewStyle$1(prop.viewStyle)), outImageStyle(prop.imageStyle)); } return Object.assign(Object.assign({}, prop), outViewStyle$1(prop.viewStyle)); }, }; const isNumber = (val) => { if (typeof val === 'string') { return !Number.isNaN(Number(val)); } return typeof val === 'number' && !Number.isNaN(val); }; const outSingle = (key, obj) => { if (obj[key] === undefined || obj[key] === null) return {}; return isNumber(obj[key]) ? { [key]: Number(obj[key]) } : {}; }; const outSpacing = (key, spacing) => { if (!spacing) return {}; return { [key]: { left: isNumber(spacing.left) ? Number(spacing.left) : spacing.left, right: isNumber(spacing.right) ? Number(spacing.right) : spacing.right, top: isNumber(spacing.top) ? Number(spacing.top) : spacing.top, bottom: isNumber(spacing.bottom) ? Number(spacing.bottom) : spacing.bottom, }, }; }; const outSize = (key, size) => { if (!size) return {}; return { [key]: { type: size.type, value: isNumber(size.value) ? Number(size.value) : size.value, }, }; }; const outMetadata = (metaData) => { if (!metaData) return {}; return { metaData: { pixelWidth: isNumber(metaData.pixelWidth) ? Number(metaData.pixelWidth) : metaData.pixelWidth, pixelHeight: isNumber(metaData.pixelHeight) ? Number(metaData.pixelHeight) : metaData.pixelHeight, }, }; }; const outViewStyle = (viewStyle) => { if (!viewStyle) return {}; return { viewStyle: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, viewStyle), outSingle('borderWidth', viewStyle)), outSingle('radius', viewStyle)), outSpacing('margin', viewStyle.margin)), outSpacing('padding', viewStyle.padding)), }; }; const outTextStyle = (textStyle) => { if (!textStyle) return {}; return { textStyle: Object.assign(Object.assign(Object.assign({}, textStyle), outSingle('size', textStyle)), outSingle('weight', textStyle)), }; }; const outCarouselStyle = (carouselstyle) => { if (!carouselstyle) return {}; return { carouselStyle: Object.assign(Object.assign(Object.assign({}, carouselstyle), outSingle('spacing', carouselstyle)), outSingle('maxChildWidth', carouselstyle)), }; }; const outCascadeStyle = (cascadeStyle) => { if (!cascadeStyle) return {}; return { cascadeStyle: Object.assign(Object.assign({}, cascadeStyle), outSingle('spacing', cascadeStyle)), }; }; const outViewProps = (view) => { return Object.assign(Object.assign(Object.assign({}, outSize('width', view.width)), outSize('height', view.height)), outViewStyle(view.viewStyle)); }; const outTextProps = (text) => { return Object.assign(Object.assign(Object.assign({}, outViewProps(text)), outTextStyle(text.textStyle)), outSingle('maxTextLines', text)); }; const outImageProps = (image) => { return Object.assign(Object.assign({}, outViewProps(image)), outMetadata(image.metaData)); }; const outCarouselProps = (carousel) => { return Object.assign(Object.assign(Object.assign({}, outViewProps(carousel)), outCarouselStyle(carousel.carouselStyle)), { // Convert only top-level items to find has fill width values. items: carousel.items.map((item) => (Object.assign(Object.assign({}, item), { body: Object.assign(Object.assign({}, item.body), { items: item.body.items.map((it) => numberTransform.run(it)) }) }))) }); }; const outCascadeProps = (cascade) => { return Object.assign(Object.assign(Object.assign({}, outViewProps(cascade)), outCascadeStyle(cascade.cascadeStyle)), { // Convert only top-level items to find has fill width values. items: cascade.items.map((item) => (Object.assign(Object.assign({}, item), { body: Object.assign(Object.assign({}, item.body), { items: item.body.items.map((it) => numberTransform.run(it)) }) }))) }); }; const numberTransform = { run(prop) { if (prop.type === ComponentType.Text || prop.type === ComponentType.TextButton) { return Object.assign(Object.assign({}, prop), outTextProps(prop)); } if (prop.type === ComponentType.ImageButton || prop.type === ComponentType.Image) { return Object.assign(Object.assign({}, prop), outImageProps(prop)); } if (prop.type === ComponentType.Carousel) { return Object.assign(Object.assign({}, prop), outCarouselProps(prop)); } if (prop.type === ComponentType.Cascade) { return Object.assign(Object.assign({}, prop), outCascadeProps(prop)); } return Object.assign(Object.assign({}, prop), outViewProps(prop)); }, }; // -------- Set property mapper const MAPPER = () => undefined; const createParser = (params) => { var _a; const defaultMapper = (params === null || params === void 0 ? void 0 : params.defaultMapper) || MAPPER; const mapper = { defaultMapper, mapBoxProps: (params === null || params === void 0 ? void 0 : params.mapBoxProps) || defaultMapper, mapTextProps: (params === null || params === void 0 ? void 0 : params.mapTextProps) || defaultMapper, mapImageProps: (params === null || params === void 0 ? void 0 : params.mapImageProps) || defaultMapper, mapTextButtonProps: (params === null || params === void 0 ? void 0 : params.mapTextButtonProps) || defaultMapper, mapImageButtonProps: (params === null || params === void 0 ? void 0 : params.mapImageButtonProps) || defaultMapper, mapCarouselProps: (params === null || params === void 0 ? void 0 : params.mapCarouselProps) || defaultMapper, mapCascadeProps: (params === null || params === void 0 ? void 0 : params.mapCascadeProps) || defaultMapper, }; const transforms = [colorTransform, numberTransform, ...((_a = params === null || params === void 0 ? void 0 : params.transforms) !== null && _a !== void 0 ? _a : [])]; return { setTransforms(newTransforms) { transforms.length = 0; transforms.push(...newTransforms); }, addTransforms(newTransforms) { transforms.push(...newTransforms); }, parse(rawItem, options) { const item = transforms.reduce((it, transform) => transform.run(it), rawItem); switch (item.type) { case ComponentType.Box: { return { transformed: item, properties: mapper.mapBoxProps(item, options) }; } case ComponentType.Text: { return { transformed: item, properties: mapper.mapTextProps(item, options) }; } case ComponentType.Image: { return { transformed: item, properties: mapper.mapImageProps(item, options) }; } case ComponentType.TextButton: { return { transformed: item, properties: mapper.mapTextButtonProps(item, options) }; } case ComponentType.ImageButton: { return { transformed: item, properties: mapper.mapImageButtonProps(item, options) }; } case ComponentType.Carousel: { return { transformed: item, properties: mapper.mapCarouselProps(item, options) }; } case ComponentType.Cascade: { return { transformed: item, properties: mapper.mapCascadeProps(item, options) }; } default: return { transformed: item, properties: undefined }; } }, }; }; const FRAGMENT = ({ children }) => React__default.createElement(React__default.Fragment, null, children); function createRenderer(params) { var _a, _b, _c, _d, _e, _f, _g; return { box: ((_a = params === null || params === void 0 ? void 0 : params.views) === null || _a === void 0 ? void 0 : _a.box) || FRAGMENT, text: ((_b = params === null || params === void 0 ? void 0 : params.views) === null || _b === void 0 ? void 0 : _b.text) || FRAGMENT, image: ((_c = params === null || params === void 0 ? void 0 : params.views) === null || _c === void 0 ? void 0 : _c.image) || FRAGMENT, imageButton: ((_d = params === null || params === void 0 ? void 0 : params.views) === null || _d === void 0 ? void 0 : _d.imageButton) || FRAGMENT, textButton: ((_e = params === null || params === void 0 ? void 0 : params.views) === null || _e === void 0 ? void 0 : _e.textButton) || FRAGMENT, carouselView: ((_f = params === null || params === void 0 ? void 0 : params.views) === null || _f === void 0 ? void 0 : _f.carouselView) || FRAGMENT, cascadeView: ((_g = params === null || params === void 0 ? void 0 : params.views) === null || _g === void 0 ? void 0 : _g.cascadeView) || FRAGMENT, }; } var __rest$5 = (undefined && undefined.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; const SizeContext = createContext({ sizes: {}, updateSize: () => { }, }); const SizeContextProvider = ({ children }) => { const [sizes, setSizes] = useState({}); const updateSize = useCallback((_a) => { var { id } = _a, rest = __rest$5(_a, ["id"]); if (id) { setSizes((prevSizes) => (Object.assign(Object.assign({}, prevSizes), { [id]: rest }))); } }, []); return React__default.createElement(SizeContext.Provider, { value: { sizes, updateSize } }, children); }; const useSizeContext = () => useContext(SizeContext); var Layout; (function (Layout) { Layout["Row"] = "row"; Layout["Column"] = "column"; })(Layout || (Layout = {})); var AlignValue; (function (AlignValue) { AlignValue["Center"] = "center"; AlignValue["Left"] = "left"; AlignValue["Right"] = "right"; AlignValue["Top"] = "top"; AlignValue["Bottom"] = "bottom"; })(AlignValue || (AlignValue = {})); /** * @description * Caution: Numbers are passed as string types in the message template builder. * Use `==` comparison instead of `===` comparison when using a regular enum instead of a string enum. */ var FlexSizeSpecValue; (function (FlexSizeSpecValue) { FlexSizeSpecValue[FlexSizeSpecValue["FillParent"] = 0] = "FillParent"; FlexSizeSpecValue[FlexSizeSpecValue["WrapContent"] = 1] = "WrapContent"; })(FlexSizeSpecValue || (FlexSizeSpecValue = {})); var FontWeight; (function (FontWeight) { FontWeight["Normal"] = "normal"; FontWeight["Bold"] = "bold"; })(FontWeight || (FontWeight = {})); var MediaContentMode; (function (MediaContentMode) { MediaContentMode["AspectFit"] = "aspectFit"; MediaContentMode["AspectFill"] = "aspectFill"; MediaContentMode["ScalesToFill"] = "scalesToFill"; })(MediaContentMode || (MediaContentMode = {})); const SUPPORTED_TEMPLATE_VERSIONS = [ 1, 2, // Composite templates: Carousel ]; const alignInFlex = (align) => { switch (align) { case AlignValue.Right: case AlignValue.Bottom: return 'flex-end'; case AlignValue.Center: return 'center'; case AlignValue.Left: case AlignValue.Top: default: return 'flex-start'; } }; const isTemplateVersionSupported = (templateVersion) => { if (!templateVersion) return true; return SUPPORTED_TEMPLATE_VERSIONS.includes(Number(templateVersion)); }; const memoize = (fn) => { const cache = new Map(); const cached = function (val) { return cache.has(val) ? cache.get(val) : cache.set(val, fn.call(this, val)) && cache.get(val); }; cached.cache = cache; return cached; }; /** * Generate each item's id by each item's array depth */ const setTemplateItemId = memoize((data) => { const addIdRecursively = (item, id) => { if ('items' in item && (item === null || item === void 0 ? void 0 : item.items) != null) { item.items.forEach((subItem, subIdx) => { const subId = `${id}-${subIdx}`; subItem.id = subId; addIdRecursively(subItem, subId); }); } }; data.forEach((item, idx) => { const id = `${idx}`; item.id = id; addIdRecursively(item, id); }); return data; }); const defaultProperties = { rootLayout: Layout.Column, view: { size: { width: { type: 'flex', value: FlexSizeSpecValue.FillParent }, height: { type: 'flex', value: FlexSizeSpecValue.WrapContent }, }, }, box: { layout: Layout.Row, align: { vertical: AlignValue.Top, horizontal: AlignValue.Left }, }, textButton: { maxTextLines: 1, }, carousel: { style: { spacing: 10, maxChildWidth: 240, }, }, cascade: { layout: Layout.Column, style: { spacing: 10, }, }, }; const createMessageTemplate = (opts) => { const Container = opts.Container || React__default.Fragment; const UnknownMessage = opts.UnknownMessage || (() => null); const parser = opts.parser || createParser(); const renderer = opts.renderer || createRenderer(); const MessageTemplateBase = ({ templateItems, templateVersion, parentLayout = defaultProperties.box.layout, depth = 0, }) => { if (!isTemplateVersionSupported(templateVersion)) { throw new Error(`Cannot parse template item due to unsupported template version: ${templateVersion}, ${SUPPORTED_TEMPLATE_VERSIONS}`); } return (React__default.createElement(React__default.Fragment, null, templateItems.map((rawProperties, index, siblings) => { const result = parser.parse(rawProperties, { parentLayout, depth, elemIdx: index, siblings }); const item = result.transformed; const rendererProps = { key: index, siblings, parentLayout, parsedProperties: result.properties, rawProperties, }; switch (item.type) { case ComponentType.Carousel: { if (!Array.isArray(item.items)) { throw new Error('Cannot parse template item as Carousel if carousel has no items.'); } return (React__default.createElement(renderer.carouselView, Object.assign({}, item, rendererProps), item.items.map((template, index) => (React__default.createElement(MessageTemplateBase, { key: index, templateItems: template.body.items || [], depth: depth + 1, templateVersion: template.version }))))); } case ComponentType.Cascade: { if (!Array.isArray(item.items)) { throw new Error('Cannot parse template item as Cascade if cascade has no items.'); } return (React__default.createElement(renderer.cascadeView, Object.assign({}, item, rendererProps), item.items.map((template, index) => (React__default.createElement(MessageTemplateBase, { key: index, templateItems: template.body.items || [], depth: depth + 1, templateVersion: template.version }))))); } case ComponentType.Box: { return (React__default.createElement(renderer.box, Object.assign({}, item, rendererProps), React__default.createElement(MessageTemplateBase, { templateItems: item.items || [], parentLayout: item.layout, depth: depth + 1, templateVersion: templateVersion }))); } case ComponentType.Text: { return React__default.createElement(renderer.text, Object.assign({}, item, rendererProps)); } case ComponentType.Image: { return React__default.createElement(renderer.image, Object.assign({}, item, rendererProps)); } case ComponentType.TextButton: { return React__default.createElement(renderer.textButton, Object.assign({}, item, rendererProps)); } case ComponentType.ImageButton: { return React__default.createElement(renderer.imageButton, Object.assign({}, item, rendererProps)); } default: { // or throw new Error('Cannot parse template item') return React__default.createElement(UnknownMessage, { item: item }); } } }))); }; return { MessageTemplate: ({ templateVersion, templateItems, parentLayout = defaultProperties.rootLayout, }) => { const items = setTemplateItemId(templateItems); return (React__default.createElement(SizeContextProvider, null, React__default.createElement(Container, { className: 'sb-message-template__parent' }, React__default.createElement(MessageTemplateBase, { isRoot: true, parentLayout: parentLayout, templateItems: items, templateVersion: templateVersion })))); }, MessageTemplateBase, }; }; var ActionType; (function (ActionType) { ActionType["Web"] = "web"; ActionType["Custom"] = "custom"; ActionType["UIKit"] = "uikit"; ActionType["Internal"] = "internal"; })(ActionType || (ActionType = {})); ({ version: 1, body: { items: [ { type: ComponentType.Image, action: { type: ActionType.Web, data: 'https://docs.sendbird.com' }, height: { type: 'fixed', value: 236 }, viewStyle: { padding: { left: 4, right: 4, top: 4, bottom: 4, }, }, imageUrl: 'https://cdn.pixabay.com/photo/2022/10/12/10/45/bird-7516219_1280.jpg', imageStyle: { contentMode: MediaContentMode.AspectFill }, }, { type: ComponentType.Box, layout: Layout.Column, width: { type: 'flex', value: FlexSizeSpecValue.FillParent }, height: { type: 'fixed', value: 200 }, items: [ { type: ComponentType.Box, width: { type: 'flex', value: FlexSizeSpecValue.FillParent }, height: { type: 'flex', value: FlexSizeSpecValue.FillParent }, viewStyle: { backgroundColor: '#fa6464' }, }, { type: ComponentType.Box, layout: Layout.Column, width: { type: 'flex', value: FlexSizeSpecValue.FillParent }, height: { type: 'flex', value: FlexSizeSpecValue.WrapContent }, viewStyle: { backgroundColor: '#ffaf5c' }, items: [ { type: ComponentType.Text, text: 'Message', align: { horizontal: AlignValue.Center, vertical: AlignValue.Center }, width: { type: 'flex', value: FlexSizeSpecValue.FillParent }, height: { type: 'fixed', value: 50 }, }, { type: ComponentType.Image, action: { type: ActionType.Web, data: 'https://docs.sendbird.com' }, width: { type: 'flex', value: FlexSizeSpecValue.FillParent }, height: { type: 'fixed', value: 50 }, imageUrl: 'https://cdn.pixabay.com/photo/2022/10/12/10/45/bird-7516219_1280.jpg', imageStyle: { contentMode: MediaContentMode.AspectFill }, }, ], }, { type: ComponentType.Box, width: { type: 'flex', value: FlexSizeSpecValue.FillParent }, height: { type: 'fixed', value: 20 }, viewStyle: { backgroundColor: '#ffe450' }, }, { type: ComponentType.Box, width: { type: 'flex', value: FlexSizeSpecValue.FillParent }, height: { type: 'flex', value: FlexSizeSpecValue.FillParent }, viewStyle: { backgroundColor: '#329a1b' }, }, ], }, { type: ComponentType.Box, viewStyle: { padding: { top: 12, bottom: 12, left: 12, right: 12 } }, layout: Layout.Column, items: [ { type: ComponentType.Box, align: { horizontal: AlignValue.Left, vertical: AlignValue.Center }, layout: Layout.Row, viewStyle: { borderWidth: 1, borderColor: '#72723f', }, width: { type: 'flex', value: FlexSizeSpecValue.FillParent }, height: { type: 'fixed', value: 150 }, items: [ { type: ComponentType.Text, width: { type: 'flex', value: FlexSizeSpecValue.FillParent }, height: { type: 'fixed', value: 50 }, text: 'Sample1 text', maxTextLines: 1, align: { vertical: AlignValue.Center, horizontal: AlignValue.Left }, viewStyle: { backgroundColor: '#cc4400', }, textStyle: { size: 16, color: '#f8f8f8', weight: FontWeight.Bold, }, }, { type: ComponentType.ImageButton, action: { type: ActionType.UIKit, data: 'uikit://delete' }, width: { type: 'fixed', value: 20 }, height: { type: 'fixed', value: 20 }, imageUrl: 'https://file-ap-1.sendbird.com/5b5379aa73fd460da22ffaf9a61d0d7f.png', imageStyle: { contentMode: MediaContentMode.AspectFit }, }, ], }, { type: ComponentType.Text, viewStyle: { padding: { top: 6, bottom: 12, left: 0, right: 0 } }, text: 'Esse eu esse duis ipsum et dolor eu ut sit amet consectetur cillum velit officia. Ex adipisicing elit quis ea sit. Occaecat in eu aliqua nulla magna id ut excepteur minim.', maxTextLines: 2, textStyle: { size: 14, color: '#e10000' }, width: { type: 'fixed', value: 200 }, height: { type: 'fixed', value: 50 }, }, { type: ComponentType.TextButton, action: { type: ActionType.Web, data: 'https://www.daum.net' }, text: 'Button 3', textStyle: { size: 14, color: '#742ddd', weight: FontWeight.Bold }, }, ], }, ], }, }); ({ 'version': 1, 'body': { 'items': [ { 'type': ComponentType.Image, 'action': { 'type': ActionType.Web, 'data': 'https://www.naver.com/' }, 'height': { 'type': 'fixed', 'value': 136 }, 'imageUrl': 'https://cdn.pixabay.com/photo/2022/10/12/10/45/bird-7516219_1280.jpg', 'imageStyle': { 'contentMode': MediaContentMode.AspectFill }, }, { 'type': ComponentType.Box, 'viewStyle': { 'padding': { 'top': 12, 'bottom': 12, 'left': 12, 'right': 12 }, backgroundColor: '#cccccc' }, 'layout': Layout.Column, width: { type: 'flex', value: FlexSizeSpecValue.FillParent }, 'items': [ { 'type': ComponentType.Box, width: { type: 'flex', value: FlexSizeSpecValue.FillParent }, 'layout': Layout.Row, 'items': [ { 'type': ComponentType.TextButton, 'action': { 'type': ActionType.Web, 'data': 'https://www.daum.net' }, width: { type: 'fixed', value: 150 }, 'viewStyle': { 'margin': { 'top': 0, 'bottom': 0, 'left': 0, 'right': 4 } }, 'text': 'Button 2', 'textStyle': { 'size': 14, 'weight': FontWeight.Bold }, }, { 'type': ComponentType.TextButton, 'action': { 'type': ActionType.Web, 'data': 'https://www.daum.net' }, width: { type: 'flex', value: FlexSizeSpecValue.FillParent }, 'viewStyle': { 'margin': { 'top': 0, 'bottom': 0, 'left': 4, 'right': 0 } }, 'text': 'Button 3', 'textStyle': { 'size': 14, 'weight': FontWeight.Bold }, }, ], }, { 'type': ComponentType.Box, width: { type: 'flex', value: FlexSizeSpecValue.FillParent }, 'layout': Layout.Row, 'items': [ { 'type': ComponentType.Text, 'text': 'Sample2 text', 'width': { 'type': 'flex', 'value': FlexSizeSpecValue.FillParent }, 'maxTextLines': 1, 'textStyle': { 'size': 16, 'weight': FontWeight.Bold }, }, { 'type': ComponentType.ImageButton, 'action': { 'type': ActionType.UIKit, 'data': 'uikit://delete' }, 'width': { 'type': 'fixed', 'value': 20 }, 'height': { 'type': 'fixed', 'value': 20 }, 'imageUrl': 'https://file-ap-1.sendbird.com/5b5379aa73fd460da22ffaf9a61d0d7f.png', 'imageStyle': { 'contentMode': MediaContentMode.AspectFit }, }, ], }, { 'type': ComponentType.Text, 'viewStyle': { 'padding': { 'top': 6, 'bottom': 12, 'left': 0, 'right': 0 } }, 'text': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aui', 'textStyle': { 'size': 14 }, }, ], }, ], }, }); ({ 'version': 1, 'body': { 'items': [ { 'type': ComponentType.Box, 'viewStyle': { 'backgroundColor': '#ffffff', 'borderWidth': 1, 'borderColor': '#eeeeee', 'radius': 16 }, 'layout': Layout.Column, 'items': [ { 'type': ComponentType.Image, 'height': { 'type': 'fixed', 'value': 200 }, 'imageUrl': 'https://img.freepik.com/free-vector/cartoon-happy-hours-background_52683-81243.jpg?w=2000&t=st=1666689198~exp=1666689798~hmac=23109d44ba03deee7aee069cbeebfcb48fa27f85e53c1cafc5d5d7345f1a2041', 'imageStyle': { 'contentMode': MediaContentMode.AspectFill }, }, { 'type': ComponentType.Box, 'viewStyle': { 'padding': { 'top': 15, 'bottom': 15, 'left': 15, 'right': 15 } }, 'layout': Layout.Column, 'items': [ { 'type': ComponentType.Text, 'text': "Don't miss these deals today", 'maxTextLines': 1, 'textStyle': { 'size': 20, 'color': '#e10000', 'weight': FontWeight.Bold }, }, { 'type': ComponentType.Text, 'viewStyle': { 'margin': { 'top': 5, 'bottom': 0, 'left': 0, 'right': 0 } }, 'text': 'Pay with Maya and get cashback!', 'maxTextLines': 1, 'textStyle': { 'size': 14, 'color': '#e10000' }, }, { 'type': ComponentType.Box, 'align': { 'horizontal': AlignValue.Left, 'vertical': AlignValue.Center }, 'viewStyle': { 'margin': { 'top': 10, 'bottom': 0, 'left': 0, 'right': 0 } }, 'layout': Layout.Row, 'items': [ { 'type': ComponentType.Image, 'width': { 'type': 'fixed', 'value': 50 }, 'height': { 'type': 'fixed', 'value': 50 }, 'viewStyle': { 'backgroundColor': '#ffffff', 'borderWidth': 1, 'borderColor': '#eeeeee', 'radius': 25, }, 'imageUrl': 'https://yt3.ggpht.com/ytc/AMLnZu8Kg89ymE7qt5bsS9vMqi9h2aHiN6m9ID-IgxR6-Q=s900-c-k-c0x00ffffff-no-rj', 'imageStyle': { 'contentMode': MediaContentMode.AspectFill }, }, { 'type': ComponentType.Box, 'align': { 'horizontal': AlignValue.Left, 'vertical': AlignValue.Center }, 'viewStyle': { 'margin': { 'top': 0, 'bottom': 0, 'left': 15, 'right': 0 } }, 'layout': Layout.Column, 'items': [ { 'type': ComponentType.Text, 'text': 'Meralco', 'maxTextLines': 1, 'textStyle': { 'size': 16, 'color': '#e10000', 'weight': FontWeight.Bold }, }, { 'type': ComponentType.Text, 'viewStyle': { 'margin': { 'top': 3, 'bottom': 0, 'left': 0, 'right': 0 } }, 'text': '30% cashback, P300 min spend', 'maxTextLines': 1, 'textStyle': { 'size': 12, 'color': '#610000', 'weight': FontWeight.Bold }, }, ], }, ], }, { 'type': ComponentType.Box, 'align': { 'horizontal': AlignValue.Left, 'vertical': AlignValue.Center }, 'viewStyle': { 'margin': { 'top': 10, 'bottom': 0, 'left': 0, 'right': 0 } }, 'layout': Layout.Row, 'items': [ { 'type': ComponentType.Image, 'width': { 'type': 'fixed', 'value': 50 }, 'height': { 'type': 'fixed', 'value': 50 }, 'viewStyle': { 'backgroundColor': '#ffffff', 'borderWidth': 1, 'borderColor': '#eeeeee', 'radius': 25, }, 'imageUrl': 'https://1000logos.net/wp-content/uploads/2021/12/Globe-Telecom-logo.png', 'imageStyle': { 'contentMode': MediaContentMode.AspectFill, tintColor: '#a49a9a' }, }, { 'type': ComponentType.Box, 'align': { 'horizontal': AlignValue.Left, 'vertical': AlignValue.Center }, 'viewStyle': { 'margin': { 'top': 0, 'bottom': 0, 'left': 15, 'right': 0 } }, 'layout': Layout.Column, 'items': [ { 'type': ComponentType.Text, 'text': 'Globe', 'maxTextLines': 1, 'textStyle': { 'size': 16, 'color': '#e10000', 'weight': FontWeight.Bold }, }, { 'type': ComponentType.Text, 'viewStyle': { 'margin': { 'top': 3, 'bottom': 0, 'left': 0, 'right': 0 } }, 'text': '30% cashback, P300 min spend', 'maxTextLines': 1, 'textStyle': { 'size': 12, 'color': '#610000', 'weight': FontWeight.Bold }, }, ], }, ], }, { 'type': ComponentType.Box, 'align': { 'horizontal': AlignValue.Left, 'vertical': AlignValue.Center }, 'viewStyle': { 'margin': { 'top': 10, 'bottom': 0, 'left': 0, 'right': 0 } }, 'layout': Layout.Row, 'items': [ { 'type': ComponentType.Image, 'width': { 'type': 'fixed', 'value': 50 }, 'height': { 'type': 'fixed', 'value': 50 }, 'viewStyle': { 'backgroundColor': '#ffffff', 'borderWidth': 1, 'borderColor': '#eeeeee', 'radius': 25, }, 'imageUrl': 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Cignal.svg/640px-Cignal.svg.png', 'imageStyle': { 'contentMode': MediaContentMode.AspectFill }, }, { 'type': ComponentType.Box, 'align': { 'horizontal': AlignValue.Left, 'vertical': AlignValue.Center }, 'viewStyle': { 'margin': { 'top': 0, 'bottom': 0, 'left': 15, 'right': 0 } }, 'layout': Layout.Column, 'items': [ { 'type': ComponentType.Text, 'text': 'Cignal', 'maxTextLines': 1, 'textStyle': { 'size': 16, 'color': '#e10000', 'weight': FontWeight.Bold }, }, { 'type': ComponentType.Text, 'viewStyle': { 'margin': { 'top': 3, 'bottom': 0, 'left': 0, 'right': 0 } }, 'text': '30% cashback, P300 min spend', 'maxTextLines': 1, 'textStyle': { 'size': 12, 'color': '#610000', 'weight': FontWeight.Bold }, }, ], }, ], }, { 'type': ComponentType.Box, 'align': { 'horizontal': AlignValue.Left, 'vertical': AlignValue.Center }, 'viewStyle': { 'margin': { 'top': 10, 'bottom': 0, 'left': 0, 'right': 0 } }, 'layout': Layout.Row, 'items': [ { 'type': ComponentType.TextButton, 'action': { 'type': ActionType.Web, 'data': 'https://www.daum.net' }, 'viewStyle': { 'backgroundColor': '#e0e0e0', 'radius': 16, 'margin': { 'top': 0, 'bottom': 0, 'left': 0, 'right': 4 }, 'padding': { 'top': 12, 'bottom': 12, 'left': 12, 'right': 12 }, }, 'text': 'Learn more', 'textStyle': { 'size': 15, 'color': '#e10000', 'weight': FontWeight.Bold }, }, { 'type': ComponentType.TextButton, 'action': { 'type': ActionType.Web, 'data': 'https://www.daum.net' }, 'viewStyle': { 'backgroundColor': '#e10000', 'radius': 16, 'margin': { 'top': 0, 'bottom': 0, 'left': 4, 'right': 0 }, 'padding': { 'top': 12, 'bottom': 12, 'left': 12, 'right': 12 }, }, 'text': 'Pay now', 'textStyle': { 'size': 15, 'color': '#ffffff', 'weight': FontWeight.Bold }, }, ], }, ], }, ], }, ], }, }); ({ 'version': 1, 'body': { 'items': [ { 'type': ComponentType.Box, 'layout': Layout.Column, 'items': [ { 'type': ComponentType.Image, 'imageUrl': 'https://static.sendbird.com/sample/profiles/profile_40_512px.png', 'metaData': { 'pixelWidth': 512, 'pixelHeight': 512, }, 'imageStyle': { 'tintColor': '#44ff1188', }, viewStyle: { 'padding': { 'top': 12, 'right': 12, 'bottom': 12, 'left': 12, }, }, }, { 'type': ComponentType.Box, 'viewStyle': { 'padding': { 'top': 12, 'right': 12, 'bottom': 12, 'left': 12, }, }, 'layout': Layout.Column, 'items': [ { 'type': ComponentType.Box, 'layout': Layout.Row, 'items': [ { 'type': ComponentType.Box, 'layout': Layout.Column, 'items': [ { 'type': ComponentType.Text, 'text': 'hi', 'maxTextLines': 3, 'viewStyle': { 'padding': { 'top': 0, 'bottom': 6, 'left': 0, 'right': 0, }, }, 'textStyle': { 'size': 16, 'weight': FontWeight.Bold, }, }, { 'type': ComponentType.Text, 'text': 'bye', 'maxTextLines': 10, 'textStyle': { 'size': 14, }, }, ], }, { 'type': ComponentType.ImageButton, 'action': { 'type': ActionType.UIKit, 'data': 'sendbirduikit://delete', }, 'width': { 'type': 'fixed', 'value': 20, }, 'height': { 'type': 'fixed', 'value': 20, }, 'metaData': { 'pixelWidth': 60, 'pixelHeight': 60, }, 'imageUrl': 'https://dxstmhyqfqr1o.cloudfront.net/sendbird-message-builder/icon-more.png', 'imageStyle': { 'tintColor': '#ff8d5a', }, }, ], }, { 'type': ComponentType.Box, 'layout': Layout.Column, 'items': [ { 'type': ComponentType.Box, 'viewStyle': { 'margin': { 'top': 16, 'bottom': 0, 'left': 0, 'right': 0, }, }, 'align': { 'horizontal': AlignValue.Left, 'vertical': AlignValue.Center, }, 'layout': Layout.Row, 'items': [ { 'type': ComponentType.Image, 'imageUrl': 'https://ca.slack-edge.com/T0ADCTNEL-ULE240VNV-83fd5776e78e-512', 'width': { 'type': 'fixed', 'value': 40, },