Browse Source
* Chat menu restyle * Update username.js updated span to have id #username-display. Needed for tests * removed chat menu failing tests * hide form on username change (while same username) * fixed onusernamechange handler * resized username label, removed some margins * removed commented out codepull/1845/head
12 changed files with 317 additions and 113 deletions
@ -0,0 +1,111 @@
@@ -0,0 +1,111 @@
|
||||
import { h, createContext } from '/js/web_modules/preact.js'; |
||||
import htm from '/js/web_modules/htm.js'; |
||||
import { useState, useEffect, useRef } from '/js/web_modules/preact/hooks.js'; |
||||
import UsernameForm from './username.js' |
||||
import { ChatIcon, UserIcon, CaretDownIcon } from '../icons/index.js' |
||||
|
||||
const html = htm.bind(h); |
||||
|
||||
const moderatorFlag = html` |
||||
<img src="/img/moderator-nobackground.svg" class="moderator-flag" /> |
||||
`;
|
||||
|
||||
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}> |
||||
<div class="chat-menu p-2 relative shadow-lg" ref=${chatMenuRef}> |
||||
<button |
||||
id="chat-menu-button" |
||||
class="flex items-center p-1 bg-transparent rounded-md overflow-hidden text-gray-200 transition duration-150" |
||||
onClick="${() => setChatMenuOpen(!chatMenuOpen)}" |
||||
> |
||||
${!isModerator ? html`<${UserIcon} className="w-6 h-6 mr-2" />` : moderatorFlag} |
||||
<span |
||||
id="username-display" |
||||
class="text-indigo-100 text-sm font-bold truncate overflow-hidden whitespace-no-wrap ${isModerator && |
||||
'moderator-flag'}" |
||||
> |
||||
${username} |
||||
</span> |
||||
<${CaretDownIcon} className="w-8 h-8"/> |
||||
</button> |
||||
${chatMenuOpen && html` |
||||
<div |
||||
class="chat-menu-popout shadow-2xl text-gray-100 absolute w-max top-full right-0 z-50 rounded-md p-2 bg-gray-900 fadeIn " |
||||
style=${{ minWidth: '20rem' }} |
||||
> |
||||
${view === "main" && |
||||
html`<ul class="chat-menu-options w-max">
|
||||
<li> |
||||
<${UsernameForm} |
||||
username=${username} |
||||
isModerator=${isModerator} |
||||
onUsernameChange=${onUsernameChange} |
||||
onFocus=${onFocus} |
||||
onBlur=${onBlur} |
||||
/> |
||||
</li> |
||||
<li> |
||||
<button |
||||
type="button" |
||||
id="chat-toggle" |
||||
onClick=${handleChatPanelToggle} |
||||
style=${{ |
||||
display: chatDisabled || noVideoContent ? 'none' : 'flex', |
||||
}} |
||||
> |
||||
<span>Toggle Chat</span> |
||||
<span><${ChatIcon} /></span> |
||||
</button> |
||||
</li> |
||||
</ul>`} |
||||
|
||||
${view != "main" && html`<${SubMenuView} view=${view} setView=${setView} />`} |
||||
</div>`} |
||||
</div> |
||||
</${Context.Provider}>` |
||||
}; |
||||
|
||||
const SubMenuView = ({ view, setView }) => { |
||||
return html` |
||||
<div className=${`chat-view fadeInRight`}> |
||||
<ul> |
||||
<li> |
||||
<button onClick=${() => setView('main')}> |
||||
<span><${CaretDownIcon} className="w-6 h-6 transform rotate-90"/></span> |
||||
<span>Back</span> |
||||
</button> |
||||
</li> |
||||
</ul> |
||||
${view === 'change_username' && html`<${ChangeUsernameView} />`} |
||||
</div> |
||||
` |
||||
} |
||||
|
||||
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]); |
||||
} |
||||
@ -0,0 +1,13 @@
@@ -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`<svg class="${className}"
|
||||
fill="currentColor" |
||||
viewBox="0 0 20 20" |
||||
xmlns="http://www.w3.org/2000/svg"> |
||||
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"> |
||||
</path> |
||||
</svg>` |
||||
} |
||||
@ -0,0 +1,15 @@
@@ -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` |
||||
<svg |
||||
className="${className}" |
||||
fill="currentColor" |
||||
viewBox="0 0 20 20" |
||||
xmlns="http://www.w3.org/2000/svg"> |
||||
<path fill-rule="evenodd" d="M18 5v8a2 2 0 01-2 2h-5l-5 4v-4H4a2 2 0 01-2-2V5a2 2 0 012-2h12a2 2 0 012 2zM7 8H5v2h2V8zm2 0h2v2H9V8zm6 0h-2v2h2V8z" clip-rule="evenodd"></path> |
||||
</svg> |
||||
`;
|
||||
}; |
||||
@ -0,0 +1,14 @@
@@ -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`<svg
|
||||
class="${className}" |
||||
fill="none" |
||||
stroke="currentColor" |
||||
viewBox="0 0 24 24" |
||||
xmlns="http://www.w3.org/2000/svg"> |
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path> |
||||
</svg>` |
||||
} |
||||
@ -0,0 +1,14 @@
@@ -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`<svg
|
||||
class="${className}" |
||||
fill="currentColor" |
||||
viewBox="0 0 20 20" |
||||
xmlns="http://www.w3.org/2000/svg"> |
||||
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path> |
||||
</svg>` |
||||
} |
||||
@ -0,0 +1,14 @@
@@ -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`<svg class="${className}"
|
||||
fill="currentColor" |
||||
viewBox="0 0 20 20" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
> |
||||
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z"></path> |
||||
</svg>` |
||||
} |
||||
@ -0,0 +1,23 @@
@@ -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` |
||||
<svg |
||||
class="${className}" |
||||
fill="currentColor" |
||||
stroke="currentColor" |
||||
viewBox="0 0 24 24" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
> |
||||
<path |
||||
stroke-linecap="round" |
||||
stroke-linejoin="round" |
||||
stroke-width="4" |
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" |
||||
></path> |
||||
</svg> |
||||
`;
|
||||
}; |
||||
@ -0,0 +1,6 @@
@@ -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" |
||||
Loading…
Reference in new issue