diff --git a/dailyjs/flying-emojis/.babelrc b/dailyjs/flying-emojis/.babelrc
new file mode 100644
index 0000000..a6f4434
--- /dev/null
+++ b/dailyjs/flying-emojis/.babelrc
@@ -0,0 +1,4 @@
+{
+ "presets": ["next/babel"],
+ "plugins": ["inline-react-svg"]
+}
diff --git a/dailyjs/flying-emojis/README.md b/dailyjs/flying-emojis/README.md
new file mode 100644
index 0000000..6703492
--- /dev/null
+++ b/dailyjs/flying-emojis/README.md
@@ -0,0 +1,41 @@
+# Text Chat
+
+
+
+### Live example
+
+**[See it in action here ➡️](https://dailyjs-text-chat.vercel.app)**
+
+---
+
+## What does this demo do?
+
+- Use [sendAppMessage](https://docs.daily.co/reference#%EF%B8%8F-sendappmessage) to send messages
+- Listen for incoming messages using the call object `app-message` event
+- Extend the basic call demo with a chat provider and aside
+- Show a notification bubble on chat tray button when a new message is received
+- Demonstrate how to play a sound whenever a message is received
+
+Please note: this demo is not currently mobile optimised
+
+### Getting started
+
+```
+# set both DAILY_API_KEY and DAILY_DOMAIN
+mv env.example .env.local
+
+yarn
+yarn workspace @dailyjs/text-chat dev
+```
+
+## How does this example work?
+
+In this example we extend the [basic call demo](../basic-call) with the ability to send chat messages.
+
+We pass a custom tray object, a custom app object (wrapping the original in a new `ChatProvider`) as well as add our `ChatAside` panel. We also symlink both the `public` and `pages/api` folders from the basic call.
+
+In a real world use case you would likely want to implement serverside logic so that participants joining a call can retrieve previously sent messages. This round trip could be done inside of the Chat context.
+
+## Deploy your own on Vercel
+
+[](https://vercel.com/new/daily-co/clone-flow?repository-url=https%3A%2F%2Fgithub.com%2Fdaily-demos%2Fexamples.git&env=DAILY_DOMAIN%2CDAILY_API_KEY&envDescription=Your%20Daily%20domain%20and%20API%20key%20can%20be%20found%20on%20your%20account%20dashboard&envLink=https%3A%2F%2Fdashboard.daily.co&project-name=daily-examples&repo-name=daily-examples)
diff --git a/dailyjs/flying-emojis/components/App/App.js b/dailyjs/flying-emojis/components/App/App.js
new file mode 100644
index 0000000..e006832
--- /dev/null
+++ b/dailyjs/flying-emojis/components/App/App.js
@@ -0,0 +1,12 @@
+import React from 'react';
+import App from '@dailyjs/basic-call/components/App';
+import FlyingEmojiOverlay from '../FlyingEmojis/FlyingEmojisOverlay';
+
+export const AppWithEmojis = () => (
+ <>
+
+
+ >
+);
+
+export default AppWithEmojis;
diff --git a/dailyjs/flying-emojis/components/App/index.js b/dailyjs/flying-emojis/components/App/index.js
new file mode 100644
index 0000000..8887a4b
--- /dev/null
+++ b/dailyjs/flying-emojis/components/App/index.js
@@ -0,0 +1 @@
+export { AppWithEmojis as default } from './App';
diff --git a/dailyjs/flying-emojis/components/FlyingEmojis/FlyingEmojisOverlay.js b/dailyjs/flying-emojis/components/FlyingEmojis/FlyingEmojisOverlay.js
new file mode 100644
index 0000000..8060e59
--- /dev/null
+++ b/dailyjs/flying-emojis/components/FlyingEmojis/FlyingEmojisOverlay.js
@@ -0,0 +1,153 @@
+import React, { useEffect, useRef, useCallback } from 'react';
+import { useCallState } from '@dailyjs/shared/contexts/CallProvider';
+
+const EMOJI_MAP = {
+ fire: '🔥',
+ squid: '🦑',
+};
+
+export const FlyingEmojisOverlay = () => {
+ const { callObject } = useCallState();
+ const overlayRef = useRef();
+
+ // -- Handlers
+
+ const handleRemoveFlyingEmoji = useCallback((node) => {
+ if (!overlayRef.current) return;
+ overlayRef.current.removeChild(node);
+ }, []);
+
+ const handleNewFlyingEmoji = useCallback(
+ (emoji) => {
+ if (!overlayRef.current) {
+ return;
+ }
+
+ console.log(`⭐ New flying emoji: ${emoji}`);
+
+ const node = document.createElement('div');
+ node.appendChild(document.createTextNode(EMOJI_MAP[emoji]));
+ node.className =
+ Math.random() * 1 > 0.5 ? 'emoji wiggle-1' : 'emoji wiggle-2';
+ node.style.transform = `rotate(${-30 + Math.random() * 60}deg)`;
+ node.style.left = `${Math.random() * 100}%`;
+ node.src = '';
+ overlayRef.current.appendChild(node);
+
+ node.addEventListener('animationend', (e) =>
+ handleRemoveFlyingEmoji(e.target)
+ );
+ },
+ [handleRemoveFlyingEmoji]
+ );
+
+ // -- Effects
+
+ // Listen for new app messages and show new flying emojis
+ useEffect(() => {
+ if (!callObject) {
+ return false;
+ }
+
+ console.log(`⭐ Listening for flying emojis...`);
+
+ callObject.on('app-message', handleNewFlyingEmoji);
+
+ return () => callObject.off('app-message', handleNewFlyingEmoji);
+ }, [callObject, handleNewFlyingEmoji]);
+
+ // Listen to window events to show local user emojis
+ useEffect(() => {
+ if (!callObject) {
+ return false;
+ }
+
+ function handleIncomingEmoji(e) {
+ const { emoji } = e.detail;
+ console.log(`⭐ Sending flying emoji: ${emoji}`);
+
+ if (emoji) {
+ callObject.sendAppMessage({ emoji }, '*');
+ handleNewFlyingEmoji(emoji);
+ }
+ }
+
+ window.addEventListener('reaction_added', handleIncomingEmoji);
+ return () =>
+ window.removeEventListener('reaction_added', handleIncomingEmoji);
+ }, [callObject, handleNewFlyingEmoji]);
+
+ // Remove all event listeners on unmount to prevent console warnings
+ useEffect(
+ () => () =>
+ overlayRef.current.childNodes.forEach((n) =>
+ n.removeEventListener('animationend', handleRemoveFlyingEmoji)
+ ),
+ [handleRemoveFlyingEmoji]
+ );
+
+ return (
+
+
+
+ );
+};
+
+export default FlyingEmojisOverlay;
diff --git a/dailyjs/flying-emojis/components/FlyingEmojis/index.js b/dailyjs/flying-emojis/components/FlyingEmojis/index.js
new file mode 100644
index 0000000..6e98cc3
--- /dev/null
+++ b/dailyjs/flying-emojis/components/FlyingEmojis/index.js
@@ -0,0 +1,2 @@
+export { FlyingEmojisOverlay } from './FlyingEmojisOverlay';
+export { FlyingEmojisOverlay as default } from './FlyingEmojisOverlay';
diff --git a/dailyjs/flying-emojis/components/Tray/Tray.js b/dailyjs/flying-emojis/components/Tray/Tray.js
new file mode 100644
index 0000000..7b0edda
--- /dev/null
+++ b/dailyjs/flying-emojis/components/Tray/Tray.js
@@ -0,0 +1,44 @@
+import React, { useState } from 'react';
+
+import Button from '@dailyjs/shared/components/Button';
+import { TrayButton } from '@dailyjs/shared/components/Tray';
+import { ReactComponent as IconStar } from '@dailyjs/shared/icons/star-md.svg';
+
+export const Tray = () => {
+ const [showEmojis, setShowEmojis] = useState(false);
+
+ function sendEmoji(emoji) {
+ // Dispatch custom event here so the local user can see their own emoji
+ window.dispatchEvent(
+ new CustomEvent('reaction_added', { detail: { emoji } })
+ );
+
+ setShowEmojis(false);
+ }
+
+ return (
+
+ {showEmojis && (
+
+
+
+
+ )}
+
setShowEmojis(!showEmojis)}>
+
+
+
+
+ );
+};
+
+export default Tray;
diff --git a/dailyjs/flying-emojis/components/Tray/index.js b/dailyjs/flying-emojis/components/Tray/index.js
new file mode 100644
index 0000000..100bcc8
--- /dev/null
+++ b/dailyjs/flying-emojis/components/Tray/index.js
@@ -0,0 +1 @@
+export { Tray as default } from './Tray';
diff --git a/dailyjs/flying-emojis/image.png b/dailyjs/flying-emojis/image.png
new file mode 100644
index 0000000..cca9672
Binary files /dev/null and b/dailyjs/flying-emojis/image.png differ
diff --git a/dailyjs/flying-emojis/next.config.js b/dailyjs/flying-emojis/next.config.js
new file mode 100644
index 0000000..9a0a6ee
--- /dev/null
+++ b/dailyjs/flying-emojis/next.config.js
@@ -0,0 +1,13 @@
+const withPlugins = require('next-compose-plugins');
+const withTM = require('next-transpile-modules')([
+ '@dailyjs/shared',
+ '@dailyjs/basic-call',
+]);
+
+const packageJson = require('./package.json');
+
+module.exports = withPlugins([withTM], {
+ env: {
+ PROJECT_TITLE: packageJson.description,
+ },
+});
diff --git a/dailyjs/flying-emojis/package.json b/dailyjs/flying-emojis/package.json
new file mode 100644
index 0000000..a084b01
--- /dev/null
+++ b/dailyjs/flying-emojis/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "@dailyjs/flying-emojis",
+ "description": "Basic Call + Flying Emojis",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "@dailyjs/basic-call": "*",
+ "@dailyjs/shared": "*",
+ "next": "^11.0.0",
+ "pluralize": "^8.0.0",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2"
+ },
+ "devDependencies": {
+ "babel-plugin-module-resolver": "^4.1.0",
+ "next-compose-plugins": "^2.2.1",
+ "next-transpile-modules": "^8.0.0"
+ }
+}
diff --git a/dailyjs/flying-emojis/pages/_app.js b/dailyjs/flying-emojis/pages/_app.js
new file mode 100644
index 0000000..16a9742
--- /dev/null
+++ b/dailyjs/flying-emojis/pages/_app.js
@@ -0,0 +1,9 @@
+import React from 'react';
+import App from '@dailyjs/basic-call/pages/_app';
+import AppWithEmojis from '../components/App';
+import Tray from '../components/Tray';
+
+App.customAppComponent = ;
+App.customTrayComponent = ;
+
+export default App;
diff --git a/dailyjs/flying-emojis/pages/api b/dailyjs/flying-emojis/pages/api
new file mode 120000
index 0000000..999f604
--- /dev/null
+++ b/dailyjs/flying-emojis/pages/api
@@ -0,0 +1 @@
+../../basic-call/pages/api
\ No newline at end of file
diff --git a/dailyjs/flying-emojis/pages/index.js b/dailyjs/flying-emojis/pages/index.js
new file mode 100644
index 0000000..d25e77e
--- /dev/null
+++ b/dailyjs/flying-emojis/pages/index.js
@@ -0,0 +1,13 @@
+import Index from '@dailyjs/basic-call/pages';
+import getDemoProps from '@dailyjs/shared/lib/demoProps';
+
+export async function getStaticProps() {
+ const defaultProps = getDemoProps();
+
+ // Pass through domain as prop
+ return {
+ props: defaultProps,
+ };
+}
+
+export default Index;
diff --git a/dailyjs/flying-emojis/public b/dailyjs/flying-emojis/public
new file mode 120000
index 0000000..33a6e67
--- /dev/null
+++ b/dailyjs/flying-emojis/public
@@ -0,0 +1 @@
+../basic-call/public
\ No newline at end of file
diff --git a/dailyjs/shared/icons/star-md.svg b/dailyjs/shared/icons/star-md.svg
new file mode 100644
index 0000000..68c299b
--- /dev/null
+++ b/dailyjs/shared/icons/star-md.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index deb3d12..6c4dc2f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -264,6 +264,11 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
+"@popperjs/core@^2.9.2":
+ version "2.9.2"
+ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353"
+ integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==
+
"@rushstack/eslint-patch@^1.0.6":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.0.6.tgz#023d72a5c4531b4ce204528971700a78a85a0c50"
@@ -2204,7 +2209,7 @@ lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
-loose-envify@^1.1.0, loose-envify@^1.4.0:
+loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -2897,6 +2902,11 @@ react-dom@^17.0.2:
object-assign "^4.1.1"
scheduler "^0.20.2"
+react-fast-compare@^3.0.1:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
+ integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
+
react-is@17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
@@ -2907,6 +2917,14 @@ react-is@^16.8.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
+react-popper@^2.2.5:
+ version "2.2.5"
+ resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.2.5.tgz#1214ef3cec86330a171671a4fbcbeeb65ee58e96"
+ integrity sha512-kxGkS80eQGtLl18+uig1UIf9MKixFSyPxglsgLBxlYnyDf65BiY9B3nZSc6C9XUNDgStROB0fMQlTEz1KxGddw==
+ dependencies:
+ react-fast-compare "^3.0.1"
+ warning "^4.0.2"
+
react-refresh@0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
@@ -3616,6 +3634,13 @@ vm-browserify@1.1.2, vm-browserify@^1.0.1:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
+warning@^4.0.2:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
+ integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
+ dependencies:
+ loose-envify "^1.0.0"
+
watchpack@2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.1.1.tgz#e99630550fca07df9f90a06056987baa40a689c7"