diff --git a/packages/livechat/.storybook/helpers.tsx b/packages/livechat/.storybook/helpers.tsx new file mode 100644 index 0000000000000..12c896dccf55c --- /dev/null +++ b/packages/livechat/.storybook/helpers.tsx @@ -0,0 +1,54 @@ +import { action } from '@storybook/addon-actions'; +import { type DecoratorFunction } from '@storybook/csf'; +import type { Args, PreactFramework } from '@storybook/preact'; +import { loremIpsum as originalLoremIpsum } from 'lorem-ipsum'; + +import gazzoAvatar from './assets/gazzo.jpg'; +import martinAvatar from './assets/martin.jpg'; +import tassoAvatar from './assets/tasso.jpg'; + +export const screenDecorator: DecoratorFunction = (storyFn) => ( +
{storyFn()}
+); + +export const screenProps = () => ({ + theme: { + color: '', + fontColor: '', + iconColor: '', + }, + notificationsEnabled: true, + minimized: false, + windowed: false, + onEnableNotifications: action('enableNotifications'), + onDisableNotifications: action('disableNotifications'), + onMinimize: action('minimize'), + onRestore: action('restore'), + onOpenWindow: action('openWindow'), +}); + +export const avatarResolver = (username: string) => + ({ + 'guilherme.gazzo': gazzoAvatar, + 'martin.schoeler': martinAvatar, + 'tasso.evangelista': tassoAvatar, + }[username]); + +export const attachmentResolver = (url: string) => url; + +const createRandom = (s: number) => () => { + s = Math.sin(s) * 10000; + return s - Math.floor(s); +}; +const loremIpsumRandom = createRandom(42); +export const loremIpsum = (options: Parameters[0]) => + originalLoremIpsum({ random: loremIpsumRandom, ...options }); + +export { gazzoAvatar, martinAvatar, tassoAvatar }; + +export { default as sampleAudio } from './assets/sample-audio.mp3'; +export { default as sampleImage } from './assets/sample-image.jpg'; +export { default as sampleVideo } from './assets/sample-video.mp4'; +export { default as accessoryImage } from './assets/accessoryImage.png'; +export { default as imageBlock } from './assets/imageBlock.png'; +export { default as beepAudio } from './assets/beep.mp3'; diff --git a/packages/livechat/.storybook/logo.svg.d.ts b/packages/livechat/.storybook/logo.svg.d.ts new file mode 100644 index 0000000000000..27c0914b230f0 --- /dev/null +++ b/packages/livechat/.storybook/logo.svg.d.ts @@ -0,0 +1,3 @@ +declare const path: string; + +export = path; diff --git a/packages/livechat/.storybook/main.js b/packages/livechat/.storybook/main.js deleted file mode 100644 index bcdef3ca56958..0000000000000 --- a/packages/livechat/.storybook/main.js +++ /dev/null @@ -1,60 +0,0 @@ -/** @type {import('@storybook/core-common').StorybookConfig} */ -const config = { - stories: ['../src/**/{*.story,story,*.stories,stories}.{js,tsx}'], - addons: [ - { - name: '@storybook/addon-essentials', - options: { - backgrounds: false, - }, - }, - '@storybook/addon-postcss', - ], - core: { - builder: 'webpack4', - }, - webpackFinal: async (config) => { - config.resolve.alias = { - ...config.resolve.alias, - 'react': 'preact/compat', - 'react-dom/test-utils': 'preact/test-utils', - 'react-dom': 'preact/compat', - 'react/jsx-runtime': 'preact/jsx-runtime', - [require.resolve('../src/lib/uiKit')]: require.resolve('./mocks/uiKit'), - }; - - config.module.rules = config.module.rules.filter(({ loader }) => !/json-loader/.test(loader)); - - const fileLoader = config.module.rules.find(({ loader }) => /file-loader/.test(loader)); - fileLoader.test = /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf|mp3|mp4)(\?.*)?$/; - - const urlLoader = config.module.rules.find(({ loader }) => /url-loader/.test(loader)); - urlLoader.test = /\.(webm|wav|m4a|aac|oga)(\?.*)?$/; - - config.module.rules.push({ - test: /\.scss$/, - use: [ - 'style-loader', - { - loader: 'css-loader', - options: { - sourceMap: true, - modules: true, - importLoaders: 1, - }, - }, - 'sass-loader', - ], - }); - - config.module.rules.push({ - test: /\.svg$/, - exclude: [__dirname], - use: ['desvg-loader/preact', 'svg-loader'], - }); - - return config; - }, -}; - -module.exports = config; diff --git a/packages/livechat/.storybook/main.ts b/packages/livechat/.storybook/main.ts new file mode 100644 index 0000000000000..8809ce6be8998 --- /dev/null +++ b/packages/livechat/.storybook/main.ts @@ -0,0 +1,92 @@ +import { type StorybookConfig } from '@storybook/core-common'; +import { type RuleSetRule } from 'webpack'; + +const config: StorybookConfig = { + stories: ['../src/**/{*.story,story,*.stories,stories}.{js,tsx}'], + addons: [ + { + name: '@storybook/addon-essentials', + options: { + backgrounds: false, + }, + }, + '@storybook/addon-postcss', + 'storybook-dark-mode', + ], + core: { + builder: 'webpack4', + }, + webpackFinal: async (config) => { + if (!config.resolve || !config.module) { + throw new Error('Invalid webpack config'); + } + + config.resolve.alias = { + ...config.resolve.alias, + 'react': 'preact/compat', + 'react-dom/test-utils': 'preact/test-utils', + 'react-dom': 'preact/compat', + 'react/jsx-runtime': 'preact/jsx-runtime', + [require.resolve('../src/lib/uiKit')]: require.resolve('./mocks/uiKit.ts'), + }; + + const isRuleSetRule = (rule: any): rule is RuleSetRule => typeof rule === 'object' && rule.test && rule.use; + + config.module.rules ??= []; + + config.module.rules = config.module.rules.filter( + (rule) => isRuleSetRule(rule) && (typeof rule.loader !== 'string' || !/json-loader/.test(rule.loader)), + ); + + const fileLoader = config.module.rules.find( + (rule): rule is RuleSetRule => isRuleSetRule(rule) && typeof rule.loader === 'string' && /file-loader/.test(rule.loader), + ); + if (!fileLoader) { + throw new Error('Invalid webpack config'); + } + fileLoader.test = /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf|mp3|mp4)(\?.*)?$/; + + const urlLoader = config.module.rules + ?.filter(isRuleSetRule) + .find(({ loader }) => typeof loader === 'string' && /url-loader/.test(loader)); + if (!urlLoader) { + throw new Error('Invalid webpack config'); + } + urlLoader.test = /\.(webm|wav|m4a|aac|oga)(\?.*)?$/; + + config.module.rules.push({ + test: /\.scss$/, + use: [ + 'style-loader', + { + loader: 'css-loader', + options: { + sourceMap: true, + modules: true, + importLoaders: 1, + }, + }, + 'sass-loader', + ], + }); + + // eslint-disable-next-line @typescript-eslint/no-var-requires + const path = require('path'); + + const logoPath = path.resolve(path.join(__dirname, './logo.svg')); + + config.module.rules.push({ + ...fileLoader, + test: (srcPath) => srcPath === logoPath, + }); + + config.module.rules.push({ + test: (srcPath) => srcPath.endsWith('.svg') && srcPath !== logoPath, + use: ['desvg-loader/preact', 'svg-loader'], + }); + + return config; + }, +}; + +module.exports = config; diff --git a/packages/livechat/.storybook/manager.js b/packages/livechat/.storybook/manager.js deleted file mode 100644 index 6c006a579dff9..0000000000000 --- a/packages/livechat/.storybook/manager.js +++ /dev/null @@ -1,16 +0,0 @@ -import { addons } from '@storybook/addons'; -import { create } from '@storybook/theming/create'; - -import manifest from '../package.json'; -import logo from './logo.svg'; - -addons.setConfig({ - theme: create({ - base: 'light', - brandTitle: manifest.name, - brandImage: logo, - brandUrl: manifest.homepage, - colorPrimary: '#cbced1', - colorSecondary: '#1d74f5', - }), -}); diff --git a/packages/livechat/.storybook/mocks/uiKit.js b/packages/livechat/.storybook/mocks/uiKit.ts similarity index 89% rename from packages/livechat/.storybook/mocks/uiKit.js rename to packages/livechat/.storybook/mocks/uiKit.ts index db3b540932644..a7c7dbd5025c0 100644 --- a/packages/livechat/.storybook/mocks/uiKit.js +++ b/packages/livechat/.storybook/mocks/uiKit.ts @@ -18,7 +18,7 @@ export const UIKitIncomingInteractionContainerType = { VIEW: 'view', }; -export const triggerAction = async (payload) => { +export const triggerAction = async (payload: unknown) => { await new Promise((resolve) => setTimeout(resolve, 1000)); action('dispatchAction')(payload); }; diff --git a/packages/livechat/.storybook/preview.js b/packages/livechat/.storybook/preview.js deleted file mode 100644 index a8cfa51d5a1da..0000000000000 --- a/packages/livechat/.storybook/preview.js +++ /dev/null @@ -1,11 +0,0 @@ -import 'emoji-mart/css/emoji-mart.css'; -import '../src/styles/index.scss'; - -export const parameters = { - grid: { - cellSize: 4, - }, - options: { - storySort: ([, a], [, b]) => a.kind.localeCompare(b.kind), - }, -}; diff --git a/packages/livechat/.storybook/preview.tsx b/packages/livechat/.storybook/preview.tsx new file mode 100644 index 0000000000000..3662c3abb5c33 --- /dev/null +++ b/packages/livechat/.storybook/preview.tsx @@ -0,0 +1,36 @@ +import { type Parameters } from '@storybook/addons'; +import { themes } from '@storybook/theming'; + +import manifest from '../package.json'; +import logo from './logo.svg'; +import 'emoji-mart/css/emoji-mart.css'; +import '../src/styles/index.scss'; + +export const parameters: Parameters = { + actions: { argTypesRegex: '^on[A-Z].*' }, + backgrounds: { + grid: { + cellSize: 4, + cellAmount: 4, + opacity: 0.5, + }, + }, + options: { + storySort: ([, a], [, b]) => a.kind.localeCompare(b.kind), + }, + layout: 'fullscreen', + darkMode: { + dark: { + ...themes.dark, + brandTitle: manifest.name, + brandImage: logo, + brandUrl: manifest.homepage, + }, + light: { + ...themes.normal, + brandTitle: manifest.name, + brandImage: logo, + brandUrl: manifest.homepage, + }, + }, +}; diff --git a/packages/livechat/package.json b/packages/livechat/package.json index 69f4fdc43b6b6..ec1f6ed603363 100644 --- a/packages/livechat/package.json +++ b/packages/livechat/package.json @@ -11,6 +11,7 @@ "url": "https://github.com/RocketChat/Rocket.Chat", "directory": "packages/livechat" }, + "homepage": "https://rocket.chat", "scripts": { "clean": "rimraf dist", "build": "cross-env TS_NODE_PROJECT=\"tsconfig.webpack.json\" webpack-cli --mode production", @@ -111,6 +112,7 @@ "query-string": "^7.1.3", "react-hook-form": "~7.45.4", "react-i18next": "~13.2.2", + "storybook-dark-mode": "~3.0.1", "whatwg-fetch": "^3.6.19" }, "browserslist": [ diff --git a/packages/livechat/src/Theme.ts b/packages/livechat/src/Theme.ts new file mode 100644 index 0000000000000..c5e62a90f43fb --- /dev/null +++ b/packages/livechat/src/Theme.ts @@ -0,0 +1,6 @@ +import { type CSSProperties } from 'preact/compat'; + +export type Theme = { + color: CSSProperties['backgroundColor']; + fontColor: CSSProperties['color']; +}; diff --git a/packages/livechat/src/components/Alert/stories.tsx b/packages/livechat/src/components/Alert/stories.tsx index 6e63e98400eda..cebf55a9feaf4 100644 --- a/packages/livechat/src/components/Alert/stories.tsx +++ b/packages/livechat/src/components/Alert/stories.tsx @@ -3,7 +3,7 @@ import type { Meta, Story } from '@storybook/preact'; import type { ComponentProps } from 'preact'; import Alert from '.'; -import { loremIpsum, screenDecorator } from '../../helpers.stories'; +import { loremIpsum, screenDecorator } from '../../../.storybook/helpers'; export default { title: 'Components/Alert', diff --git a/packages/livechat/src/components/Avatar/stories.tsx b/packages/livechat/src/components/Avatar/stories.tsx index 7d04d08966aba..104d7769bddb8 100644 --- a/packages/livechat/src/components/Avatar/stories.tsx +++ b/packages/livechat/src/components/Avatar/stories.tsx @@ -2,7 +2,7 @@ import type { Meta, Story } from '@storybook/preact'; import type { ComponentProps } from 'preact'; import { Avatar } from '.'; -import { gazzoAvatar } from '../../helpers.stories'; +import { gazzoAvatar } from '../../../.storybook/helpers'; export default { title: 'Components/Avatar', diff --git a/packages/livechat/src/components/Button/index.tsx b/packages/livechat/src/components/Button/index.tsx index 439928497b8d6..337e433592833 100644 --- a/packages/livechat/src/components/Button/index.tsx +++ b/packages/livechat/src/components/Button/index.tsx @@ -27,6 +27,7 @@ type ButtonProps = { style?: CSSProperties; img?: string; onClick?: JSXInternal.MouseEventHandler; + onMouseUp?: JSXInternal.MouseEventHandler; full?: boolean; }; diff --git a/packages/livechat/src/components/Button/stories.tsx b/packages/livechat/src/components/Button/stories.tsx index 42bdee65166bf..3cf57a4b0d09b 100644 --- a/packages/livechat/src/components/Button/stories.tsx +++ b/packages/livechat/src/components/Button/stories.tsx @@ -3,7 +3,7 @@ import type { Meta, Story } from '@storybook/preact'; import type { ComponentProps } from 'preact'; import { Button } from '.'; -import { gazzoAvatar } from '../../helpers.stories'; +import { gazzoAvatar } from '../../../.storybook/helpers'; import ChatIcon from '../../icons/chat.svg'; const iconElement = ; diff --git a/packages/livechat/src/components/FilesDropTarget/index.tsx b/packages/livechat/src/components/FilesDropTarget/index.tsx index 3e9935c37565c..10d5dcbb9ac81 100644 --- a/packages/livechat/src/components/FilesDropTarget/index.tsx +++ b/packages/livechat/src/components/FilesDropTarget/index.tsx @@ -1,7 +1,5 @@ import type { ComponentChildren, Ref } from 'preact'; -import type { TargetedEvent } from 'preact/compat'; -import { useState } from 'preact/hooks'; -import type { JSXInternal } from 'preact/src/jsx'; +import { useState, type CSSProperties, type ChangeEvent, type TargetedEvent } from 'preact/compat'; import { createClassName } from '../../helpers/createClassName'; import styles from './styles.scss'; @@ -14,7 +12,7 @@ type FilesDropTargetProps = { accept?: string; multiple?: boolean; className?: string; - style?: JSXInternal.CSSProperties; + style?: CSSProperties; children?: ComponentChildren; inputRef?: Ref; onUpload?: (files: File[]) => void; @@ -33,21 +31,21 @@ export const FilesDropTarget = ({ }: FilesDropTargetProps) => { const [dragLevel, setDragLevel] = useState(0); - const handleDragOver = (event: DragEvent) => { + const handleDragOver = (event: TargetedEvent) => { event.preventDefault(); }; - const handleDragEnter = (event: DragEvent) => { + const handleDragEnter = (event: TargetedEvent) => { event.preventDefault(); setDragLevel(dragLevel + 1); }; - const handleDragLeave = (event: DragEvent) => { + const handleDragLeave = (event: TargetedEvent) => { event.preventDefault(); setDragLevel(dragLevel - 1); }; - const handleDrop = (event: DragEvent) => { + const handleDrop = (event: TargetedEvent) => { event.preventDefault(); if (dragLevel === 0 || !event?.dataTransfer?.files?.length) { @@ -59,7 +57,7 @@ export const FilesDropTarget = ({ handleUpload(event?.dataTransfer?.files); }; - const handleInputChange = (event: TargetedEvent) => { + const handleInputChange = (event: ChangeEvent) => { if (!event?.currentTarget?.files?.length) { return; } diff --git a/packages/livechat/src/components/Form/DateInput/index.tsx b/packages/livechat/src/components/Form/DateInput/index.tsx index 974e9b5c5f97b..97ce3ec74f869 100644 --- a/packages/livechat/src/components/Form/DateInput/index.tsx +++ b/packages/livechat/src/components/Form/DateInput/index.tsx @@ -6,7 +6,7 @@ import { createClassName } from '../../../helpers/createClassName'; import styles from './styles.scss'; type DateInputProps = { - name: string; + name?: string; value?: string; placeholder?: string; disabled?: boolean; diff --git a/packages/livechat/src/components/Form/FormField/stories.tsx b/packages/livechat/src/components/Form/FormField/stories.tsx index e36529cb5e63a..249af3001d9ec 100644 --- a/packages/livechat/src/components/Form/FormField/stories.tsx +++ b/packages/livechat/src/components/Form/FormField/stories.tsx @@ -3,7 +3,7 @@ import type { ComponentProps } from 'preact'; import { FormField } from '.'; import { Form, TextInput } from '..'; -import { loremIpsum } from '../../../helpers.stories'; +import { loremIpsum } from '../../../../.storybook/helpers'; export default { title: 'Forms/FormField', diff --git a/packages/livechat/src/components/Form/SelectInput/index.tsx b/packages/livechat/src/components/Form/SelectInput/index.tsx index 32899b4a4aed4..c785445b30211 100644 --- a/packages/livechat/src/components/Form/SelectInput/index.tsx +++ b/packages/livechat/src/components/Form/SelectInput/index.tsx @@ -1,4 +1,4 @@ -import type { Ref } from 'preact'; +import type { ComponentChild, Ref } from 'preact'; import type { TargetedEvent } from 'preact/compat'; import { useState } from 'preact/hooks'; import type { JSXInternal } from 'preact/src/jsx'; @@ -9,8 +9,8 @@ import styles from './styles.scss'; type SelectInputProps = { name?: string; - placeholder?: string; - options: { value: string; label: string }[]; + placeholder?: ComponentChild; + options: { value: string; label: ComponentChild }[]; disabled?: boolean; small?: boolean; error?: boolean; diff --git a/packages/livechat/src/components/Header/index.tsx b/packages/livechat/src/components/Header/index.tsx index 9764c3c2ce372..68c63d0f68080 100644 --- a/packages/livechat/src/components/Header/index.tsx +++ b/packages/livechat/src/components/Header/index.tsx @@ -2,15 +2,13 @@ import type { ComponentChildren, Ref } from 'preact'; import { toChildArray } from 'preact'; import type { JSXInternal } from 'preact/src/jsx'; +import { type Theme } from '../../Theme'; import { createClassName } from '../../helpers/createClassName'; import styles from './styles.scss'; type HeaderProps = { children?: ComponentChildren; - theme?: { - color?: string; - fontColor?: string; - }; + theme?: Partial; className?: string; post?: ComponentChildren; large?: boolean; diff --git a/packages/livechat/src/components/Header/stories.tsx b/packages/livechat/src/components/Header/stories.tsx index 8f84deb67347b..484d98526fdc7 100644 --- a/packages/livechat/src/components/Header/stories.tsx +++ b/packages/livechat/src/components/Header/stories.tsx @@ -3,7 +3,7 @@ import type { Meta, Story } from '@storybook/preact'; import type { ComponentProps } from 'preact'; import Header, { Picture, Content, SubTitle, Title, Actions, Action, Post, CustomField } from '.'; -import { gazzoAvatar } from '../../helpers.stories'; +import { gazzoAvatar } from '../../../.storybook/helpers'; import Arrow from '../../icons/arrowDown.svg'; import Bell from '../../icons/bell.svg'; import NewWindow from '../../icons/newWindow.svg'; diff --git a/packages/livechat/src/components/Menu/index.js b/packages/livechat/src/components/Menu/index.tsx similarity index 56% rename from packages/livechat/src/components/Menu/index.js rename to packages/livechat/src/components/Menu/index.tsx index a280dd0c91d78..1bf8125a80a04 100644 --- a/packages/livechat/src/components/Menu/index.js +++ b/packages/livechat/src/components/Menu/index.tsx @@ -1,44 +1,76 @@ -import { Component } from 'preact'; +import { Component, type ComponentChildren } from 'preact'; +import type { HTMLAttributes, TargetedEvent } from 'preact/compat'; import { createClassName } from '../../helpers/createClassName'; import { normalizeDOMRect } from '../../helpers/normalizeDOMRect'; import { PopoverTrigger } from '../Popover'; import styles from './styles.scss'; -/** @typedef {{ children: any, hidden?: boolean, placement?: string }} MenuProps */ +type MenuProps = { + hidden?: boolean; + placement?: string; + ref?: any; // FIXME: remove this +} & Omit, 'ref'>; -/** @type {{ (props: MenuProps) => JSX.Element; Group: any; Item: any}} */ -export const Menu = ({ children, hidden, placement = '', ...props }) => ( +export const Menu = ({ children, hidden, placement = '', ...props }: MenuProps) => (
{children}
); -/** @typedef {{ children: any, title?: string }} GroupProps */ +type GroupProps = { + title?: string; +} & HTMLAttributes; -/** @type {(props: GroupProps) => JSX.Element} */ -export const Group = ({ children, title = '', ...props }) => ( +export const Group = ({ children, title = '', ...props }: GroupProps) => (
{title &&
{title}
} {children}
); -export const Item = ({ children, primary = false, danger = false, disabled = false, icon = undefined, ...props }) => ( +type ItemProps = { + primary?: boolean; + danger?: boolean; + disabled?: boolean; + icon?: () => ComponentChildren; +} & HTMLAttributes; + +export const Item = ({ children, primary = false, danger = false, disabled = false, icon = undefined, ...props }: ItemProps) => ( ); -class PopoverMenuWrapper extends Component { - state = {}; - handleRef = (ref) => { +type PopoverMenuWrapperProps = { + children?: ComponentChildren; + dismiss: () => void; + triggerBounds: DOMRect; + overlayBounds: DOMRect; +}; + +type PopoverMenuWrapperState = { + position?: { + left?: number; + right?: number; + top?: number; + bottom?: number; + }; + placement?: string; +}; + +class PopoverMenuWrapper extends Component { + state: PopoverMenuWrapperState = {}; + + menuRef: (Component & { base: Element }) | null = null; + + handleRef = (ref: (Component & { base: Element }) | null) => { this.menuRef = ref; }; - handleClick = ({ target }) => { - if (!target.closest(`.${styles.menu__item}`)) { + handleClick = ({ target }: TargetedEvent) => { + if (!(target as HTMLElement)?.closest(`.${styles.menu__item}`)) { return; } @@ -48,7 +80,7 @@ class PopoverMenuWrapper extends Component { componentDidMount() { const { triggerBounds, overlayBounds } = this.props; - const menuBounds = normalizeDOMRect(this.menuRef.base.getBoundingClientRect()); + const menuBounds = normalizeDOMRect(this.menuRef?.base?.getBoundingClientRect()); const menuWidth = menuBounds.right - menuBounds.left; const menuHeight = menuBounds.bottom - menuBounds.top; @@ -56,11 +88,11 @@ class PopoverMenuWrapper extends Component { const rightSpace = overlayBounds.right - triggerBounds.left; const bottomSpace = overlayBounds.bottom - triggerBounds.bottom; - const left = menuWidth < rightSpace ? triggerBounds.left - overlayBounds.left : null; - const right = menuWidth < rightSpace ? null : overlayBounds.right - triggerBounds.right; + const left = menuWidth < rightSpace ? triggerBounds.left - overlayBounds.left : undefined; + const right = menuWidth < rightSpace ? undefined : overlayBounds.right - triggerBounds.right; - const top = menuHeight < bottomSpace ? triggerBounds.bottom : null; - const bottom = menuHeight < bottomSpace ? null : overlayBounds.bottom - triggerBounds.top; + const top = menuHeight < bottomSpace ? triggerBounds.bottom : undefined; + const bottom = menuHeight < bottomSpace ? undefined : overlayBounds.bottom - triggerBounds.top; const placement = `${menuWidth < rightSpace ? 'right' : 'left'}-${menuHeight < bottomSpace ? 'bottom' : 'top'}`; @@ -71,7 +103,7 @@ class PopoverMenuWrapper extends Component { }); } - render = ({ children }) => ( + render = ({ children }: PopoverMenuWrapperProps) => ( void }) => void, overlayed?: boolean }} PopoverMenuProps */ +type PopoverMenuProps = { + children?: ComponentChildren; + trigger: (contextValue: { pop: () => void }) => void; + overlayed?: boolean; +}; -/** @type {(props: PopoverMenuProps) => JSX.Element} */ -export const PopoverMenu = ({ children = null, trigger, overlayed }) => ( +export const PopoverMenu = ({ children = null, trigger, overlayed }: PopoverMenuProps) => ( ( {({ open }) => children[0]({ pop: open.bind(null, children[1], props) })} ); diff --git a/packages/livechat/src/components/Screen/Footer.stories.tsx b/packages/livechat/src/components/Screen/Footer.stories.tsx index 5038b1faf5840..90faa3869a84e 100644 --- a/packages/livechat/src/components/Screen/Footer.stories.tsx +++ b/packages/livechat/src/components/Screen/Footer.stories.tsx @@ -4,7 +4,7 @@ import i18next from 'i18next'; import type { ComponentProps } from 'preact'; import { Screen } from '.'; -import { screenDecorator } from '../../helpers.stories'; +import { screenDecorator } from '../../../.storybook/helpers'; import { FooterOptions } from '../Footer'; import Menu from '../Menu'; diff --git a/packages/livechat/src/components/Screen/stories.tsx b/packages/livechat/src/components/Screen/stories.tsx index 5113046cc97a0..65217c143cae6 100644 --- a/packages/livechat/src/components/Screen/stories.tsx +++ b/packages/livechat/src/components/Screen/stories.tsx @@ -3,7 +3,7 @@ import type { Meta, Story } from '@storybook/preact'; import type { ComponentProps } from 'preact'; import { Screen } from '.'; -import { gazzoAvatar, screenDecorator } from '../../helpers.stories'; +import { gazzoAvatar, screenDecorator } from '../../../.storybook/helpers'; export default { title: 'Components/Screen', diff --git a/packages/livechat/src/components/Sound/stories.tsx b/packages/livechat/src/components/Sound/stories.tsx index 4a5a89b16ab51..6a854dce39957 100644 --- a/packages/livechat/src/components/Sound/stories.tsx +++ b/packages/livechat/src/components/Sound/stories.tsx @@ -3,7 +3,7 @@ import type { Meta, Story } from '@storybook/preact'; import type { ComponentProps } from 'preact'; import { Sound } from '.'; -import { beepAudio, sampleAudio } from '../../helpers.stories'; +import { beepAudio, sampleAudio } from '../../../.storybook/helpers'; export default { title: 'Components/Sound', diff --git a/packages/livechat/src/components/uiKit/message/ActionsBlock/index.js b/packages/livechat/src/components/uiKit/message/ActionsBlock/index.tsx similarity index 80% rename from packages/livechat/src/components/uiKit/message/ActionsBlock/index.js rename to packages/livechat/src/components/uiKit/message/ActionsBlock/index.tsx index 9791a8df3ed2a..d94c6e03a592a 100644 --- a/packages/livechat/src/components/uiKit/message/ActionsBlock/index.js +++ b/packages/livechat/src/components/uiKit/message/ActionsBlock/index.tsx @@ -1,13 +1,20 @@ +import type * as uikit from '@rocket.chat/ui-kit'; import { BlockContext } from '@rocket.chat/ui-kit'; import { useState, useMemo, useCallback } from 'preact/compat'; -import { withTranslation } from 'react-i18next'; +import { useTranslation } from 'react-i18next'; import { createClassName } from '../../../../helpers/createClassName'; import { Button } from '../../../Button'; import Block from '../Block'; import styles from './styles.scss'; -const ActionsBlock = ({ appId, blockId, elements, parser, t }) => { +type ActionsBlockProps = uikit.ActionsBlock & { + parser: any; + t: any; +}; + +const ActionsBlock = ({ appId, blockId, elements, parser }: ActionsBlockProps) => { + const { t } = useTranslation(); const [collapsed, setCollapsed] = useState(true); const renderableElements = useMemo(() => (collapsed ? elements.slice(0, 5) : elements), [collapsed, elements]); const hiddenElementsCount = elements.length - renderableElements.length; @@ -43,4 +50,4 @@ const ActionsBlock = ({ appId, blockId, elements, parser, t }) => { ); }; -export default withTranslation()(ActionsBlock); +export default ActionsBlock; diff --git a/packages/livechat/src/components/uiKit/message/Block.js b/packages/livechat/src/components/uiKit/message/Block.tsx similarity index 74% rename from packages/livechat/src/components/uiKit/message/Block.js rename to packages/livechat/src/components/uiKit/message/Block.tsx index b90beac1ef515..a6b8e371de8b3 100644 --- a/packages/livechat/src/components/uiKit/message/Block.js +++ b/packages/livechat/src/components/uiKit/message/Block.tsx @@ -1,4 +1,4 @@ -import { createContext } from 'preact'; +import { type ComponentChildren, createContext } from 'preact'; import { memo, useContext, useCallback, useState, useRef, useEffect } from 'preact/compat'; import { useDispatchAction } from './Surface'; @@ -8,7 +8,13 @@ const BlockContext = createContext({ blockId: null, }); -const Block = ({ appId, blockId, children }) => ( +type BlockProps = { + appId?: string; + blockId?: string; + children: ComponentChildren; +}; + +const Block = ({ appId, blockId, children }: BlockProps) => ( ( /> ); -export const usePerformAction = (actionId) => { +export const usePerformAction = (actionId: string) => { const { appId } = useContext(BlockContext); const dispatchAction = useDispatchAction(); @@ -51,7 +57,7 @@ export const usePerformAction = (actionId) => { [actionId, appId, dispatchAction], ); - return [perform, performing]; + return [perform, performing] as const; }; export default memo(Block); diff --git a/packages/livechat/src/components/uiKit/message/ButtonElement/index.js b/packages/livechat/src/components/uiKit/message/ButtonElement/index.tsx similarity index 57% rename from packages/livechat/src/components/uiKit/message/ButtonElement/index.js rename to packages/livechat/src/components/uiKit/message/ButtonElement/index.tsx index 2cc2ea02615c1..cab76bddad55c 100644 --- a/packages/livechat/src/components/uiKit/message/ButtonElement/index.js +++ b/packages/livechat/src/components/uiKit/message/ButtonElement/index.tsx @@ -1,17 +1,24 @@ -import { BlockContext } from '@rocket.chat/ui-kit'; +import * as uikit from '@rocket.chat/ui-kit'; +import type { ComponentChild } from 'preact'; +import type { TargetedEvent } from 'preact/compat'; import { memo, useCallback } from 'preact/compat'; import { createClassName } from '../../../../helpers/createClassName'; import { usePerformAction } from '../Block'; import styles from './styles.scss'; -const handleMouseUp = ({ target }) => target.blur(); +const handleMouseUp = ({ currentTarget }: TargetedEvent) => currentTarget.blur(); -const ButtonElement = ({ text, actionId, url, value, style, context, confirm, parser }) => { +type ButtonElementProps = uikit.ButtonElement & { + context: uikit.BlockContext; + parser: uikit.SurfaceRenderer; +}; + +const ButtonElement = ({ text, actionId, url, value, style, context, confirm, parser }: ButtonElementProps) => { const [performAction, performingAction] = usePerformAction(actionId); const handleClick = useCallback( - async (event) => { + async (event: TargetedEvent) => { event.preventDefault(); if (confirm) { @@ -20,6 +27,9 @@ const ButtonElement = ({ text, actionId, url, value, style, context, confirm, pa if (url) { const newTab = window.open(); + if (!newTab) { + throw new Error('Failed to open new tab'); + } newTab.opener = null; newTab.location = url; return; @@ -35,8 +45,8 @@ const ButtonElement = ({ text, actionId, url, value, style, context, confirm, pa children={parser.text(text)} className={createClassName(styles, 'uikit-button', { style, - accessory: context === BlockContext.SECTION, - action: context === BlockContext.ACTION, + accessory: context === uikit.BlockContext.SECTION, + action: context === uikit.BlockContext.ACTION, })} disabled={performingAction} type='button' diff --git a/packages/livechat/src/components/uiKit/message/ButtonElement/stories.tsx b/packages/livechat/src/components/uiKit/message/ButtonElement/stories.tsx index adf60b56dc958..36c41c67f13a6 100644 --- a/packages/livechat/src/components/uiKit/message/ButtonElement/stories.tsx +++ b/packages/livechat/src/components/uiKit/message/ButtonElement/stories.tsx @@ -14,7 +14,7 @@ export default { actionId: undefined, url: undefined, value: undefined, - style: null, + style: undefined, context: undefined, confirm: undefined, }, diff --git a/packages/livechat/src/components/uiKit/message/ContextBlock.stories.tsx b/packages/livechat/src/components/uiKit/message/ContextBlock.stories.tsx index 1760ca18135d5..1f967365b4087 100644 --- a/packages/livechat/src/components/uiKit/message/ContextBlock.stories.tsx +++ b/packages/livechat/src/components/uiKit/message/ContextBlock.stories.tsx @@ -1,7 +1,7 @@ import type { Meta } from '@storybook/preact'; import { renderMessageBlocks } from '.'; -import { accessoryImage } from '../../../helpers.stories'; +import { accessoryImage } from '../../../../.storybook/helpers'; export default { title: 'UiKit/Message/Context block', diff --git a/packages/livechat/src/components/uiKit/message/ContextBlock/index.js b/packages/livechat/src/components/uiKit/message/ContextBlock/index.tsx similarity index 60% rename from packages/livechat/src/components/uiKit/message/ContextBlock/index.js rename to packages/livechat/src/components/uiKit/message/ContextBlock/index.tsx index 62d1f05ffb80b..79f0902c7849d 100644 --- a/packages/livechat/src/components/uiKit/message/ContextBlock/index.js +++ b/packages/livechat/src/components/uiKit/message/ContextBlock/index.tsx @@ -1,16 +1,22 @@ +import type * as uikit from '@rocket.chat/ui-kit'; import { BlockContext } from '@rocket.chat/ui-kit'; +import type { ComponentChild } from 'preact'; import { memo } from 'preact/compat'; import { createClassName } from '../../../../helpers/createClassName'; import Block from '../Block'; import styles from './styles.scss'; -const ContextBlock = ({ appId, blockId, elements, parser }) => ( +type ContextBlockProps = uikit.ContextBlock & { + parser: uikit.SurfaceRenderer; +}; + +const ContextBlock = ({ appId, blockId, elements, parser }: ContextBlockProps) => (
{elements.map((element, key) => (
- {parser.renderContext(element, BlockContext.CONTEXT)} + {parser.renderContext(element, BlockContext.CONTEXT, undefined, key)}
))}
diff --git a/packages/livechat/src/components/uiKit/message/DatePickerElement/index.js b/packages/livechat/src/components/uiKit/message/DatePickerElement/index.tsx similarity index 68% rename from packages/livechat/src/components/uiKit/message/DatePickerElement/index.js rename to packages/livechat/src/components/uiKit/message/DatePickerElement/index.tsx index c5fa758ff60c5..724bd8de9fbd5 100644 --- a/packages/livechat/src/components/uiKit/message/DatePickerElement/index.js +++ b/packages/livechat/src/components/uiKit/message/DatePickerElement/index.tsx @@ -1,13 +1,17 @@ +import type * as uikit from '@rocket.chat/ui-kit'; +import type { ChangeEvent } from 'preact/compat'; import { memo, useCallback } from 'preact/compat'; import DateInput from '../../../Form/DateInput'; import { usePerformAction } from '../Block'; -const DatePickerElement = ({ actionId, confirm /* , placeholder */, initialDate /* , parser */ }) => { +type DatePickerElementProps = uikit.DatePickerElement; + +const DatePickerElement = ({ actionId, confirm /* , placeholder */, initialDate /* , parser */ }: DatePickerElementProps) => { const [performAction, performingAction] = usePerformAction(actionId); const handleChange = useCallback( - async (event) => { + async (event: ChangeEvent) => { event.preventDefault(); if (confirm) { @@ -16,7 +20,7 @@ const DatePickerElement = ({ actionId, confirm /* , placeholder */, initialDate await performAction({ initialDate, - selectedDate: event.target.value, + selectedDate: event.currentTarget?.value, }); }, [confirm, initialDate, performAction], diff --git a/packages/livechat/src/components/uiKit/message/DividerBlock/index.js b/packages/livechat/src/components/uiKit/message/DividerBlock/index.tsx similarity index 67% rename from packages/livechat/src/components/uiKit/message/DividerBlock/index.js rename to packages/livechat/src/components/uiKit/message/DividerBlock/index.tsx index 2e8d222d97c11..cdcd95d1bf323 100644 --- a/packages/livechat/src/components/uiKit/message/DividerBlock/index.js +++ b/packages/livechat/src/components/uiKit/message/DividerBlock/index.tsx @@ -1,10 +1,13 @@ +import type * as uikit from '@rocket.chat/ui-kit'; import { memo } from 'preact/compat'; import { createClassName } from '../../../../helpers/createClassName'; import Block from '../Block'; import styles from './styles.scss'; -const DividerBlock = ({ appId, blockId }) => ( +type DividerBlockProps = uikit.DividerBlock; + +const DividerBlock = ({ appId, blockId }: DividerBlockProps) => (
diff --git a/packages/livechat/src/components/uiKit/message/ImageBlock.stories.tsx b/packages/livechat/src/components/uiKit/message/ImageBlock.stories.tsx index 2623e606dbc9b..ed24559f7878a 100644 --- a/packages/livechat/src/components/uiKit/message/ImageBlock.stories.tsx +++ b/packages/livechat/src/components/uiKit/message/ImageBlock.stories.tsx @@ -1,7 +1,7 @@ import type { Meta } from '@storybook/preact'; import { renderMessageBlocks } from '.'; -import { imageBlock } from '../../../helpers.stories'; +import { imageBlock } from '../../../../.storybook/helpers'; export default { title: 'UiKit/Message/Image block', diff --git a/packages/livechat/src/components/uiKit/message/ImageBlock/index.js b/packages/livechat/src/components/uiKit/message/ImageBlock/index.tsx similarity index 89% rename from packages/livechat/src/components/uiKit/message/ImageBlock/index.js rename to packages/livechat/src/components/uiKit/message/ImageBlock/index.tsx index b07c5c0e6bf83..b9801b13ebe47 100644 --- a/packages/livechat/src/components/uiKit/message/ImageBlock/index.js +++ b/packages/livechat/src/components/uiKit/message/ImageBlock/index.tsx @@ -1,3 +1,5 @@ +import type * as uikit from '@rocket.chat/ui-kit'; +import type { ComponentChild } from 'preact'; import { memo, useEffect, useState, useMemo } from 'preact/compat'; import { createClassName } from '../../../../helpers/createClassName'; @@ -6,7 +8,11 @@ import styles from './styles.scss'; const MAX_SIZE = 360; -const ImageBlock = ({ appId, blockId, title, imageUrl, altText, parser }) => { +type ImageBlockProps = uikit.ImageBlock & { + parser: uikit.SurfaceRenderer; +}; + +const ImageBlock = ({ appId, blockId, title, imageUrl, altText, parser }: ImageBlockProps) => { const [{ loading, naturalWidth, naturalHeight }, updateImageState] = useState(() => ({ loading: true, naturalWidth: MAX_SIZE, diff --git a/packages/livechat/src/components/uiKit/message/ImageElement/index.js b/packages/livechat/src/components/uiKit/message/ImageElement/index.tsx similarity index 53% rename from packages/livechat/src/components/uiKit/message/ImageElement/index.js rename to packages/livechat/src/components/uiKit/message/ImageElement/index.tsx index f09ffbb250d59..1c8f698c2bc8e 100644 --- a/packages/livechat/src/components/uiKit/message/ImageElement/index.js +++ b/packages/livechat/src/components/uiKit/message/ImageElement/index.tsx @@ -1,15 +1,19 @@ -import { BlockContext } from '@rocket.chat/ui-kit'; +import * as uikit from '@rocket.chat/ui-kit'; import { memo } from 'preact/compat'; import { createClassName } from '../../../../helpers/createClassName'; import styles from './styles.scss'; -const ImageElement = ({ imageUrl, altText, context }) => ( +type ImageElementProps = uikit.ImageElement & { + context: uikit.BlockContext; +}; + +const ImageElement = ({ imageUrl, altText, context }: ImageElementProps) => (
{ - const handleMouseUp = useCallback(({ target }) => { - target.blur(); +type OverflowTriggerProps = { + loading: boolean; + onClick: () => void; +}; + +const OverflowTrigger = ({ loading, onClick }: OverflowTriggerProps) => { + const handleMouseUp = useCallback(({ currentTarget }: TargetedEvent) => { + currentTarget.blur(); }, []); return ( @@ -26,9 +34,15 @@ const OverflowTrigger = ({ loading, onClick }) => { ); }; -const OverflowOption = ({ confirm, text, value, url, parser, onClick }) => { +type OverflowOptionProps = uikit.Option & { + confirm: boolean; + parser: uikit.SurfaceRenderer; + onClick: (value: string) => void; +}; + +const OverflowOption = ({ confirm, text, value, url, parser, onClick }: OverflowOptionProps) => { const handleClick = useCallback( - async (event) => { + async (event: TargetedEvent) => { event.preventDefault(); if (confirm) { @@ -37,6 +51,9 @@ const OverflowOption = ({ confirm, text, value, url, parser, onClick }) => { if (url) { const newTab = window.open(); + if (!newTab) { + throw new Error('Could not open new tab'); + } newTab.opener = null; newTab.location = url; return; @@ -50,11 +67,15 @@ const OverflowOption = ({ confirm, text, value, url, parser, onClick }) => { return {parser.text(text)}; }; -const OverflowElement = ({ actionId, confirm, options, parser }) => { +type OverflowElementProps = uikit.OverflowElement & { + parser: uikit.SurfaceRenderer; +}; + +const OverflowElement = ({ actionId, confirm, options, parser }: OverflowElementProps) => { const [performAction, performingAction] = usePerformAction(actionId); const handleClick = useCallback( - async (value) => { + async (value: TargetedEvent) => { await performAction({ value }); }, [performAction], diff --git a/packages/livechat/src/components/uiKit/message/PlainText/index.tsx b/packages/livechat/src/components/uiKit/message/PlainText/index.tsx index bbbe5ae7ebb9e..e5cb6986359f4 100644 --- a/packages/livechat/src/components/uiKit/message/PlainText/index.tsx +++ b/packages/livechat/src/components/uiKit/message/PlainText/index.tsx @@ -1,7 +1,7 @@ import { memo } from 'preact/compat'; import { createClassName } from '../../../../helpers/createClassName'; -import shortnameToUnicode from '../../../Emoji/shortnameToUnicode'; +import shortnameToUnicode from '../../../../lib/emoji/shortnameToUnicode'; import MarkdownBlock from '../../../MarkdownBlock'; import styles from './styles.scss'; diff --git a/packages/livechat/src/components/uiKit/message/SectionBlock.stories.tsx b/packages/livechat/src/components/uiKit/message/SectionBlock.stories.tsx index f24c308da0902..3e74f3384e0a5 100644 --- a/packages/livechat/src/components/uiKit/message/SectionBlock.stories.tsx +++ b/packages/livechat/src/components/uiKit/message/SectionBlock.stories.tsx @@ -2,7 +2,7 @@ import { action } from '@storybook/addon-actions'; import type { Meta } from '@storybook/preact'; import { renderMessageBlocks } from '.'; -import { accessoryImage } from '../../../helpers.stories'; +import { accessoryImage } from '../../../../.storybook/helpers'; import { PopoverContainer } from '../../Popover'; import Surface from './Surface'; diff --git a/packages/livechat/src/components/uiKit/message/SectionBlock/index.js b/packages/livechat/src/components/uiKit/message/SectionBlock/index.tsx similarity index 63% rename from packages/livechat/src/components/uiKit/message/SectionBlock/index.js rename to packages/livechat/src/components/uiKit/message/SectionBlock/index.tsx index 0d829788c282b..c59b522464f6a 100644 --- a/packages/livechat/src/components/uiKit/message/SectionBlock/index.js +++ b/packages/livechat/src/components/uiKit/message/SectionBlock/index.tsx @@ -1,20 +1,27 @@ -import { BlockContext } from '@rocket.chat/ui-kit'; +import * as uikit from '@rocket.chat/ui-kit'; +import type { ComponentChild } from 'preact'; import { memo } from 'preact/compat'; import { createClassName } from '../../../../helpers/createClassName'; import Block from '../Block'; import styles from './styles.scss'; -const SectionBlock = ({ appId, blockId, text, fields, accessory, parser }) => ( +type SectionBlockProps = uikit.SectionBlock & { + parser: uikit.SurfaceRenderer; +}; + +const SectionBlock = ({ appId, blockId, text, fields, accessory, parser }: SectionBlockProps) => (
- {text &&
{parser.text(text, BlockContext.SECTION)}
} + {text && ( +
{parser.text(text, uikit.BlockContext.SECTION)}
+ )} {Array.isArray(fields) && fields.length > 0 && (
{fields.map((field, i) => (
- {parser.text(field, BlockContext.SECTION)} + {parser.text(field, uikit.BlockContext.SECTION)}
))}
@@ -22,7 +29,7 @@ const SectionBlock = ({ appId, blockId, text, fields, accessory, parser }) => (
{accessory && (
- {parser.renderAccessories(accessory, BlockContext.SECTION)} + {parser.renderAccessories(accessory, uikit.BlockContext.SECTION, undefined, 0)}
)}
diff --git a/packages/livechat/src/components/uiKit/message/StaticSelectElement/index.js b/packages/livechat/src/components/uiKit/message/StaticSelectElement/index.tsx similarity index 63% rename from packages/livechat/src/components/uiKit/message/StaticSelectElement/index.js rename to packages/livechat/src/components/uiKit/message/StaticSelectElement/index.tsx index a5fb80552afd1..7bf9931bc2d75 100644 --- a/packages/livechat/src/components/uiKit/message/StaticSelectElement/index.js +++ b/packages/livechat/src/components/uiKit/message/StaticSelectElement/index.tsx @@ -1,3 +1,6 @@ +import type * as uikit from '@rocket.chat/ui-kit'; +import type { ComponentChild } from 'preact'; +import type { TargetedEvent } from 'preact/compat'; import { memo, useCallback, useMemo } from 'preact/compat'; import { createClassName } from '../../../../helpers/createClassName'; @@ -5,11 +8,22 @@ import { SelectInput } from '../../../Form/SelectInput'; import { usePerformAction } from '../Block'; import styles from './styles.scss'; -const StaticSelectElement = ({ actionId, confirm, placeholder, options /* , optionGroups */, initialOption, parser }) => { +type StaticSelectElementProps = uikit.StaticSelectElement & { + parser: uikit.SurfaceRenderer; +}; + +const StaticSelectElement = ({ + actionId, + confirm, + placeholder, + options /* , optionGroups */, + initialOption, + parser, +}: StaticSelectElementProps) => { const [performAction, performingAction] = usePerformAction(actionId); const handleChange = useCallback( - async (event) => { + async (event: TargetedEvent) => { event.preventDefault(); if (confirm) { @@ -17,7 +31,7 @@ const StaticSelectElement = ({ actionId, confirm, placeholder, options /* , opti } await performAction({ - value: event.target.value, + value: event.currentTarget?.value, }); }, [confirm, performAction], @@ -39,7 +53,7 @@ const StaticSelectElement = ({ actionId, confirm, placeholder, options /* , opti options={selectOptions} placeholder={placeholder && parser.text(placeholder)} small - value={(initialOption && initialOption.value) || ''} + value={initialOption?.value ?? ''} onChange={handleChange} /> ); diff --git a/packages/livechat/src/components/uiKit/message/Surface.js b/packages/livechat/src/components/uiKit/message/Surface.js deleted file mode 100644 index fdc8d4955cf2d..0000000000000 --- a/packages/livechat/src/components/uiKit/message/Surface.js +++ /dev/null @@ -1,19 +0,0 @@ -import { createContext } from 'preact'; -import { memo, useContext } from 'preact/compat'; - -const SurfaceContext = createContext({ - dispatchAction: () => undefined, -}); - -const Surface = ({ children, dispatchAction }) => ( - -); - -export const useDispatchAction = () => useContext(SurfaceContext).dispatchAction; - -export default memo(Surface); diff --git a/packages/livechat/src/components/uiKit/message/Surface.tsx b/packages/livechat/src/components/uiKit/message/Surface.tsx new file mode 100644 index 0000000000000..2ca22907c502b --- /dev/null +++ b/packages/livechat/src/components/uiKit/message/Surface.tsx @@ -0,0 +1,29 @@ +import type { ComponentChildren } from 'preact'; +import { createContext } from 'preact'; +import { memo, useContext } from 'preact/compat'; + +type SurfaceContextValue = { + dispatchAction: (args: { appId: any; actionId: any; payload: any }) => void; +}; + +const SurfaceContext = createContext({ + dispatchAction: () => undefined, +}); + +type SurfaceProps = { + children: ComponentChildren; + dispatchAction: (action: any) => void; +}; + +const Surface = ({ children, dispatchAction }: SurfaceProps) => ( + +); + +export const useDispatchAction = () => useContext(SurfaceContext).dispatchAction; + +export default memo(Surface); diff --git a/packages/livechat/src/definitions/jpg.d.ts b/packages/livechat/src/definitions/jpg.d.ts new file mode 100644 index 0000000000000..51ef84b5f794c --- /dev/null +++ b/packages/livechat/src/definitions/jpg.d.ts @@ -0,0 +1,4 @@ +declare module '*.jpg' { + const path: string; + export = path; +} diff --git a/packages/livechat/src/definitions/mp3.d.ts b/packages/livechat/src/definitions/mp3.d.ts new file mode 100644 index 0000000000000..acb2a2495626d --- /dev/null +++ b/packages/livechat/src/definitions/mp3.d.ts @@ -0,0 +1,4 @@ +declare module '*.mp3' { + const path: string; + export = path; +} diff --git a/packages/livechat/src/definitions/mp4.d.ts b/packages/livechat/src/definitions/mp4.d.ts new file mode 100644 index 0000000000000..729c2e1232f8d --- /dev/null +++ b/packages/livechat/src/definitions/mp4.d.ts @@ -0,0 +1,4 @@ +declare module '*.mp4' { + const path: string; + export = path; +} diff --git a/packages/livechat/src/definitions/png.d.ts b/packages/livechat/src/definitions/png.d.ts new file mode 100644 index 0000000000000..d39b09460e7ad --- /dev/null +++ b/packages/livechat/src/definitions/png.d.ts @@ -0,0 +1,4 @@ +declare module '*.png' { + const path: string; + export = path; +} diff --git a/packages/livechat/src/helpers.stories.js b/packages/livechat/src/helpers.stories.js deleted file mode 100644 index 1a597d8a2e82e..0000000000000 --- a/packages/livechat/src/helpers.stories.js +++ /dev/null @@ -1,49 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { loremIpsum as originalLoremIpsum } from 'lorem-ipsum'; - -import gazzoAvatar from '../.storybook/assets/gazzo.jpg'; -import martinAvatar from '../.storybook/assets/martin.jpg'; -import tassoAvatar from '../.storybook/assets/tasso.jpg'; - -export const screenDecorator = (storyFn) =>
{storyFn()}
; - -export const screenProps = () => ({ - theme: { - color: '', - fontColor: '', - iconColor: '', - }, - notificationsEnabled: true, - minimized: false, - windowed: false, - onEnableNotifications: action('enableNotifications'), - onDisableNotifications: action('disableNotifications'), - onMinimize: action('minimize'), - onRestore: action('restore'), - onOpenWindow: action('openWindow'), -}); - -export const avatarResolver = (username) => - ({ - 'guilherme.gazzo': gazzoAvatar, - 'martin.schoeler': martinAvatar, - 'tasso.evangelista': tassoAvatar, - }[username]); - -export const attachmentResolver = (url) => url; - -const createRandom = (s) => () => { - s = Math.sin(s) * 10000; - return s - Math.floor(s); -}; -const loremIpsumRandom = createRandom(42); -export const loremIpsum = (options) => originalLoremIpsum({ random: loremIpsumRandom, ...options }); - -export { gazzoAvatar, martinAvatar, tassoAvatar }; - -export { default as sampleAudio } from '../.storybook/assets/sample-audio.mp3'; -export { default as sampleImage } from '../.storybook/assets/sample-image.jpg'; -export { default as sampleVideo } from '../.storybook/assets/sample-video.mp4'; -export { default as accessoryImage } from '../.storybook/assets/accessoryImage.png'; -export { default as imageBlock } from '../.storybook/assets/imageBlock.png'; -export { default as beepAudio } from '../.storybook/assets/beep.mp3'; diff --git a/packages/livechat/src/helpers/normalizeDOMRect.ts b/packages/livechat/src/helpers/normalizeDOMRect.ts index b9cd729e38269..2211f4904d597 100644 --- a/packages/livechat/src/helpers/normalizeDOMRect.ts +++ b/packages/livechat/src/helpers/normalizeDOMRect.ts @@ -1,6 +1,13 @@ -export const normalizeDOMRect = ({ left, top, right, bottom }: DOMRect) => ({ - left, - top, - right, - bottom, -}); +export const normalizeDOMRect = (rect: DOMRect | undefined) => { + if (!rect) { + throw new Error('DOMRect is not defined'); + } + + const { left, top, right, bottom } = rect; + return { + left, + top, + right, + bottom, + }; +}; diff --git a/packages/livechat/src/lib/api.js b/packages/livechat/src/lib/api.ts similarity index 68% rename from packages/livechat/src/lib/api.js rename to packages/livechat/src/lib/api.ts index ac7df77072b47..84cf63a97ce36 100644 --- a/packages/livechat/src/lib/api.js +++ b/packages/livechat/src/lib/api.ts @@ -1,10 +1,12 @@ +import type { IOmnichannelAgent } from '@rocket.chat/core-typings'; import i18next from 'i18next'; import { getDateFnsLocale } from './locale'; -export const normalizeAgent = (agentData) => agentData && { name: agentData.name, username: agentData.username, status: agentData.status }; +export const normalizeAgent = (agentData: IOmnichannelAgent) => + agentData && { name: agentData.name, username: agentData.username, status: agentData.status }; -export const normalizeQueueAlert = async (queueInfo) => { +export const normalizeQueueAlert = async (queueInfo: any) => { if (!queueInfo) { return; } diff --git a/packages/livechat/src/lib/commands.js b/packages/livechat/src/lib/commands.ts similarity index 100% rename from packages/livechat/src/lib/commands.js rename to packages/livechat/src/lib/commands.ts diff --git a/packages/livechat/src/lib/connection.js b/packages/livechat/src/lib/connection.ts similarity index 72% rename from packages/livechat/src/lib/connection.js rename to packages/livechat/src/lib/connection.ts index 0bd850d6d83e2..34bcf77541dd6 100644 --- a/packages/livechat/src/lib/connection.js +++ b/packages/livechat/src/lib/connection.ts @@ -6,12 +6,11 @@ import constants from './constants'; import { loadConfig } from './main'; import { loadMessages } from './room'; -let self; -let connectedListener; -let disconnectedListener; +let connectedListener: Promise<() => void> | false; +let disconnectedListener: Promise<() => void> | false; let initiated = false; const { livechatDisconnectedAlertId, livechatConnectedAlertId } = constants; -const removeListener = (l) => l.stop(); +const removeListener = (l: any) => l.stop(); const Connection = { async init() { @@ -20,7 +19,6 @@ const Connection = { } initiated = true; - self = this; await this.connect(); }, @@ -65,24 +63,29 @@ const Connection = { }, async handleConnected() { - await self.clearAlerts(); - await self.displayAlert({ id: livechatConnectedAlertId, children: i18next.t('livechat_connected'), success: true }); + await Connection.clearAlerts(); + await Connection.displayAlert({ id: livechatConnectedAlertId, children: i18next.t('livechat_connected'), success: true }); await loadMessages(); }, async handleDisconnected() { - await self.clearAlerts(); - await self.displayAlert({ id: livechatDisconnectedAlertId, children: i18next.t('livechat_is_not_connected'), error: true, timeout: 0 }); + await Connection.clearAlerts(); + await Connection.displayAlert({ + id: livechatDisconnectedAlertId, + children: i18next.t('livechat_is_not_connected'), + error: true, + timeout: 0, + }); // self.reconnect(); }, addListeners() { if (!connectedListener) { - connectedListener = Livechat.connection.on('connected', this.handleConnected); + connectedListener = Promise.resolve(Livechat.connection.on('connected', this.handleConnected)); } if (!disconnectedListener) { - disconnectedListener = Livechat.connection.on('disconnected', this.handleDisconnected); + disconnectedListener = Promise.resolve(Livechat.connection.on('disconnected', this.handleDisconnected)); } }, diff --git a/packages/livechat/src/lib/constants.js b/packages/livechat/src/lib/constants.ts similarity index 96% rename from packages/livechat/src/lib/constants.js rename to packages/livechat/src/lib/constants.ts index 6c968a707f3e7..780228b73882c 100644 --- a/packages/livechat/src/lib/constants.js +++ b/packages/livechat/src/lib/constants.ts @@ -5,4 +5,4 @@ export default { livechatDisconnectedAlertId: 'LIVECHAT_DISCONNECTED', livechatQueueMessageId: 'LIVECHAT_QUEUE_MESSAGE', webRTCCallStartedMessageType: 'livechat_webrtc_video_call', -}; +} as const; diff --git a/packages/livechat/src/components/Emoji/ascii.ts b/packages/livechat/src/lib/emoji/ascii.ts similarity index 100% rename from packages/livechat/src/components/Emoji/ascii.ts rename to packages/livechat/src/lib/emoji/ascii.ts diff --git a/packages/livechat/src/components/Emoji/emojis.ts b/packages/livechat/src/lib/emoji/emojis.ts similarity index 100% rename from packages/livechat/src/components/Emoji/emojis.ts rename to packages/livechat/src/lib/emoji/emojis.ts diff --git a/packages/livechat/src/components/Emoji/isBigEmoji.ts b/packages/livechat/src/lib/emoji/isBigEmoji.ts similarity index 100% rename from packages/livechat/src/components/Emoji/isBigEmoji.ts rename to packages/livechat/src/lib/emoji/isBigEmoji.ts diff --git a/packages/livechat/src/components/Emoji/shortnameToUnicode.ts b/packages/livechat/src/lib/emoji/shortnameToUnicode.ts similarity index 100% rename from packages/livechat/src/components/Emoji/shortnameToUnicode.ts rename to packages/livechat/src/lib/emoji/shortnameToUnicode.ts diff --git a/packages/livechat/src/routes/Chat/stories.tsx b/packages/livechat/src/routes/Chat/stories.tsx index f2aa1a4f6c802..2c91e6b5c1320 100644 --- a/packages/livechat/src/routes/Chat/stories.tsx +++ b/packages/livechat/src/routes/Chat/stories.tsx @@ -2,7 +2,7 @@ import { action } from '@storybook/addon-actions'; import type { Meta, Story } from '@storybook/preact'; import type { ComponentProps } from 'preact'; -import { screenProps, avatarResolver, beepAudio, screenDecorator } from '../../helpers.stories'; +import { screenProps, avatarResolver, beepAudio, screenDecorator } from '../../../.storybook/helpers'; import Chat from './component'; const now = new Date(Date.parse('2021-01-01T00:00:00.000Z')); diff --git a/packages/livechat/src/routes/ChatFinished/stories.tsx b/packages/livechat/src/routes/ChatFinished/stories.tsx index 4479ee57d99df..6a4cc0f2b259b 100644 --- a/packages/livechat/src/routes/ChatFinished/stories.tsx +++ b/packages/livechat/src/routes/ChatFinished/stories.tsx @@ -2,7 +2,7 @@ import { action } from '@storybook/addon-actions'; import type { Meta, Story } from '@storybook/preact'; import type { ComponentProps } from 'preact'; -import { screenProps, loremIpsum, screenDecorator } from '../../helpers.stories'; +import { screenProps, loremIpsum, screenDecorator } from '../../../.storybook/helpers'; import ChatFinished from './component'; export default { diff --git a/packages/livechat/src/routes/GDPRAgreement/stories.tsx b/packages/livechat/src/routes/GDPRAgreement/stories.tsx index cc764b888441d..fb0139cd2e53e 100644 --- a/packages/livechat/src/routes/GDPRAgreement/stories.tsx +++ b/packages/livechat/src/routes/GDPRAgreement/stories.tsx @@ -2,7 +2,7 @@ import { action } from '@storybook/addon-actions'; import type { Meta, Story } from '@storybook/preact'; import type { ComponentProps } from 'preact'; -import { screenDecorator, screenProps } from '../../helpers.stories'; +import { screenDecorator, screenProps } from '../../../.storybook/helpers'; import GDPRAgreement from './component'; export default { diff --git a/packages/livechat/src/routes/LeaveMessage/stories.tsx b/packages/livechat/src/routes/LeaveMessage/stories.tsx index 7560a8e1f7b54..61c2a3b803b06 100644 --- a/packages/livechat/src/routes/LeaveMessage/stories.tsx +++ b/packages/livechat/src/routes/LeaveMessage/stories.tsx @@ -1,7 +1,7 @@ import type { Meta, Story } from '@storybook/preact'; import type { ComponentProps } from 'preact'; -import { screenDecorator } from '../../helpers.stories'; +import { screenDecorator } from '../../../.storybook/helpers'; import LeaveMessage from './index'; export default { diff --git a/packages/livechat/src/routes/Register/stories.tsx b/packages/livechat/src/routes/Register/stories.tsx index 1e7170c9f189b..09cedd1958c62 100644 --- a/packages/livechat/src/routes/Register/stories.tsx +++ b/packages/livechat/src/routes/Register/stories.tsx @@ -2,7 +2,7 @@ import type { Meta, Story } from '@storybook/preact'; import type { ComponentProps } from 'preact'; import Register from '.'; -import { screenDecorator, screenProps } from '../../helpers.stories'; +import { screenDecorator, screenProps } from '../../../.storybook/helpers'; export default { title: 'Routes/Register', diff --git a/packages/livechat/src/routes/SwitchDepartment/stories.tsx b/packages/livechat/src/routes/SwitchDepartment/stories.tsx index 7c30604644afe..37405c97bbc7f 100644 --- a/packages/livechat/src/routes/SwitchDepartment/stories.tsx +++ b/packages/livechat/src/routes/SwitchDepartment/stories.tsx @@ -2,7 +2,7 @@ import { action } from '@storybook/addon-actions'; import type { Meta, Story } from '@storybook/preact'; import type { ComponentProps } from 'preact'; -import { screenDecorator, screenProps } from '../../helpers.stories'; +import { screenDecorator, screenProps } from '../../../.storybook/helpers'; import SwitchDepartment from './index'; export default { diff --git a/packages/livechat/src/routes/TriggerMessage/stories.tsx b/packages/livechat/src/routes/TriggerMessage/stories.tsx index b530461a9d51b..8ee5291e8e44b 100644 --- a/packages/livechat/src/routes/TriggerMessage/stories.tsx +++ b/packages/livechat/src/routes/TriggerMessage/stories.tsx @@ -2,7 +2,7 @@ import { action } from '@storybook/addon-actions'; import type { Meta, Story } from '@storybook/preact'; import type { ComponentProps } from 'preact'; -import { screenDecorator, screenProps } from '../../helpers.stories'; +import { screenDecorator, screenProps } from '../../../.storybook/helpers'; import TriggerMessage from './component'; const now = new Date(Date.parse('2021-01-01T00:00:00.000Z')); diff --git a/packages/livechat/src/store/index.tsx b/packages/livechat/src/store/index.tsx index d682d95b34c12..48e3e1df3dddc 100644 --- a/packages/livechat/src/store/index.tsx +++ b/packages/livechat/src/store/index.tsx @@ -66,6 +66,7 @@ type StoreState = { lastReadMessageId?: any; triggerAgent?: any; queueInfo?: any; + connecting?: boolean; }; export const initialState = (): StoreState => ({ diff --git a/packages/livechat/tsconfig.json b/packages/livechat/tsconfig.json index 0abc3fefa0131..a276085c9a61a 100644 --- a/packages/livechat/tsconfig.json +++ b/packages/livechat/tsconfig.json @@ -1,12 +1,13 @@ { "extends": "../../tsconfig.base.client.json", "compilerOptions": { + "module": "CommonJS", "outDir": "./dist", "allowJs": true, "checkJs": false, "jsxImportSource": "preact", }, - "include": ["./src", "./webpack.config.ts", "./svg-component-loader.ts"], + "include": ["./src", "./webpack.config.ts", "./svg-component-loader.ts", ".storybook/**/*.ts"], "exclude": [ "./node_modules", "./dist" diff --git a/packages/livechat/webpack.config.ts b/packages/livechat/webpack.config.ts index 361077be2a684..24bd213a43866 100644 --- a/packages/livechat/webpack.config.ts +++ b/packages/livechat/webpack.config.ts @@ -162,12 +162,12 @@ const config = (_env: any, args: webpack.WebpackOptionsNormalized): webpack.Conf { ...common(args), entry: { - script: _('./src/widget.js'), + 'rocketchat-livechat.min': _('./src/widget.js'), } as webpack.Entry, output: { path: _('./dist'), publicPath: args.mode === 'production' ? 'livechat/' : '/', - filename: 'rocketchat-livechat.min.js', + filename: '[name].js', }, module: { rules: [ diff --git a/yarn.lock b/yarn.lock index 5090e9c4cb5c1..bcb5af86810d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9594,6 +9594,7 @@ __metadata: sass: ~1.62.1 sass-loader: ~10.4.1 serve: ^11.3.2 + storybook-dark-mode: ~3.0.1 style-loader: ^1.2.1 stylelint: ^14.9.1 stylelint-order: ^5.0.0