diff --git a/.storybook/preview.js b/.storybook/preview.js deleted file mode 100644 index 3f5230d..0000000 --- a/.storybook/preview.js +++ /dev/null @@ -1,11 +0,0 @@ -import "../src/index.css"; - -export const parameters = { - actions: { argTypesRegex: "^on[A-Z].*" }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, - }, - }, -}; diff --git a/.storybook/preview.jsx b/.storybook/preview.jsx new file mode 100644 index 0000000..9fda6df --- /dev/null +++ b/.storybook/preview.jsx @@ -0,0 +1,25 @@ +import React from "react"; +import { addDecorator } from "@storybook/react"; +import { MemoryRouter } from "react-router-dom"; +import { usePageFocusStyle } from "../src/usePageFocusStyle"; +import { OverlayProvider } from "@react-aria/overlays"; +import "../src/index.css"; + +export const parameters = { + actions: { argTypesRegex: "^on[A-Z].*" }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, +}; + +addDecorator((story) => { + usePageFocusStyle(); + return ( + + {story()} + + ); +}); diff --git a/src/App.jsx b/src/App.jsx index d9fb5ee..cf2770e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -32,29 +32,12 @@ import { ClientProvider, defaultHomeserverHost, } from "./ConferenceCallManagerHooks"; -import { useFocusVisible } from "@react-aria/interactions"; -import styles from "./App.module.css"; import { LoadingView } from "./FullScreenView"; - +import { usePageFocusStyle } from "./usePageFocusStyle"; const SentryRoute = Sentry.withSentryRouting(Route); export default function App({ history }) { - const { isFocusVisible } = useFocusVisible(); - - useEffect(() => { - const classList = document.body.classList; - const hasClass = classList.contains(styles.hideFocus); - - if (isFocusVisible && hasClass) { - classList.remove(styles.hideFocus); - } else if (!isFocusVisible && !hasClass) { - classList.add(styles.hideFocus); - } - - return () => { - classList.remove(styles.hideFocus); - }; - }, [isFocusVisible]); + usePageFocusStyle(); return ( diff --git a/src/Header.jsx b/src/Header.jsx index 7c1cbcc..3eb83b7 100644 --- a/src/Header.jsx +++ b/src/Header.jsx @@ -15,10 +15,15 @@ export function Header({ children, className, ...rest }) { ); } -export function LeftNav({ children, className, ...rest }) { +export function LeftNav({ children, className, hideMobile, ...rest }) { return (
{children} @@ -39,7 +44,7 @@ export function RightNav({ children, className, ...rest }) { export function HeaderLogo() { return ( - + ); diff --git a/src/Header.module.css b/src/Header.module.css index dc2b8ca..3baba13 100644 --- a/src/Header.module.css +++ b/src/Header.module.css @@ -16,16 +16,24 @@ height: 64px; } -.logo { - display: flex; +.headerLogo { + display: none; align-items: center; text-decoration: none; } +.leftNav.hideMobile { + display: none; +} + .leftNav > * { margin-right: 12px; } +.leftNav h3 { + font-size: 15px; +} + .rightNav { justify-content: flex-end; } @@ -40,7 +48,7 @@ .roomAvatar { position: relative; - display: flex; + display: none; justify-content: center; align-items: center; width: 36px; @@ -93,7 +101,17 @@ } @media (min-width: 800px) { + .headerLogo, + .roomAvatar, + .leftNav.hideMobile { + display: flex; + } + + .leftNav h3 { + font-size: 18px; + } + .nav { - height: 98px; + height: 76px; } } diff --git a/src/Header.stories.jsx b/src/Header.stories.jsx index 881d747..6d9a714 100644 --- a/src/Header.stories.jsx +++ b/src/Header.stories.jsx @@ -1,14 +1,106 @@ import React from "react"; -import { Header, LeftNav, RightNav } from "./Header"; +import { GridLayoutMenu } from "./GridLayoutMenu"; +import { + Header, + HeaderLogo, + LeftNav, + RightNav, + RoomHeaderInfo, +} from "./Header"; +import { UserMenu } from "./UserMenu"; export default { title: "Header", component: Header, + parameters: { + layout: "fullscreen", + }, }; -export const Home = () => ( +export const HomeAnonymous = () => (
- + + + + + + +
+); + +export const HomeNamedGuest = () => ( +
+ + + + + + +
+); + +export const HomeLoggedIn = () => ( +
+ + + + + + +
+); + +export const LobbyNamedGuest = () => ( +
+ + + + + + +
+); + +export const LobbyLoggedIn = () => ( +
+ + + + + + +
+); + +export const InRoomNamedGuest = () => ( +
+ + + + + + + +
+); + +export const InRoomLoggedIn = () => ( +
+ + + + + + + +
+); + +export const CreateAccount = () => ( +
+ + +
); diff --git a/src/Home.jsx b/src/Home.jsx index 3f9769e..e19007a 100644 --- a/src/Home.jsx +++ b/src/Home.jsx @@ -36,6 +36,7 @@ import { useModalTriggerState } from "./Modal"; import { randomString } from "matrix-js-sdk/src/randomstring"; import { JoinExistingCallModal } from "./JoinExistingCallModal"; import { RecaptchaInput } from "./RecaptchaInput"; +import { UserMenuContainer } from "./UserMenuContainer"; export function Home() { const { @@ -188,7 +189,7 @@ function UnregisteredView({ - +
@@ -319,7 +320,7 @@ function RegisteredView({ - +
diff --git a/src/Popover.jsx b/src/Popover.jsx index 7729fc0..fe32fe6 100644 --- a/src/Popover.jsx +++ b/src/Popover.jsx @@ -3,11 +3,11 @@ import { DismissButton, useOverlay } from "@react-aria/overlays"; import { FocusScope } from "@react-aria/focus"; import classNames from "classnames"; import styles from "./Popover.module.css"; +import { useObjectRef } from "@react-aria/utils"; export const Popover = forwardRef( ({ isOpen = true, onClose, className, children, ...rest }, ref) => { - const fallbackRef = useRef(); - const popoverRef = ref || fallbackRef; + const popoverRef = useObjectRef(ref); const { overlayProps } = useOverlay( { diff --git a/src/Room.jsx b/src/Room.jsx index ccd064b..6738718 100644 --- a/src/Room.jsx +++ b/src/Room.jsx @@ -51,6 +51,7 @@ import { GridLayoutMenu } from "./GridLayoutMenu"; import { UserMenu } from "./UserMenu"; import classNames from "classnames"; import { Avatar } from "./Avatar"; +import { UserMenuContainer } from "./UserMenuContainer"; const canScreenshare = "getDisplayMedia" in navigator.mediaDevices; // There is currently a bug in Safari our our code with cloning and sending MediaStreams @@ -309,7 +310,7 @@ function RoomSetupView({ - +
@@ -470,7 +471,7 @@ function InRoomView({ - {!isGuest && } + {!isGuest && } {items.length === 0 ? ( diff --git a/src/Tooltip.jsx b/src/Tooltip.jsx index fdc0798..e9d3596 100644 --- a/src/Tooltip.jsx +++ b/src/Tooltip.jsx @@ -1,7 +1,7 @@ -import React, { forwardRef, useRef } from "react"; +import React, { forwardRef } from "react"; import { useTooltipTriggerState } from "@react-stately/tooltip"; import { useTooltipTrigger, useTooltip } from "@react-aria/tooltip"; -import { mergeProps } from "@react-aria/utils"; +import { mergeProps, useObjectRef } from "@react-aria/utils"; import styles from "./Tooltip.module.css"; import classNames from "classnames"; @@ -20,8 +20,7 @@ export function Tooltip({ position, state, ...props }) { export const TooltipTrigger = forwardRef(({ children, ...rest }, ref) => { const tooltipState = useTooltipTriggerState(rest); - const fallbackRef = useRef(); - const triggerRef = ref || fallbackRef; + const triggerRef = useObjectRef(ref); const { triggerProps, tooltipProps } = useTooltipTrigger( rest, tooltipState, diff --git a/src/UserMenu.jsx b/src/UserMenu.jsx index a6d5d07..d1d9a5a 100644 --- a/src/UserMenu.jsx +++ b/src/UserMenu.jsx @@ -1,58 +1,34 @@ -import React, { useCallback, useMemo } from "react"; +import React, { useMemo } from "react"; +import { Item } from "@react-stately/collections"; import { Button, LinkButton } from "./button"; import { PopoverMenuTrigger } from "./PopoverMenu"; +import { Menu } from "./Menu"; +import { Tooltip, TooltipTrigger } from "./Tooltip"; +import { Avatar } from "./Avatar"; import { ReactComponent as UserIcon } from "./icons/User.svg"; import { ReactComponent as LoginIcon } from "./icons/Login.svg"; import { ReactComponent as LogoutIcon } from "./icons/Logout.svg"; import styles from "./UserMenu.module.css"; -import { Item } from "@react-stately/collections"; -import { Menu } from "./Menu"; -import { useHistory, useLocation } from "react-router-dom"; -import { useClient, useProfile } from "./ConferenceCallManagerHooks"; -import { useModalTriggerState } from "./Modal"; -import { ProfileModal } from "./ProfileModal"; -import { Tooltip, TooltipTrigger } from "./Tooltip"; -import { Avatar } from "./Avatar"; +import { useLocation } from "react-router-dom"; -export function UserMenu({ disableLogout }) { +export function UserMenu({ + disableLogout, + isAuthenticated, + isPasswordlessUser, + displayName, + avatarUrl, + onAction, +}) { const location = useLocation(); - const history = useHistory(); - const { - isAuthenticated, - isGuest, - isPasswordlessUser, - logout, - userName, - client, - } = useClient(); - const { displayName, avatarUrl } = useProfile(client); - const { modalState, modalProps } = useModalTriggerState(); - - const onAction = useCallback( - (value) => { - switch (value) { - case "user": - modalState.open(); - break; - case "logout": - logout(); - break; - case "login": - history.push("/login", { state: { from: location } }); - break; - } - }, - [history, location, logout, modalState] - ); const items = useMemo(() => { const arr = []; - if (isAuthenticated && !isGuest) { + if (isAuthenticated) { arr.push({ key: "user", icon: UserIcon, - label: displayName || userName, + label: displayName, }); if (isPasswordlessUser) { @@ -73,9 +49,9 @@ export function UserMenu({ disableLogout }) { } return arr; - }, [isAuthenticated, isGuest, userName, displayName]); + }, [isAuthenticated, isPasswordlessUser, displayName, disableLogout]); - if (isGuest || !isAuthenticated) { + if (!isAuthenticated) { return ( Log in @@ -84,46 +60,36 @@ export function UserMenu({ disableLogout }) { } return ( - <> - - - - {(props) => ( - - Profile - + + + {(props) => ( - - {items.map(({ key, icon: Icon, label }) => ( - - - {label} - - ))} - + + Profile + )} - - {modalState.isOpen && ( - + + {(props) => ( + + {items.map(({ key, icon: Icon, label }) => ( + + + {label} + + ))} + )} - + ); } diff --git a/src/UserMenu.module.css b/src/UserMenu.module.css index 60597b4..8cda2eb 100644 --- a/src/UserMenu.module.css +++ b/src/UserMenu.module.css @@ -1,3 +1,17 @@ .userButton svg * { fill: var(--textColor1); } + +.avatar { + width: 24px; + height: 24px; + font-size: 12px; +} + +@media (min-width: 800px) { + .avatar { + width: 32px; + height: 32px; + font-size: 15px; + } +} diff --git a/src/UserMenuContainer.jsx b/src/UserMenuContainer.jsx new file mode 100644 index 0000000..d9f2f5c --- /dev/null +++ b/src/UserMenuContainer.jsx @@ -0,0 +1,62 @@ +import React, { useCallback } from "react"; +import { useHistory, useLocation } from "react-router-dom"; +import { useClient, useProfile } from "./ConferenceCallManagerHooks"; +import { useModalTriggerState } from "./Modal"; +import { ProfileModal } from "./ProfileModal"; +import { UserMenu } from "./UserMenu"; + +export function UserMenuContainer({ disableLogout }) { + const location = useLocation(); + const history = useHistory(); + const { + isAuthenticated, + isGuest, + isPasswordlessUser, + logout, + userName, + client, + } = useClient(); + const { displayName, avatarUrl } = useProfile(client); + const { modalState, modalProps } = useModalTriggerState(); + + const onAction = useCallback( + (value) => { + switch (value) { + case "user": + modalState.open(); + break; + case "logout": + logout(); + break; + case "login": + history.push("/login", { state: { from: location } }); + break; + } + }, + [history, location, logout, modalState] + ); + + return ( + <> + + {modalState.isOpen && ( + + )} + + ); +} diff --git a/src/button/Button.jsx b/src/button/Button.jsx index 434d4fd..a98fbb6 100644 --- a/src/button/Button.jsx +++ b/src/button/Button.jsx @@ -62,7 +62,7 @@ export const Button = forwardRef( [styles.off]: off, } )} - {...filteredButtonProps} + {...mergeProps(rest, filteredButtonProps)} ref={buttonRef} > {children} diff --git a/src/usePageFocusStyle.js b/src/usePageFocusStyle.js new file mode 100644 index 0000000..c7ec75f --- /dev/null +++ b/src/usePageFocusStyle.js @@ -0,0 +1,22 @@ +import { useEffect } from "react"; +import { useFocusVisible } from "@react-aria/interactions"; +import styles from "./usePageFocusStyle.module.css"; + +export function usePageFocusStyle() { + const { isFocusVisible } = useFocusVisible(); + + useEffect(() => { + const classList = document.body.classList; + const hasClass = classList.contains(styles.hideFocus); + + if (isFocusVisible && hasClass) { + classList.remove(styles.hideFocus); + } else if (!isFocusVisible && !hasClass) { + classList.add(styles.hideFocus); + } + + return () => { + classList.remove(styles.hideFocus); + }; + }, [isFocusVisible]); +} diff --git a/src/App.module.css b/src/usePageFocusStyle.module.css similarity index 100% rename from src/App.module.css rename to src/usePageFocusStyle.module.css