Feat/improve mobile display (#843)
* Ok 1 * Finished * Fix PR * Fix PR * Fix desktop * Fix * Fix absolute listen click outside * console.log * Fix according to code review --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -37,6 +37,7 @@
|
||||
"react-hotkeys-hook": "^4.4.0",
|
||||
"react-loading-skeleton": "^3.3.1",
|
||||
"react-modal": "^3.16.1",
|
||||
"react-responsive": "^9.0.2",
|
||||
"react-router-dom": "^6.4.4",
|
||||
"react-textarea-autosize": "^8.4.1",
|
||||
"react-tooltip": "^5.13.1",
|
||||
|
||||
@ -11,7 +11,7 @@ import {
|
||||
IconTargetArrow,
|
||||
IconUser,
|
||||
} from '@/ui/icon/index';
|
||||
import { useIsSubNavbarDisplayed } from '@/ui/layout/hooks/useIsSubNavbarDisplayed';
|
||||
import { useIsSubMenuNavbarDisplayed } from '@/ui/layout/hooks/useIsSubMenuNavbarDisplayed';
|
||||
import MainNavbar from '@/ui/navbar/components/MainNavbar';
|
||||
import NavItem from '@/ui/navbar/components/NavItem';
|
||||
import NavTitle from '@/ui/navbar/components/NavTitle';
|
||||
@ -21,51 +21,49 @@ export function AppNavbar() {
|
||||
const currentPath = useLocation().pathname;
|
||||
const { openCommandMenu } = useCommandMenu();
|
||||
|
||||
const isSubNavbarDisplayed = useIsSubNavbarDisplayed();
|
||||
const isInSubMenu = useIsSubMenuNavbarDisplayed();
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isSubNavbarDisplayed ? (
|
||||
{!isInSubMenu ? (
|
||||
<MainNavbar>
|
||||
<>
|
||||
<NavItem
|
||||
label="Search"
|
||||
icon={<IconSearch size={theme.icon.size.md} />}
|
||||
onClick={() => {
|
||||
openCommandMenu();
|
||||
}}
|
||||
/>
|
||||
<NavItem
|
||||
label="Inbox"
|
||||
to="/inbox"
|
||||
icon={<IconInbox size={theme.icon.size.md} />}
|
||||
soon={true}
|
||||
/>
|
||||
<NavItem
|
||||
label="Settings"
|
||||
to="/settings/profile"
|
||||
icon={<IconSettings size={theme.icon.size.md} />}
|
||||
/>
|
||||
<NavTitle label="Workspace" />
|
||||
<NavItem
|
||||
label="Companies"
|
||||
to="/companies"
|
||||
icon={<IconBuildingSkyscraper size={theme.icon.size.md} />}
|
||||
active={currentPath === '/companies'}
|
||||
/>
|
||||
<NavItem
|
||||
label="People"
|
||||
to="/people"
|
||||
icon={<IconUser size={theme.icon.size.md} />}
|
||||
active={currentPath === '/people'}
|
||||
/>
|
||||
<NavItem
|
||||
label="Opportunities"
|
||||
to="/opportunities"
|
||||
icon={<IconTargetArrow size={theme.icon.size.md} />}
|
||||
active={currentPath === '/opportunities'}
|
||||
/>
|
||||
</>
|
||||
<NavItem
|
||||
label="Search"
|
||||
icon={<IconSearch size={theme.icon.size.md} />}
|
||||
onClick={() => {
|
||||
openCommandMenu();
|
||||
}}
|
||||
/>
|
||||
<NavItem
|
||||
label="Inbox"
|
||||
to="/inbox"
|
||||
icon={<IconInbox size={theme.icon.size.md} />}
|
||||
soon={true}
|
||||
/>
|
||||
<NavItem
|
||||
label="Settings"
|
||||
to="/settings/profile"
|
||||
icon={<IconSettings size={theme.icon.size.md} />}
|
||||
/>
|
||||
<NavTitle label="Workspace" />
|
||||
<NavItem
|
||||
label="Companies"
|
||||
to="/companies"
|
||||
icon={<IconBuildingSkyscraper size={theme.icon.size.md} />}
|
||||
active={currentPath === '/companies'}
|
||||
/>
|
||||
<NavItem
|
||||
label="People"
|
||||
to="/people"
|
||||
icon={<IconUser size={theme.icon.size.md} />}
|
||||
active={currentPath === '/people'}
|
||||
/>
|
||||
<NavItem
|
||||
label="Opportunities"
|
||||
to="/opportunities"
|
||||
icon={<IconTargetArrow size={theme.icon.size.md} />}
|
||||
active={currentPath === '/opportunities'}
|
||||
/>
|
||||
</MainNavbar>
|
||||
) : (
|
||||
<SettingsNavbar />
|
||||
|
||||
@ -4,6 +4,7 @@ import { useRecoilValue } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||
import { AutosizeTextInput } from '@/ui/input/components/AutosizeTextInput';
|
||||
import { CommentThread, useCreateCommentMutation } from '~/generated/graphql';
|
||||
import { isNonEmptyString } from '~/utils/isNonEmptyString';
|
||||
@ -38,7 +39,10 @@ const StyledCommentActionBar = styled.div`
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
display: flex;
|
||||
padding: 16px 24px 16px 48px;
|
||||
width: calc(${({ theme }) => theme.rightDrawerWidth} - 48px - 24px);
|
||||
width: calc(
|
||||
${({ theme }) => (useIsMobile() ? '100%' : theme.rightDrawerWidth)} - 48px -
|
||||
24px
|
||||
);
|
||||
`;
|
||||
|
||||
const StyledThreadCommentTitle = styled.div`
|
||||
|
||||
@ -12,7 +12,7 @@ import { CompanyChip } from '@/companies/components/CompanyChip';
|
||||
import { useFilteredSearchCompanyQuery } from '@/companies/queries';
|
||||
import { PersonChip } from '@/people/components/PersonChip';
|
||||
import { useFilteredSearchPeopleQuery } from '@/people/queries';
|
||||
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
||||
import { useListenClickOutside } from '@/ui/hooks/useListenClickOutside';
|
||||
import { usePreviousHotkeyScope } from '@/ui/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
||||
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
|
||||
@ -194,7 +194,7 @@ export function CommentThreadRelationPicker({ commentThread }: OwnProps) {
|
||||
placement: 'bottom-start',
|
||||
});
|
||||
|
||||
useListenClickOutsideArrayOfRef({
|
||||
useListenClickOutside({
|
||||
refs: [refs.floating, refs.domReference],
|
||||
callback: () => {
|
||||
exitEditMode();
|
||||
|
||||
@ -9,6 +9,7 @@ import { CommentThreadTypeDropdown } from '@/activities/components/CommentThread
|
||||
import { GET_COMMENT_THREAD } from '@/activities/queries';
|
||||
import { PropertyBox } from '@/ui/editable-field/property-box/components/PropertyBox';
|
||||
import { PropertyBoxItem } from '@/ui/editable-field/property-box/components/PropertyBoxItem';
|
||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||
import { IconArrowUpRight } from '@/ui/icon/index';
|
||||
import {
|
||||
useGetCommentThreadQuery,
|
||||
@ -43,7 +44,8 @@ const StyledTopContainer = styled.div`
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-bottom: ${({ theme }) =>
|
||||
useIsMobile() ? 'none' : `1px solid ${theme.border.color.medium}`};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
@ -187,7 +189,6 @@ export function CommentThread({
|
||||
onChange={updateTitleFromBody}
|
||||
/>
|
||||
</StyledUpperPartContainer>
|
||||
|
||||
{showComment && (
|
||||
<CommentThreadComments
|
||||
commentThread={{
|
||||
|
||||
@ -7,6 +7,7 @@ import { useOpenCommentThreadRightDrawer } from '@/activities/hooks/useOpenComme
|
||||
import { useOpenCreateCommentThreadDrawer } from '@/activities/hooks/useOpenCreateCommentThreadDrawer';
|
||||
import { CommentableEntity } from '@/activities/types/CommentableEntity';
|
||||
import { CommentThreadForDrawer } from '@/activities/types/CommentThreadForDrawer';
|
||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||
import { IconNotes } from '@/ui/icon/index';
|
||||
import {
|
||||
ActivityType,
|
||||
@ -23,21 +24,26 @@ import { OverflowingTextWithTooltip } from '../../../ui/tooltip/OverflowingTextW
|
||||
const StyledMainContainer = styled.div`
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
border-top: ${({ theme }) =>
|
||||
useIsMobile() ? `1px solid ${theme.border.color.medium}` : 'none'};
|
||||
display: flex;
|
||||
flex: 1 0 0;
|
||||
flex-direction: column;
|
||||
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const StyledTimelineContainer = styled.div`
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
|
||||
display: flex;
|
||||
flex: 1 0 0;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
justify-content: flex-start;
|
||||
overflow-y: auto;
|
||||
|
||||
padding: 12px 16px 12px 16px;
|
||||
`;
|
||||
|
||||
@ -175,9 +181,13 @@ const StyledTooltip = styled(Tooltip)`
|
||||
const StyledTopActionBar = styled.div`
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
backdrop-filter: blur(5px);
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
border-top-right-radius: 8px;
|
||||
backdrop-filter: ${() => (useIsMobile() ? 'none' : `blur(5px)`)};
|
||||
|
||||
border-bottom: ${({ theme }) =>
|
||||
useIsMobile() ? 'none' : `1px solid ${theme.border.color.medium}`};
|
||||
|
||||
border-top-right-radius: ${() => (useIsMobile() ? 'none' : `8px`)};
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
left: 0px;
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Command } from 'cmdk';
|
||||
|
||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||
|
||||
export const StyledDialog = styled(Command.Dialog)`
|
||||
background: ${({ theme }) => theme.background.primary};
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
@ -13,8 +15,9 @@ export const StyledDialog = styled(Command.Dialog)`
|
||||
padding: ${({ theme }) => theme.spacing(1)};
|
||||
position: fixed;
|
||||
top: 30%;
|
||||
transform: translateX(-50%);
|
||||
width: 100%;
|
||||
transform: ${() =>
|
||||
useIsMobile() ? 'translateX(-49.5%)' : 'translateX(-50%)'};
|
||||
width: ${() => (useIsMobile() ? 'calc(100% - 40px)' : '100%')};
|
||||
z-index: 1000;
|
||||
`;
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
} from '@/ui/icon/index';
|
||||
import NavItem from '@/ui/navbar/components/NavItem';
|
||||
import NavTitle from '@/ui/navbar/components/NavTitle';
|
||||
import SubNavbar from '@/ui/navbar/components/SubNavbar';
|
||||
import SubMenuNavbar from '@/ui/navbar/components/SubMenuNavbar';
|
||||
|
||||
export function SettingsNavbar() {
|
||||
const theme = useTheme();
|
||||
@ -24,62 +24,60 @@ export function SettingsNavbar() {
|
||||
}, [signOut]);
|
||||
|
||||
return (
|
||||
<SubNavbar backButtonTitle="Settings">
|
||||
<>
|
||||
<NavTitle label="User" />
|
||||
<NavItem
|
||||
label="Profile"
|
||||
to="/settings/profile"
|
||||
icon={<IconUser size={theme.icon.size.md} />}
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/settings/profile').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavItem
|
||||
label="Experience"
|
||||
to="/settings/profile/experience"
|
||||
icon={<IconColorSwatch size={theme.icon.size.md} />}
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/settings/profile/experience').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavTitle label="Workspace" />
|
||||
<NavItem
|
||||
label="Members"
|
||||
to="/settings/workspace-members"
|
||||
icon={<IconUsers size={theme.icon.size.md} />}
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/settings/workspace-members').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavItem
|
||||
label="General"
|
||||
to="/settings/workspace"
|
||||
icon={<IconSettings size={theme.icon.size.md} />}
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/settings/workspace').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavTitle label="Other" />
|
||||
<NavItem
|
||||
label="Logout"
|
||||
onClick={handleLogout}
|
||||
icon={<IconLogout size={theme.icon.size.md} />}
|
||||
danger={true}
|
||||
/>
|
||||
</>
|
||||
</SubNavbar>
|
||||
<SubMenuNavbar backButtonTitle="Settings">
|
||||
<NavTitle label="User" />
|
||||
<NavItem
|
||||
label="Profile"
|
||||
to="/settings/profile"
|
||||
icon={<IconUser size={theme.icon.size.md} />}
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/settings/profile').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavItem
|
||||
label="Experience"
|
||||
to="/settings/profile/experience"
|
||||
icon={<IconColorSwatch size={theme.icon.size.md} />}
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/settings/profile/experience').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavTitle label="Workspace" />
|
||||
<NavItem
|
||||
label="Members"
|
||||
to="/settings/workspace-members"
|
||||
icon={<IconUsers size={theme.icon.size.md} />}
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/settings/workspace-members').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavItem
|
||||
label="General"
|
||||
to="/settings/workspace"
|
||||
icon={<IconSettings size={theme.icon.size.md} />}
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/settings/workspace').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavTitle label="Other" />
|
||||
<NavItem
|
||||
label="Logout"
|
||||
onClick={handleLogout}
|
||||
icon={<IconLogout size={theme.icon.size.md} />}
|
||||
danger={true}
|
||||
/>
|
||||
</SubMenuNavbar>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ReactElement, useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
||||
import { useListenClickOutside } from '@/ui/hooks/useListenClickOutside';
|
||||
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
||||
import { overlayBackground } from '@/ui/themes/effects';
|
||||
|
||||
@ -43,7 +43,7 @@ export function BoardCardEditableFieldEditMode({
|
||||
}: OwnProps) {
|
||||
const wrapperRef = useRef(null);
|
||||
|
||||
useListenClickOutsideArrayOfRef({
|
||||
useListenClickOutside({
|
||||
refs: [wrapperRef],
|
||||
callback: () => {
|
||||
onExit();
|
||||
|
||||
@ -7,7 +7,7 @@ import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
||||
import DropdownButton from '@/ui/filter-n-sort/components/DropdownButton';
|
||||
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
||||
import { useListenClickOutside } from '@/ui/hooks/useListenClickOutside';
|
||||
|
||||
import { BoardColumnEditTitleMenu } from './BoardColumnEditTitleMenu';
|
||||
|
||||
@ -35,7 +35,7 @@ export function BoardColumnMenu({
|
||||
const [openMenu, setOpenMenu] = useState('actions');
|
||||
const boardColumnMenuRef = useRef(null);
|
||||
|
||||
useListenClickOutsideArrayOfRef({
|
||||
useListenClickOutside({
|
||||
refs: [boardColumnMenuRef],
|
||||
callback: onClose,
|
||||
});
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
||||
import { useListenClickOutside } from '@/ui/hooks/useListenClickOutside';
|
||||
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
||||
|
||||
import { EditableFieldHotkeyScope } from '../types/EditableFieldHotkeyScope';
|
||||
@ -12,7 +12,7 @@ export function useRegisterCloseFieldHandlers(
|
||||
) {
|
||||
const { closeEditableField, isFieldInEditMode } = useEditableField();
|
||||
|
||||
useListenClickOutsideArrayOfRef({
|
||||
useListenClickOutside({
|
||||
refs: [wrapperRef],
|
||||
callback: () => {
|
||||
if (isFieldInEditMode) {
|
||||
|
||||
@ -2,7 +2,7 @@ import { useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { DropdownMenu } from '../../dropdown/components/DropdownMenu';
|
||||
import { useListenClickOutsideArrayOfRef } from '../../hooks/useListenClickOutsideArrayOfRef';
|
||||
import { useListenClickOutside } from '../../hooks/useListenClickOutside';
|
||||
|
||||
export const StyledDropdownMenuContainer = styled.ul`
|
||||
position: absolute;
|
||||
@ -19,7 +19,7 @@ export function DropdownMenuContainer({
|
||||
}) {
|
||||
const dropdownRef = useRef(null);
|
||||
|
||||
useListenClickOutsideArrayOfRef({
|
||||
useListenClickOutside({
|
||||
refs: [dropdownRef],
|
||||
callback: () => {
|
||||
onClose?.();
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { useRef } from 'react';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
|
||||
import { useListenClickOutsideArrayOfRef } from '../useListenClickOutsideArrayOfRef';
|
||||
import { useListenClickOutside } from '../useListenClickOutside';
|
||||
|
||||
const onOutsideClick = jest.fn();
|
||||
|
||||
function TestComponentDomMode() {
|
||||
const buttonRef = useRef(null);
|
||||
const buttonRef2 = useRef(null);
|
||||
useListenClickOutsideArrayOfRef({
|
||||
useListenClickOutside({
|
||||
refs: [buttonRef, buttonRef2],
|
||||
callback: onOutsideClick,
|
||||
});
|
||||
@ -22,7 +22,7 @@ function TestComponentDomMode() {
|
||||
);
|
||||
}
|
||||
|
||||
test('useListenClickOutsideArrayOfRef hook works in dom mode', async () => {
|
||||
test('useListenClickOutside hook works in dom mode', async () => {
|
||||
const { getByText } = render(<TestComponentDomMode />);
|
||||
const inside = getByText('Inside');
|
||||
const inside2 = getByText('Inside 2');
|
||||
|
||||
7
front/src/modules/ui/hooks/useIsMobile.ts
Normal file
7
front/src/modules/ui/hooks/useIsMobile.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { useMediaQuery } from 'react-responsive';
|
||||
|
||||
import { MOBILE_VIEWPORT } from '@/ui/themes/themes';
|
||||
|
||||
export function useIsMobile() {
|
||||
return useMediaQuery({ query: `(max-width: ${MOBILE_VIEWPORT}px)` });
|
||||
}
|
||||
@ -5,7 +5,7 @@ export enum ClickOutsideMode {
|
||||
dom = 'dom',
|
||||
}
|
||||
|
||||
export function useListenClickOutsideArrayOfRef<T extends Element>({
|
||||
export function useListenClickOutside<T extends Element>({
|
||||
refs,
|
||||
callback,
|
||||
mode = ClickOutsideMode.dom,
|
||||
@ -37,9 +37,13 @@ export function useListenClickOutsideArrayOfRef<T extends Element>({
|
||||
const { x, y, width, height } = ref.current.getBoundingClientRect();
|
||||
|
||||
const clientX =
|
||||
'clientX' in event ? event.clientX : event.touches[0].clientX;
|
||||
'clientX' in event
|
||||
? event.clientX
|
||||
: event.changedTouches[0].clientX;
|
||||
const clientY =
|
||||
'clientY' in event ? event.clientY : event.touches[0].clientY;
|
||||
'clientY' in event
|
||||
? event.clientY
|
||||
: event.changedTouches[0].clientY;
|
||||
|
||||
if (
|
||||
clientX < x ||
|
||||
@ -1,5 +1,3 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
import { AnimatePresence, LayoutGroup } from 'framer-motion';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
@ -11,10 +9,9 @@ import { CommandMenu } from '@/command-menu/components/CommandMenu';
|
||||
import { NavbarAnimatedContainer } from '@/ui/navbar/components/NavbarAnimatedContainer';
|
||||
import { MOBILE_VIEWPORT } from '@/ui/themes/themes';
|
||||
import { AppNavbar } from '~/AppNavbar';
|
||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||
import { CompaniesMockMode } from '~/pages/companies/CompaniesMockMode';
|
||||
|
||||
import { AppPath } from '../../../types/AppPath';
|
||||
import { useAutoNavigateOnboarding } from '../hooks/useAutoNavigateOnboarding';
|
||||
import { isNavbarOpenedState } from '../states/isNavbarOpenedState';
|
||||
|
||||
const StyledLayout = styled.div`
|
||||
@ -46,72 +43,30 @@ type OwnProps = {
|
||||
};
|
||||
|
||||
export function DefaultLayout({ children }: OwnProps) {
|
||||
const navigate = useNavigate();
|
||||
const isMatchingLocation = useIsMatchingLocation();
|
||||
useAutoNavigateOnboarding();
|
||||
|
||||
const onboardingStatus = useOnboardingStatus();
|
||||
|
||||
useEffect(() => {
|
||||
const isMachinOngoingUserCreationRoute =
|
||||
isMatchingLocation(AppPath.SignUp) ||
|
||||
isMatchingLocation(AppPath.SignIn) ||
|
||||
isMatchingLocation(AppPath.Invite) ||
|
||||
isMatchingLocation(AppPath.Verify);
|
||||
|
||||
const isMatchingOnboardingRoute =
|
||||
isMatchingLocation(AppPath.SignUp) ||
|
||||
isMatchingLocation(AppPath.SignIn) ||
|
||||
isMatchingLocation(AppPath.Invite) ||
|
||||
isMatchingLocation(AppPath.Verify) ||
|
||||
isMatchingLocation(AppPath.CreateWorkspace) ||
|
||||
isMatchingLocation(AppPath.CreateProfile);
|
||||
|
||||
if (
|
||||
onboardingStatus === OnboardingStatus.OngoingUserCreation &&
|
||||
!isMachinOngoingUserCreationRoute
|
||||
) {
|
||||
navigate(AppPath.SignIn);
|
||||
} else if (
|
||||
onboardingStatus === OnboardingStatus.OngoingWorkspaceCreation &&
|
||||
!isMatchingLocation(AppPath.CreateWorkspace)
|
||||
) {
|
||||
navigate(AppPath.CreateWorkspace);
|
||||
} else if (
|
||||
onboardingStatus === OnboardingStatus.OngoingProfileCreation &&
|
||||
!isMatchingLocation(AppPath.CreateProfile)
|
||||
) {
|
||||
navigate(AppPath.CreateProfile);
|
||||
} else if (
|
||||
onboardingStatus === OnboardingStatus.Completed &&
|
||||
isMatchingOnboardingRoute
|
||||
) {
|
||||
navigate('/');
|
||||
}
|
||||
}, [onboardingStatus, navigate, isMatchingLocation]);
|
||||
|
||||
return (
|
||||
<StyledLayout>
|
||||
<>
|
||||
<CommandMenu />
|
||||
<NavbarAnimatedContainer>
|
||||
<AppNavbar />
|
||||
</NavbarAnimatedContainer>
|
||||
<MainContainer>
|
||||
{onboardingStatus &&
|
||||
onboardingStatus !== OnboardingStatus.Completed ? (
|
||||
<>
|
||||
<CompaniesMockMode />
|
||||
<AnimatePresence mode="wait">
|
||||
<LayoutGroup>
|
||||
<AuthModal>{children}</AuthModal>
|
||||
</LayoutGroup>
|
||||
</AnimatePresence>
|
||||
</>
|
||||
) : (
|
||||
<>{children}</>
|
||||
)}
|
||||
</MainContainer>
|
||||
</>
|
||||
<CommandMenu />
|
||||
<NavbarAnimatedContainer>
|
||||
<AppNavbar />
|
||||
</NavbarAnimatedContainer>
|
||||
<MainContainer>
|
||||
{onboardingStatus && onboardingStatus !== OnboardingStatus.Completed ? (
|
||||
<>
|
||||
<CompaniesMockMode />
|
||||
<AnimatePresence mode="wait">
|
||||
<LayoutGroup>
|
||||
<AuthModal>{children}</AuthModal>
|
||||
</LayoutGroup>
|
||||
</AnimatePresence>
|
||||
</>
|
||||
) : (
|
||||
<>{children}</>
|
||||
)}
|
||||
</MainContainer>
|
||||
</StyledLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@ -28,6 +28,7 @@ type LeftContainerProps = {
|
||||
|
||||
const StyledLeftContainer = styled.div<LeftContainerProps>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
11
front/src/modules/ui/layout/components/ShowPageContainer.tsx
Normal file
11
front/src/modules/ui/layout/components/ShowPageContainer.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||
|
||||
export const ShowPageContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: ${() => (useIsMobile() ? 'column' : 'row')};
|
||||
gap: ${({ theme }) => (useIsMobile() ? theme.spacing(3) : '0')};
|
||||
height: 100%;
|
||||
width: ${() => (useIsMobile() ? `calc(100% - 2px);` : '100%')};
|
||||
`;
|
||||
@ -12,7 +12,7 @@ const StyledContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export function NoTopBarContainer({ children }: OwnProps) {
|
||||
export function SubMenuTopBarContainer({ children }: OwnProps) {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<RightDrawerContainer topMargin={16}>{children}</RightDrawerContainer>
|
||||
@ -0,0 +1,52 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useIsMatchingLocation } from '../../../../hooks/useIsMatchingLocation';
|
||||
import { useOnboardingStatus } from '../../../auth/hooks/useOnboardingStatus';
|
||||
import { OnboardingStatus } from '../../../auth/utils/getOnboardingStatus';
|
||||
import { AppPath } from '../../../types/AppPath';
|
||||
|
||||
export function useAutoNavigateOnboarding() {
|
||||
const navigate = useNavigate();
|
||||
const isMatchingLocation = useIsMatchingLocation();
|
||||
|
||||
const onboardingStatus = useOnboardingStatus();
|
||||
|
||||
useEffect(() => {
|
||||
const isMachinOngoingUserCreationRoute =
|
||||
isMatchingLocation(AppPath.SignUp) ||
|
||||
isMatchingLocation(AppPath.SignIn) ||
|
||||
isMatchingLocation(AppPath.Invite) ||
|
||||
isMatchingLocation(AppPath.Verify);
|
||||
|
||||
const isMatchingOnboardingRoute =
|
||||
isMatchingLocation(AppPath.SignUp) ||
|
||||
isMatchingLocation(AppPath.SignIn) ||
|
||||
isMatchingLocation(AppPath.Invite) ||
|
||||
isMatchingLocation(AppPath.Verify) ||
|
||||
isMatchingLocation(AppPath.CreateWorkspace) ||
|
||||
isMatchingLocation(AppPath.CreateProfile);
|
||||
|
||||
if (
|
||||
onboardingStatus === OnboardingStatus.OngoingUserCreation &&
|
||||
!isMachinOngoingUserCreationRoute
|
||||
) {
|
||||
navigate(AppPath.SignIn);
|
||||
} else if (
|
||||
onboardingStatus === OnboardingStatus.OngoingWorkspaceCreation &&
|
||||
!isMatchingLocation(AppPath.CreateWorkspace)
|
||||
) {
|
||||
navigate(AppPath.CreateWorkspace);
|
||||
} else if (
|
||||
onboardingStatus === OnboardingStatus.OngoingProfileCreation &&
|
||||
!isMatchingLocation(AppPath.CreateProfile)
|
||||
) {
|
||||
navigate(AppPath.CreateProfile);
|
||||
} else if (
|
||||
onboardingStatus === OnboardingStatus.Completed &&
|
||||
isMatchingOnboardingRoute
|
||||
) {
|
||||
navigate('/');
|
||||
}
|
||||
}, [onboardingStatus, navigate, isMatchingLocation]);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
export function useIsSubNavbarDisplayed() {
|
||||
export function useIsSubMenuNavbarDisplayed() {
|
||||
const currentPath = useLocation().pathname;
|
||||
return currentPath.match(/\/settings\//g) !== null;
|
||||
}
|
||||
@ -1,13 +1,23 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||
|
||||
export const ShowPageLeftContainer = styled.div`
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border-bottom-left-radius: 8px;
|
||||
border-right: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-right: 1px solid
|
||||
${({ theme }) => {
|
||||
const isMobile = useIsMobile();
|
||||
return !isMobile ? theme.border.color.medium : 0;
|
||||
}};
|
||||
border-top-left-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(3)};
|
||||
padding: 0px ${({ theme }) => theme.spacing(3)};
|
||||
width: 320px;
|
||||
width: ${({ theme }) => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return isMobile ? `calc(100% - ${theme.spacing(6)})` : '320px';
|
||||
}};
|
||||
`;
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
import { ReactNode, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { IconButton } from '@/ui/button/components/IconButton';
|
||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||
import { IconChevronLeft, IconPlus } from '@/ui/icon/index';
|
||||
import NavCollapseButton from '@/ui/navbar/components/NavCollapseButton';
|
||||
|
||||
import { navbarIconSize } from '../../../navbar/constants';
|
||||
import { OverflowingTextWithTooltip } from '../../../tooltip/OverflowingTextWithTooltip';
|
||||
import { isNavbarOpenedState } from '../../states/isNavbarOpenedState';
|
||||
|
||||
export const TOP_BAR_MIN_HEIGHT = 40;
|
||||
|
||||
@ -20,6 +24,7 @@ const TopBarContainer = styled.div`
|
||||
justify-content: space-between;
|
||||
min-height: ${TOP_BAR_MIN_HEIGHT}px;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
padding-left: 0;
|
||||
padding-right: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
@ -37,10 +42,22 @@ const TitleContainer = styled.div`
|
||||
max-width: 50%;
|
||||
`;
|
||||
|
||||
const TopBarButtonContainer = styled.div`
|
||||
margin-right: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const BackIconButton = styled(IconButton)`
|
||||
margin-right: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledTopBarIconTitleContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
title: string;
|
||||
hasBackButton?: boolean;
|
||||
@ -57,21 +74,38 @@ export function TopBar({
|
||||
const navigate = useNavigate();
|
||||
const navigateBack = useCallback(() => navigate(-1), [navigate]);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
const isNavbarOpened = useRecoilValue(isNavbarOpenedState);
|
||||
|
||||
const showNavCollapseButton = isMobile || !isNavbarOpened;
|
||||
|
||||
const iconSize = useIsMobile()
|
||||
? navbarIconSize.mobile
|
||||
: navbarIconSize.desktop;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TopBarContainer>
|
||||
<StyledLeftContainer>
|
||||
<NavCollapseButton hideIfOpen={true} />
|
||||
{hasBackButton && (
|
||||
<BackIconButton
|
||||
icon={<IconChevronLeft size={16} />}
|
||||
onClick={navigateBack}
|
||||
/>
|
||||
{showNavCollapseButton && (
|
||||
<TopBarButtonContainer>
|
||||
<NavCollapseButton direction="right" />
|
||||
</TopBarButtonContainer>
|
||||
)}
|
||||
{icon}
|
||||
<TitleContainer data-testid="top-bar-title">
|
||||
<OverflowingTextWithTooltip text={title} />
|
||||
</TitleContainer>
|
||||
{hasBackButton && (
|
||||
<TopBarButtonContainer>
|
||||
<BackIconButton
|
||||
icon={<IconChevronLeft size={iconSize} />}
|
||||
onClick={navigateBack}
|
||||
/>
|
||||
</TopBarButtonContainer>
|
||||
)}
|
||||
<StyledTopBarIconTitleContainer>
|
||||
{icon}
|
||||
<TitleContainer data-testid="top-bar-title">
|
||||
<OverflowingTextWithTooltip text={title} />
|
||||
</TitleContainer>
|
||||
</StyledTopBarIconTitleContainer>
|
||||
</StyledLeftContainer>
|
||||
{onAddButtonClick && (
|
||||
<IconButton
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import NavCollapseButton from '@/ui/navbar/components/NavCollapseButton';
|
||||
|
||||
const TitleAndCollapseContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
@ -22,7 +20,6 @@ type OwnProps = {
|
||||
export function TopTitle({ title }: OwnProps) {
|
||||
return (
|
||||
<TitleAndCollapseContainer>
|
||||
<NavCollapseButton hideIfOpen={true} hideOnDesktop={true} />
|
||||
<TitleContainer data-testid="top-bar-title">{title}</TitleContainer>
|
||||
</TitleAndCollapseContainer>
|
||||
);
|
||||
|
||||
@ -4,11 +4,11 @@ import NavItemsContainer from './NavItemsContainer';
|
||||
import NavWorkspaceButton from './NavWorkspaceButton';
|
||||
|
||||
type OwnProps = {
|
||||
children: JSX.Element;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
width: ${({ theme }) => theme.leftNavBarWidth};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export default function MainNavbar({ children }: OwnProps) {
|
||||
|
||||
@ -5,8 +5,6 @@ import { useRecoilState } from 'recoil';
|
||||
import { IconChevronLeft } from '@/ui/icon/index';
|
||||
import { isNavbarSwitchingSizeState } from '@/ui/layout/states/isNavbarSwitchingSizeState';
|
||||
|
||||
import NavCollapseButton from './NavCollapseButton';
|
||||
|
||||
type OwnProps = {
|
||||
title: string;
|
||||
};
|
||||
@ -50,7 +48,6 @@ export default function NavBackButton({ title }: OwnProps) {
|
||||
<IconChevronLeft />
|
||||
<span>{title}</span>
|
||||
</IconAndButtonContainer>
|
||||
<NavCollapseButton hideOnDesktop={true} />
|
||||
</StyledContainer>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||
import {
|
||||
IconLayoutSidebarLeftCollapse,
|
||||
IconLayoutSidebarRightCollapse,
|
||||
} from '@/ui/icon';
|
||||
import { isNavbarOpenedState } from '@/ui/layout/states/isNavbarOpenedState';
|
||||
import { MOBILE_VIEWPORT } from '@/ui/themes/themes';
|
||||
|
||||
const CollapseButton = styled.button<{ hideOnDesktop: boolean | undefined }>`
|
||||
import { navbarIconSize } from '../constants';
|
||||
|
||||
const CollapseButton = styled.button`
|
||||
align-items: center;
|
||||
background: inherit;
|
||||
border: 0;
|
||||
@ -25,46 +27,33 @@ const CollapseButton = styled.button<{ hideOnDesktop: boolean | undefined }>`
|
||||
justify-content: center;
|
||||
|
||||
padding: 0;
|
||||
|
||||
user-select: none;
|
||||
|
||||
width: 32px;
|
||||
|
||||
${(props) =>
|
||||
props.hideOnDesktop &&
|
||||
`@media (min-width: ${MOBILE_VIEWPORT}px) {
|
||||
display:none;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
type CollapseButtonProps = {
|
||||
hideIfOpen?: boolean;
|
||||
hideIfClosed?: boolean;
|
||||
hideOnDesktop?: boolean;
|
||||
direction?: 'left' | 'right';
|
||||
};
|
||||
|
||||
export default function NavCollapseButton({
|
||||
hideIfOpen,
|
||||
hideOnDesktop,
|
||||
direction = 'left',
|
||||
}: CollapseButtonProps) {
|
||||
const [isNavOpen, setIsNavOpen] = useRecoilState(isNavbarOpenedState);
|
||||
|
||||
const iconSize = useIsMobile()
|
||||
? navbarIconSize.mobile
|
||||
: navbarIconSize.desktop;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isNavOpen && !hideIfOpen && (
|
||||
<CollapseButton
|
||||
onClick={() => setIsNavOpen(!isNavOpen)}
|
||||
hideOnDesktop={hideOnDesktop}
|
||||
>
|
||||
<IconLayoutSidebarLeftCollapse size={16} />
|
||||
{direction === 'left' ? (
|
||||
<CollapseButton onClick={() => setIsNavOpen(!isNavOpen)}>
|
||||
<IconLayoutSidebarLeftCollapse size={iconSize} />
|
||||
</CollapseButton>
|
||||
)}
|
||||
{!isNavOpen && (
|
||||
<CollapseButton
|
||||
onClick={() => setIsNavOpen(!isNavOpen)}
|
||||
hideOnDesktop={hideOnDesktop}
|
||||
>
|
||||
<IconLayoutSidebarRightCollapse size={16} />
|
||||
) : (
|
||||
<CollapseButton onClick={() => setIsNavOpen(!isNavOpen)}>
|
||||
<IconLayoutSidebarRightCollapse size={iconSize} />
|
||||
</CollapseButton>
|
||||
)}
|
||||
</>
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||
import { MOBILE_VIEWPORT } from '@/ui/themes/themes';
|
||||
|
||||
import { isNavbarOpenedState } from '../../layout/states/isNavbarOpenedState';
|
||||
|
||||
type OwnProps = {
|
||||
label: string;
|
||||
to?: string;
|
||||
@ -79,21 +83,25 @@ const StyledSoonPill = styled.div`
|
||||
|
||||
function NavItem({ label, icon, to, onClick, active, danger, soon }: OwnProps) {
|
||||
const navigate = useNavigate();
|
||||
const [, setIsNavbarOpened] = useRecoilState(isNavbarOpenedState);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
function handleItemClick() {
|
||||
if (isMobile) {
|
||||
setIsNavbarOpened(false);
|
||||
}
|
||||
|
||||
const onItemClick = () => {
|
||||
if (onClick) {
|
||||
onClick();
|
||||
return;
|
||||
}
|
||||
if (to) {
|
||||
} else if (to) {
|
||||
navigate(to);
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledItem
|
||||
onClick={onItemClick}
|
||||
onClick={handleItemClick}
|
||||
active={active}
|
||||
aria-selected={active}
|
||||
danger={danger}
|
||||
|
||||
@ -65,7 +65,7 @@ function NavWorkspaceButton() {
|
||||
></StyledLogo>
|
||||
<StyledName>{currentWorkspace?.displayName ?? 'Twenty'}</StyledName>
|
||||
</LogoAndNameContainer>
|
||||
<NavCollapseButton />
|
||||
<NavCollapseButton direction="left" />
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,10 +3,12 @@ import styled from '@emotion/styled';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { useIsSubNavbarDisplayed } from '@/ui/layout/hooks/useIsSubNavbarDisplayed';
|
||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||
import { useIsSubMenuNavbarDisplayed } from '@/ui/layout/hooks/useIsSubMenuNavbarDisplayed';
|
||||
import { isNavbarOpenedState } from '@/ui/layout/states/isNavbarOpenedState';
|
||||
import { isNavbarSwitchingSizeState } from '@/ui/layout/states/isNavbarSwitchingSizeState';
|
||||
import { MOBILE_VIEWPORT } from '@/ui/themes/themes';
|
||||
|
||||
import { leftNavbarWidth, leftSubMenuNavbarWidth } from '../constants';
|
||||
|
||||
const StyledNavbarContainer = styled(motion.div)`
|
||||
align-items: end;
|
||||
@ -15,35 +17,37 @@ const StyledNavbarContainer = styled(motion.div)`
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
width: ${(props) =>
|
||||
useRecoilValue(isNavbarOpenedState)
|
||||
? `calc(100% - ` + props.theme.spacing(4) + `)`
|
||||
: '0'};
|
||||
}
|
||||
`;
|
||||
|
||||
type NavbarProps = {
|
||||
children: React.ReactNode;
|
||||
layout?: string;
|
||||
};
|
||||
|
||||
export function NavbarAnimatedContainer({ children, layout }: NavbarProps) {
|
||||
export function NavbarAnimatedContainer({ children }: NavbarProps) {
|
||||
const isMenuOpened = useRecoilValue(isNavbarOpenedState);
|
||||
const [, setIsNavbarSwitchingSize] = useRecoilState(
|
||||
isNavbarSwitchingSizeState,
|
||||
);
|
||||
const isSubNavbarDisplayed = useIsSubNavbarDisplayed();
|
||||
const isInSubMenu = useIsSubMenuNavbarDisplayed();
|
||||
const theme = useTheme();
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const leftBarWidth = isInSubMenu
|
||||
? isMobile
|
||||
? leftSubMenuNavbarWidth.mobile
|
||||
: leftSubMenuNavbarWidth.desktop
|
||||
: isMobile
|
||||
? leftNavbarWidth.mobile
|
||||
: leftNavbarWidth.desktop;
|
||||
|
||||
return (
|
||||
<StyledNavbarContainer
|
||||
onAnimationComplete={() => {
|
||||
setIsNavbarSwitchingSize(false);
|
||||
}}
|
||||
animate={{
|
||||
width: isMenuOpened ? (isSubNavbarDisplayed ? '520px' : '220px') : '0',
|
||||
width: isMenuOpened ? leftBarWidth : '0',
|
||||
opacity: isMenuOpened ? 1 : 0,
|
||||
}}
|
||||
transition={{
|
||||
|
||||
@ -1,21 +1,25 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||
|
||||
import { leftNavbarWidth } from '../constants';
|
||||
|
||||
import NavBackButton from './NavBackButton';
|
||||
import NavItemsContainer from './NavItemsContainer';
|
||||
|
||||
type OwnProps = {
|
||||
children: JSX.Element;
|
||||
children: React.ReactNode;
|
||||
backButtonTitle: string;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: ${({ theme }) => theme.spacing(6)};
|
||||
width: 220px;
|
||||
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||
width: ${({ theme }) => (useIsMobile() ? '100%' : leftNavbarWidth.desktop)};
|
||||
`;
|
||||
|
||||
export default function SubNavbar({ children, backButtonTitle }: OwnProps) {
|
||||
export default function SubMenuNavbar({ children, backButtonTitle }: OwnProps) {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<NavBackButton title={backButtonTitle} />
|
||||
14
front/src/modules/ui/navbar/constants/index.ts
Normal file
14
front/src/modules/ui/navbar/constants/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export const leftNavbarWidth = {
|
||||
mobile: 'calc(100% - 16px)',
|
||||
desktop: '220px',
|
||||
};
|
||||
|
||||
export const leftSubMenuNavbarWidth = {
|
||||
mobile: 'calc(100% - 16px)',
|
||||
desktop: '520px',
|
||||
};
|
||||
|
||||
export const navbarIconSize = {
|
||||
mobile: 18,
|
||||
desktop: 16,
|
||||
};
|
||||
@ -6,7 +6,7 @@ import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearch } from '@/ui/dropdown/components/DropdownMenuSearch';
|
||||
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
|
||||
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
||||
import { useListenClickOutside } from '@/ui/hooks/useListenClickOutside';
|
||||
import { IconPlus } from '@/ui/icon';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
@ -46,7 +46,7 @@ export function SingleEntitySelect<
|
||||
|
||||
const showCreateButton = isDefined(onCreate) && searchFilter !== '';
|
||||
|
||||
useListenClickOutsideArrayOfRef({
|
||||
useListenClickOutside({
|
||||
refs: [containerRef],
|
||||
callback: () => {
|
||||
onCancel?.();
|
||||
|
||||
@ -5,13 +5,15 @@ import { motion } from 'framer-motion';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||
import {
|
||||
ClickOutsideMode,
|
||||
useListenClickOutsideArrayOfRef,
|
||||
} from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
||||
useListenClickOutside,
|
||||
} from '@/ui/hooks/useListenClickOutside';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { useScopedHotkeys } from '../../hotkey/hooks/useScopedHotkeys';
|
||||
import { leftNavbarWidth } from '../../navbar/constants';
|
||||
import { useRightDrawer } from '../hooks/useRightDrawer';
|
||||
import { isRightDrawerExpandedState } from '../states/isRightDrawerExpandedState';
|
||||
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
|
||||
@ -51,7 +53,7 @@ export function RightDrawer() {
|
||||
|
||||
const rightDrawerRef = useRef(null);
|
||||
|
||||
useListenClickOutsideArrayOfRef({
|
||||
useListenClickOutside({
|
||||
refs: [rightDrawerRef],
|
||||
callback: () => closeRightDrawer(),
|
||||
mode: ClickOutsideMode.absolute,
|
||||
@ -66,16 +68,22 @@ export function RightDrawer() {
|
||||
[setIsRightDrawerOpen],
|
||||
);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const rightDrawerWidthExpanded = `calc(100% - ${
|
||||
theme.leftNavBarWidth
|
||||
leftNavbarWidth.desktop
|
||||
} - ${theme.spacing(2)})`;
|
||||
|
||||
const rightDrawerWidth = isRightDrawerOpen
|
||||
? isRightDrawerExpanded
|
||||
? isMobile
|
||||
? '100%'
|
||||
: isRightDrawerExpanded
|
||||
? rightDrawerWidthExpanded
|
||||
: theme.rightDrawerWidth
|
||||
: '0';
|
||||
|
||||
console.log(rightDrawerWidth);
|
||||
|
||||
if (!isDefined(rightDrawerPage)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||
|
||||
import { RightDrawerTopBarCloseButton } from './RightDrawerTopBarCloseButton';
|
||||
import { RightDrawerTopBarExpandButton } from './RightDrawerTopBarExpandButton';
|
||||
|
||||
@ -20,10 +22,12 @@ const StyledRightDrawerTopBar = styled.div`
|
||||
`;
|
||||
|
||||
export function RightDrawerTopBar() {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<StyledRightDrawerTopBar>
|
||||
<RightDrawerTopBarCloseButton />
|
||||
<RightDrawerTopBarExpandButton />
|
||||
{!isMobile && <RightDrawerTopBarExpandButton />}
|
||||
</StyledRightDrawerTopBar>
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import styled from '@emotion/styled';
|
||||
|
||||
import { TableColumn } from '@/people/table/components/peopleColumns';
|
||||
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
|
||||
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
||||
import { useListenClickOutside } from '@/ui/hooks/useListenClickOutside';
|
||||
|
||||
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
|
||||
import { TableHeader } from '../table-header/components/TableHeader';
|
||||
@ -90,7 +90,7 @@ export function EntityTable<SortField>({
|
||||
|
||||
const leaveTableFocus = useLeaveTableFocus();
|
||||
|
||||
useListenClickOutsideArrayOfRef({
|
||||
useListenClickOutside({
|
||||
refs: [tableBodyRef],
|
||||
callback: () => {
|
||||
leaveTableFocus();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
||||
import { useListenClickOutside } from '@/ui/hooks/useListenClickOutside';
|
||||
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
||||
|
||||
import { useMoveSoftFocus } from '../../hooks/useMoveSoftFocus';
|
||||
@ -14,7 +14,7 @@ export function useRegisterCloseCellHandlers(
|
||||
) {
|
||||
const { closeEditableCell } = useEditableCell();
|
||||
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
|
||||
useListenClickOutsideArrayOfRef({
|
||||
useListenClickOutside({
|
||||
refs: [wrapperRef],
|
||||
callback: (event) => {
|
||||
if (isCurrentCellInEditMode) {
|
||||
|
||||
@ -37,7 +37,6 @@ const common = {
|
||||
checkboxColumnWidth: '32px',
|
||||
},
|
||||
rightDrawerWidth: '500px',
|
||||
leftNavBarWidth: '220px',
|
||||
clickableElementBackgroundTransition: 'background 0.1s ease',
|
||||
lastLayerZIndex: 2147483647,
|
||||
};
|
||||
|
||||
@ -18,6 +18,7 @@ import { CommentableType } from '~/generated/graphql';
|
||||
import { getLogoUrlFromDomainName } from '~/utils';
|
||||
|
||||
import { CompanyNameEditableField } from '../../modules/companies/editable-field/components/CompanyNameEditableField';
|
||||
import { ShowPageContainer } from '../../modules/ui/layout/components/ShowPageContainer';
|
||||
|
||||
export function CompanyShow() {
|
||||
const companyId = useParams().companyId ?? '';
|
||||
@ -35,29 +36,31 @@ export function CompanyShow() {
|
||||
hasBackButton
|
||||
icon={<IconBuildingSkyscraper size={theme.icon.size.md} />}
|
||||
>
|
||||
<ShowPageLeftContainer>
|
||||
<ShowPageSummaryCard
|
||||
id={company?.id}
|
||||
logoOrAvatar={getLogoUrlFromDomainName(company?.domainName ?? '')}
|
||||
title={company?.name ?? 'No name'}
|
||||
date={company?.createdAt ?? ''}
|
||||
renderTitleEditComponent={() => (
|
||||
<CompanyNameEditableField company={company} />
|
||||
)}
|
||||
/>
|
||||
<PropertyBox extraPadding={true}>
|
||||
<CompanyDomainNameEditableField company={company} />
|
||||
<CompanyAccountOwnerEditableField company={company} />
|
||||
<CompanyEmployeesEditableField company={company} />
|
||||
<CompanyAddressEditableField company={company} />
|
||||
<CompanyCreatedAtEditableField company={company} />
|
||||
</PropertyBox>
|
||||
</ShowPageLeftContainer>
|
||||
<ShowPageRightContainer>
|
||||
<Timeline
|
||||
entity={{ id: company?.id ?? '', type: CommentableType.Company }}
|
||||
/>
|
||||
</ShowPageRightContainer>
|
||||
<ShowPageContainer>
|
||||
<ShowPageLeftContainer>
|
||||
<ShowPageSummaryCard
|
||||
id={company?.id}
|
||||
logoOrAvatar={getLogoUrlFromDomainName(company?.domainName ?? '')}
|
||||
title={company?.name ?? 'No name'}
|
||||
date={company?.createdAt ?? ''}
|
||||
renderTitleEditComponent={() => (
|
||||
<CompanyNameEditableField company={company} />
|
||||
)}
|
||||
/>
|
||||
<PropertyBox extraPadding={true}>
|
||||
<CompanyDomainNameEditableField company={company} />
|
||||
<CompanyAccountOwnerEditableField company={company} />
|
||||
<CompanyEmployeesEditableField company={company} />
|
||||
<CompanyAddressEditableField company={company} />
|
||||
<CompanyCreatedAtEditableField company={company} />
|
||||
</PropertyBox>
|
||||
</ShowPageLeftContainer>
|
||||
<ShowPageRightContainer>
|
||||
<Timeline
|
||||
entity={{ id: company?.id ?? '', type: CommentableType.Company }}
|
||||
/>
|
||||
</ShowPageRightContainer>
|
||||
</ShowPageContainer>
|
||||
</WithTopBarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSu
|
||||
import { CommentableType } from '~/generated/graphql';
|
||||
|
||||
import { PeopleFullNameEditableField } from '../../modules/people/editable-field/components/PeopleFullNameEditableField';
|
||||
import { ShowPageContainer } from '../../modules/ui/layout/components/ShowPageContainer';
|
||||
|
||||
export function PersonShow() {
|
||||
const personId = useParams().personId ?? '';
|
||||
@ -27,7 +28,7 @@ export function PersonShow() {
|
||||
icon={<IconUser size={theme.icon.size.md} />}
|
||||
hasBackButton
|
||||
>
|
||||
<>
|
||||
<ShowPageContainer>
|
||||
<ShowPageLeftContainer>
|
||||
<ShowPageSummaryCard
|
||||
id={person?.id}
|
||||
@ -44,7 +45,7 @@ export function PersonShow() {
|
||||
entity={{ id: person?.id ?? '', type: CommentableType.Person }}
|
||||
/>
|
||||
</ShowPageRightContainer>
|
||||
</>
|
||||
</ShowPageContainer>
|
||||
</WithTopBarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { ColorSchemePicker } from '@/ui/color-scheme/components/ColorSchemePicker';
|
||||
import { NoTopBarContainer } from '@/ui/layout/components/NoTopBarContainer';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/components/SubMenuTopBarContainer';
|
||||
import { useColorScheme } from '@/ui/themes/hooks/useColorScheme';
|
||||
import { MainSectionTitle } from '@/ui/title/components/MainSectionTitle';
|
||||
import { SubSectionTitle } from '@/ui/title/components/SubSectionTitle';
|
||||
@ -27,7 +27,7 @@ export function SettingsExperience() {
|
||||
const { colorScheme, setColorScheme } = useColorScheme();
|
||||
|
||||
return (
|
||||
<NoTopBarContainer>
|
||||
<SubMenuTopBarContainer>
|
||||
<div>
|
||||
<StyledContainer>
|
||||
<MainSectionTitle>Experience</MainSectionTitle>
|
||||
@ -37,6 +37,6 @@ export function SettingsExperience() {
|
||||
</StyledSectionContainer>
|
||||
</StyledContainer>
|
||||
</div>
|
||||
</NoTopBarContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import styled from '@emotion/styled';
|
||||
import { EmailField } from '@/settings/profile/components/EmailField';
|
||||
import { NameFields } from '@/settings/profile/components/NameFields';
|
||||
import { ProfilePictureUploader } from '@/settings/profile/components/ProfilePictureUploader';
|
||||
import { NoTopBarContainer } from '@/ui/layout/components/NoTopBarContainer';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/components/SubMenuTopBarContainer';
|
||||
import { MainSectionTitle } from '@/ui/title/components/MainSectionTitle';
|
||||
import { SubSectionTitle } from '@/ui/title/components/SubSectionTitle';
|
||||
|
||||
@ -26,7 +26,7 @@ const StyledSectionContainer = styled.div`
|
||||
|
||||
export function SettingsProfile() {
|
||||
return (
|
||||
<NoTopBarContainer>
|
||||
<SubMenuTopBarContainer>
|
||||
<div>
|
||||
<StyledContainer>
|
||||
<MainSectionTitle>Profile</MainSectionTitle>
|
||||
@ -50,6 +50,6 @@ export function SettingsProfile() {
|
||||
</StyledSectionContainer>
|
||||
</StyledContainer>
|
||||
</div>
|
||||
</NoTopBarContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import styled from '@emotion/styled';
|
||||
|
||||
import { NameField } from '@/settings/workspace/components/NameField';
|
||||
import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader';
|
||||
import { NoTopBarContainer } from '@/ui/layout/components/NoTopBarContainer';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/components/SubMenuTopBarContainer';
|
||||
import { MainSectionTitle } from '@/ui/title/components/MainSectionTitle';
|
||||
import { SubSectionTitle } from '@/ui/title/components/SubSectionTitle';
|
||||
|
||||
@ -24,7 +24,7 @@ const StyledSectionContainer = styled.div`
|
||||
|
||||
export function SettingsWorksapce() {
|
||||
return (
|
||||
<NoTopBarContainer>
|
||||
<SubMenuTopBarContainer>
|
||||
<div>
|
||||
<StyledContainer>
|
||||
<MainSectionTitle>General</MainSectionTitle>
|
||||
@ -41,6 +41,6 @@ export function SettingsWorksapce() {
|
||||
</StyledSectionContainer>
|
||||
</StyledContainer>
|
||||
</div>
|
||||
</NoTopBarContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import { useRecoilState } from 'recoil';
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { Button } from '@/ui/button/components/Button';
|
||||
import { IconTrash } from '@/ui/icon';
|
||||
import { NoTopBarContainer } from '@/ui/layout/components/NoTopBarContainer';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/components/SubMenuTopBarContainer';
|
||||
import { MainSectionTitle } from '@/ui/title/components/MainSectionTitle';
|
||||
import { SubSectionTitle } from '@/ui/title/components/SubSectionTitle';
|
||||
import { WorkspaceInviteLink } from '@/workspace/components/WorkspaceInviteLink';
|
||||
@ -75,7 +75,7 @@ export function SettingsWorkspaceMembers() {
|
||||
};
|
||||
|
||||
return (
|
||||
<NoTopBarContainer>
|
||||
<SubMenuTopBarContainer>
|
||||
<StyledContainer>
|
||||
<MainSectionTitle>Members</MainSectionTitle>
|
||||
{workspace?.inviteHash && (
|
||||
@ -112,6 +112,6 @@ export function SettingsWorkspaceMembers() {
|
||||
/>
|
||||
))}
|
||||
</StyledContainer>
|
||||
</NoTopBarContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -8275,6 +8275,11 @@ css-loader@^6.5.1, css-loader@^6.7.1, css-loader@^6.7.3:
|
||||
postcss-value-parser "^4.2.0"
|
||||
semver "^7.3.8"
|
||||
|
||||
css-mediaquery@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/css-mediaquery/-/css-mediaquery-0.1.2.tgz#6a2c37344928618631c54bd33cedd301da18bea0"
|
||||
integrity sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==
|
||||
|
||||
css-minimizer-webpack-plugin@^3.2.0:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz#ab78f781ced9181992fe7b6e4f3422e76429878f"
|
||||
@ -11054,6 +11059,11 @@ human-signals@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
||||
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
|
||||
|
||||
hyphenate-style-name@^1.0.0:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
|
||||
integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==
|
||||
|
||||
iconv-lite@0.4.24, iconv-lite@^0.4.24:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
@ -13201,6 +13211,13 @@ markdown-to-jsx@^7.1.8:
|
||||
resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-7.2.1.tgz#87061fd3176ad926ef3d99493e5c57f6335e0c51"
|
||||
integrity sha512-9HrdzBAo0+sFz9ZYAGT5fB8ilzTW+q6lPocRxrIesMO+aB40V9MgFfbfMXxlGjf22OpRy+IXlvVaQenicdpgbg==
|
||||
|
||||
matchmediaquery@^0.3.0:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/matchmediaquery/-/matchmediaquery-0.3.1.tgz#8247edc47e499ebb7c58f62a9ff9ccf5b815c6d7"
|
||||
integrity sha512-Hlk20WQHRIm9EE9luN1kjRjYXAQToHOIAHPJn9buxBwuhfTHoKUcX+lXBbxc85DVQfXYbEQ4HcwQdd128E3qHQ==
|
||||
dependencies:
|
||||
css-mediaquery "^0.1.2"
|
||||
|
||||
mdast-util-definitions@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz#c5c1a84db799173b4dcf7643cda999e440c24db2"
|
||||
@ -15306,7 +15323,7 @@ prompts@^2.0.1, prompts@^2.4.0, prompts@^2.4.1, prompts@^2.4.2:
|
||||
kleur "^3.0.3"
|
||||
sisteransi "^1.0.5"
|
||||
|
||||
prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
prop-types@^15.6.1, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
@ -15850,6 +15867,16 @@ react-remove-scroll@2.5.4:
|
||||
use-callback-ref "^1.3.0"
|
||||
use-sidecar "^1.1.2"
|
||||
|
||||
react-responsive@^9.0.2:
|
||||
version "9.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-responsive/-/react-responsive-9.0.2.tgz#34531ca77a61e7a8775714016d21241df7e4205c"
|
||||
integrity sha512-+4CCab7z8G8glgJoRjAwocsgsv6VA2w7JPxFWHRc7kvz8mec1/K5LutNC2MG28Mn8mu6+bu04XZxHv5gyfT7xQ==
|
||||
dependencies:
|
||||
hyphenate-style-name "^1.0.0"
|
||||
matchmediaquery "^0.3.0"
|
||||
prop-types "^15.6.1"
|
||||
shallow-equal "^1.2.1"
|
||||
|
||||
react-router-dom@^6.4.4:
|
||||
version "6.14.1"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.14.1.tgz#0ad7ba7abdf75baa61169d49f096f0494907a36f"
|
||||
@ -16795,6 +16822,11 @@ shallow-clone@^3.0.0:
|
||||
dependencies:
|
||||
kind-of "^6.0.2"
|
||||
|
||||
shallow-equal@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da"
|
||||
integrity sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==
|
||||
|
||||
shebang-command@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
|
||||
|
||||
Reference in New Issue
Block a user