+ `;
+
+const Context = createContext()
+
+export const ChatMenu = (props) => {
+ const { username, isModerator, chatDisabled, noVideoContent, handleChatPanelToggle, onUsernameChange, onFocus, onBlur } = props
+
+ const [chatMenuOpen, setChatMenuOpen] = useState(false);
+ const [view, setView] = useState('main')
+
+ const chatMenuRef = useRef(undefined)
+ closeOnOutsideClick(chatMenuRef, setChatMenuOpen)
+
+ useEffect(() => {
+ if (chatMenuOpen) setView('main')
+ }, [chatMenuOpen])
+
+ return html`
+ <${Context.Provider} value=${props}>
+
+
+ ${chatMenuOpen && html`
+
+ ${view === "main" &&
+ html``}
+
+ ${view != "main" && html`<${SubMenuView} view=${view} setView=${setView} />`}
+
`}
+
+ ${Context.Provider}>`
+};
+
+const SubMenuView = ({ view, setView }) => {
+ return html`
+
+
+ -
+
+
+
+ ${view === 'change_username' && html`<${ChangeUsernameView} />`}
+
+ `
+}
+
+function closeOnOutsideClick(ref, setter) {
+ useEffect(() => {
+ function handleClickOutside(event) {
+ if (ref.current && !ref.current.contains(event.target)) {
+ setter(undefined)
+ }
+ }
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => {
+ document.removeEventListener("mousedown", handleClickOutside);
+ };
+ }, [ref]);
+}
diff --git a/webroot/js/components/chat/username.js b/webroot/js/components/chat/username.js
index 536b3cfbf..271ceb96b 100644
--- a/webroot/js/components/chat/username.js
+++ b/webroot/js/components/chat/username.js
@@ -8,6 +8,8 @@ import {
KEY_CUSTOM_USERNAME_SET,
} from '../../utils/constants.js';
+import { CheckIcon, CloseIcon, EditIcon } from '../icons/index.js'
+
export default class UsernameForm extends Component {
constructor(props, context) {
super(props, context);
@@ -62,6 +64,8 @@ export default class UsernameForm extends Component {
onUsernameChange(newName);
}
this.handleHideForm();
+ } else {
+ this.handleHideForm();
}
}
@@ -98,65 +102,54 @@ export default class UsernameForm extends Component {
},
};
- const moderatorFlag = html`
-
- `;
- const userIcon = html`
-
- `;
-
return html`
-
-
+
+
Change Username
+
<${EditIcon} />
+
-
+
+
-
+
+
`;
diff --git a/webroot/js/components/icons/CaretDownIcon.js b/webroot/js/components/icons/CaretDownIcon.js
new file mode 100644
index 000000000..fdab9972e
--- /dev/null
+++ b/webroot/js/components/icons/CaretDownIcon.js
@@ -0,0 +1,13 @@
+import { h } from '/js/web_modules/preact.js';
+import htm from '/js/web_modules/htm.js';
+const html = htm.bind(h);
+
+export const CaretDownIcon = ({ className = "w-6 h-6"}) => {
+ return html`
`
+}
diff --git a/webroot/js/components/icons/ChatIcon.js b/webroot/js/components/icons/ChatIcon.js
new file mode 100644
index 000000000..268b111ef
--- /dev/null
+++ b/webroot/js/components/icons/ChatIcon.js
@@ -0,0 +1,15 @@
+import { h } from '/js/web_modules/preact.js';
+import htm from '/js/web_modules/htm.js';
+const html = htm.bind(h);
+
+export const ChatIcon = ({ className = 'w-6 h-6' }) => {
+ return html`
+
+ `;
+};
diff --git a/webroot/js/components/icons/CheckIcon.js b/webroot/js/components/icons/CheckIcon.js
new file mode 100644
index 000000000..34c13bb1d
--- /dev/null
+++ b/webroot/js/components/icons/CheckIcon.js
@@ -0,0 +1,14 @@
+import { h } from '/js/web_modules/preact.js';
+import htm from '/js/web_modules/htm.js';
+const html = htm.bind(h);
+
+export const CheckIcon = ({ className = 'w-6 h-6' }) => {
+ return html`
`
+}
diff --git a/webroot/js/components/icons/CloseIcon.js b/webroot/js/components/icons/CloseIcon.js
new file mode 100644
index 000000000..faf56012e
--- /dev/null
+++ b/webroot/js/components/icons/CloseIcon.js
@@ -0,0 +1,14 @@
+
+import { h } from '/js/web_modules/preact.js';
+import htm from '/js/web_modules/htm.js';
+const html = htm.bind(h);
+
+export const CloseIcon = ({ className = 'w-6 h-6' }) => {
+ return html`
`
+}
diff --git a/webroot/js/components/icons/EditIcon.js b/webroot/js/components/icons/EditIcon.js
new file mode 100644
index 000000000..7c138880d
--- /dev/null
+++ b/webroot/js/components/icons/EditIcon.js
@@ -0,0 +1,14 @@
+
+import { h } from '/js/web_modules/preact.js';
+import htm from '/js/web_modules/htm.js';
+const html = htm.bind(h);
+
+export const EditIcon = ({ className = 'w-6 h-6' }) => {
+ return html`
`
+}
diff --git a/webroot/js/components/icons/UserIcon.js b/webroot/js/components/icons/UserIcon.js
new file mode 100644
index 000000000..b83faaad2
--- /dev/null
+++ b/webroot/js/components/icons/UserIcon.js
@@ -0,0 +1,23 @@
+
+import { h } from '/js/web_modules/preact.js';
+import htm from '/js/web_modules/htm.js';
+const html = htm.bind(h);
+
+export const UserIcon = ({ className }) => {
+ return html`
+
+ `;
+};
diff --git a/webroot/js/components/icons/index.js b/webroot/js/components/icons/index.js
new file mode 100644
index 000000000..0855194e9
--- /dev/null
+++ b/webroot/js/components/icons/index.js
@@ -0,0 +1,6 @@
+export { ChatIcon } from "./ChatIcon.js"
+export { UserIcon } from "./UserIcon.js"
+export { EditIcon } from "./EditIcon.js"
+export { CheckIcon } from "./CheckIcon.js"
+export { CloseIcon } from "./CloseIcon.js"
+export { CaretDownIcon } from "./CaretDownIcon.js"
diff --git a/webroot/styles/app.css b/webroot/styles/app.css
index 94863d8bf..e1835af61 100644
--- a/webroot/styles/app.css
+++ b/webroot/styles/app.css
@@ -78,12 +78,33 @@ header {
background-image: url(/img/logo.svg);
}
+.chat-menu-options li button {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ align-items: center;
+ justify-content: space-between;
+ border-radius: .25rem;
+ padding: .5rem .3rem;
+ color: #dbdbdb;
+ animation: fadeIn 150ms;
+}
+
+.chat-view li button {
+ animation: slideInRight 150ms;
+}
+
+.chat-menu-options li button:hover {
+ background-color: var(--owncast-purple);
+}
+
+
#chat-toggle {
min-width: 3rem;
}
#user-info-change {
- display: none;
+ background-color: transparent;
}
.external-action-button {
@@ -144,10 +165,6 @@ header {
justify-content: center;
}
-.chat-hidden #chat-toggle {
- opacity: 0.75;
-}
-
/* hide chat by default */
#chat-container-wrap {
display: none;
@@ -309,13 +326,8 @@ header {
/* ************************************************ */
@media screen and (max-width: 600px) {
- #user-info-change {
- width: 75vw;
- }
#user-info-display {
- max-width: 30vw;
- overflow: hidden;
- text-overflow: ellipsis;
+ max-width: none;
}
}
@@ -395,6 +407,30 @@ header {
/**************************
Modal Animation Style
**************************/
+.fadeIn {
+ animation: mmfadeIn 150ms forwards;
+}
+
+.fadeOut {
+ animation: mmfadeOut 150ms forwards;
+}
+
+.slideInTop {
+ animation: mmslideIn 150ms forwards;
+}
+
+.slideOutBottom {
+ animation: mmslideOut 150ms forwards;
+}
+
+.slideInLeft {
+ animation: mmslideInLeft 150ms forwards;
+}
+
+.slideInRight {
+ animation: mmslideInRight 150ms forwards;
+}
+
@keyframes mmfadeIn {
from {
@@ -414,6 +450,25 @@ header {
}
}
+
+@keyframes mmslideInRight {
+ from {
+ transform: translateX(15%);
+ }
+ to {
+ transform: translateX(0);
+ }
+}
+
+@keyframes mmslideInLeft {
+ from {
+ transform: translateX(-15%);
+ }
+ to {
+ transform: translateX(0);
+ }
+}
+
@keyframes mmslideIn {
from {
transform: translateY(15%);
@@ -578,21 +633,10 @@ header {
bottom: calc(100% + 0.5rem);
opacity: 0;
transform: translateY(0%);
- animation: fade-in 300ms forwards;
+ animation: mmfadeIn 300ms forwards;
animation-delay: 1s;
}
-@keyframes fade-in {
- from {
- opacity: 0;
- transform: translateY(-20%);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
#notify-button-container .external-action-icon {
margin: 0.25em 0.5em 0.25em 0.5em;
}