apps/web: add mobile tabs
This commit is contained in:
parent
07ca9a5b2a
commit
b87c22b6ed
@ -1,3 +1,5 @@
|
||||
import { NavigationContext } from '@/context/navigation';
|
||||
import { useContext } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Container = styled.div`
|
||||
@ -22,11 +24,31 @@ const TabButton = styled.button`
|
||||
`;
|
||||
|
||||
export function NavigationBar() {
|
||||
const { setCurrentTab, tabsList } = useContext(NavigationContext);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<TabButton>Tab1</TabButton>
|
||||
<TabButton>Tab2</TabButton>
|
||||
<TabButton>Tab3</TabButton>
|
||||
{tabsList.map(({ key, title }) => (
|
||||
<TabButton key={key} onClick={() => setCurrentTab(key)}>
|
||||
{title}
|
||||
</TabButton>
|
||||
))}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
function Tab({ children, visible }) {
|
||||
if (!visible) return null;
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
export function Tabs({ tabs }) {
|
||||
const { currentTab } = useContext(NavigationContext);
|
||||
|
||||
return tabs.map(({ Component, key }) => (
|
||||
<Tab key={key} visible={key === currentTab}>
|
||||
<Component />
|
||||
</Tab>
|
||||
));
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import Header from './Header';
|
||||
import { AppMenu } from './Menu';
|
||||
import { NavigationBar } from './Navigation';
|
||||
import { screens } from '@/config/ui';
|
||||
import { NavigationProvider } from '@/context/navigation';
|
||||
import { max, min } from '@/styles/mq';
|
||||
import dynamic from 'next/dynamic';
|
||||
import styled from 'styled-components';
|
||||
@ -24,13 +25,13 @@ const Main = styled.main`
|
||||
|
||||
export default function Layout({ children, user }) {
|
||||
return (
|
||||
<>
|
||||
<NavigationProvider>
|
||||
<Header />
|
||||
{user?.admin ? <AppMenu /> : false}
|
||||
<Main>{children}</Main>
|
||||
<MediaQuery maxWidth={screens.laptop}>
|
||||
<NavigationBar />
|
||||
</MediaQuery>
|
||||
</>
|
||||
</NavigationProvider>
|
||||
);
|
||||
}
|
||||
|
||||
27
apps/web/context/navigation.tsx
Normal file
27
apps/web/context/navigation.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { createContext, useMemo, useState } from 'react';
|
||||
|
||||
type Tab = {
|
||||
key: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
type NavigationContextType = {
|
||||
currentTab: string;
|
||||
setCurrentTab: (tab: string) => void;
|
||||
setTabsList: (list: Tab[]) => void;
|
||||
tabsList: Tab[];
|
||||
};
|
||||
|
||||
export const NavigationContext = createContext<NavigationContextType>({} as NavigationContextType);
|
||||
|
||||
export function NavigationProvider({ children }: { readonly children: React.ReactNode }) {
|
||||
const [currentTab, setCurrentTab] = useState('');
|
||||
const [tabsList, setTabsList] = useState<Tab[]>([]);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({ currentTab, setCurrentTab, setTabsList, tabsList }),
|
||||
[currentTab, setCurrentTab, setTabsList, tabsList]
|
||||
);
|
||||
|
||||
return <NavigationContext.Provider value={value}>{children}</NavigationContext.Provider>;
|
||||
}
|
||||
@ -33,6 +33,7 @@
|
||||
"radash": "^11.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-responsive": "^10.0.0",
|
||||
"styled-components": "^5.3.11",
|
||||
"superjson": "^2.2.1",
|
||||
"tools": "workspace:*",
|
||||
|
||||
@ -1,11 +1,39 @@
|
||||
import initializeApollo from '@/apollo/client';
|
||||
import * as Calculation from '@/Components/Calculation';
|
||||
import { Error } from '@/Components/Common/Error';
|
||||
import { Tabs } from '@/Components/Layout/Navigation';
|
||||
import { screens } from '@/config/ui';
|
||||
import { NavigationContext } from '@/context/navigation';
|
||||
import * as hooks from '@/process/hooks';
|
||||
import { getPageTitle } from '@/utils/page';
|
||||
import { makeGetUserType } from '@/utils/user';
|
||||
import { dehydrate, QueryClient } from '@tanstack/react-query';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Head from 'next/head';
|
||||
import { useContext, useEffect } from 'react';
|
||||
|
||||
const MediaQuery = dynamic(() => import('react-responsive'), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
Component: () => <Calculation.Settings />,
|
||||
key: 'settings',
|
||||
title: 'Расчет',
|
||||
},
|
||||
{
|
||||
Component: () => <Calculation.Form prune={['unlimited']} />,
|
||||
key: 'form',
|
||||
title: 'Параметры',
|
||||
},
|
||||
|
||||
{
|
||||
Component: () => <Calculation.Output />,
|
||||
key: 'output',
|
||||
title: 'Результаты',
|
||||
},
|
||||
];
|
||||
|
||||
function Content() {
|
||||
hooks.useSentryScope();
|
||||
@ -13,15 +41,32 @@ function Content() {
|
||||
hooks.useInsuranceData();
|
||||
hooks.useReactions();
|
||||
|
||||
const { setCurrentTab, setTabsList } = useContext(NavigationContext);
|
||||
|
||||
useEffect(() => {
|
||||
setTabsList(tabs);
|
||||
setCurrentTab('settings');
|
||||
}, [setCurrentTab, setTabsList]);
|
||||
|
||||
return (
|
||||
<Calculation.Layout>
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle()}</title>
|
||||
</Head>
|
||||
<Calculation.Form prune={['unlimited']} />
|
||||
<Calculation.Settings />
|
||||
<Calculation.Output />
|
||||
</Calculation.Layout>
|
||||
<MediaQuery maxWidth={screens.laptop}>
|
||||
{(match) => {
|
||||
if (match) return <Tabs tabs={tabs} />;
|
||||
|
||||
return (
|
||||
<Calculation.Layout>
|
||||
<Calculation.Form prune={['unlimited']} />
|
||||
<Calculation.Settings />
|
||||
<Calculation.Output />
|
||||
</Calculation.Layout>
|
||||
);
|
||||
}}
|
||||
</MediaQuery>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,39 @@
|
||||
import initializeApollo from '@/apollo/client';
|
||||
import * as Calculation from '@/Components/Calculation';
|
||||
import { Error } from '@/Components/Common/Error';
|
||||
import { Tabs } from '@/Components/Layout/Navigation';
|
||||
import { screens } from '@/config/ui';
|
||||
import { NavigationContext } from '@/context/navigation';
|
||||
import * as hooks from '@/process/hooks';
|
||||
import { getPageTitle } from '@/utils/page';
|
||||
import { makeGetUserType } from '@/utils/user';
|
||||
import { dehydrate, QueryClient } from '@tanstack/react-query';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Head from 'next/head';
|
||||
import { useContext, useEffect } from 'react';
|
||||
|
||||
const MediaQuery = dynamic(() => import('react-responsive'), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
Component: () => <Calculation.Settings />,
|
||||
key: 'settings',
|
||||
title: 'Расчет',
|
||||
},
|
||||
{
|
||||
Component: () => <Calculation.Form />,
|
||||
key: 'form',
|
||||
title: 'Параметры',
|
||||
},
|
||||
|
||||
{
|
||||
Component: () => <Calculation.Output />,
|
||||
key: 'output',
|
||||
title: 'Результаты',
|
||||
},
|
||||
];
|
||||
|
||||
function Content() {
|
||||
hooks.useSentryScope();
|
||||
@ -14,15 +42,32 @@ function Content() {
|
||||
hooks.useInsuranceData();
|
||||
hooks.useReactions();
|
||||
|
||||
const { setCurrentTab, setTabsList } = useContext(NavigationContext);
|
||||
|
||||
useEffect(() => {
|
||||
setTabsList(tabs);
|
||||
setCurrentTab('settings');
|
||||
}, [setCurrentTab, setTabsList]);
|
||||
|
||||
return (
|
||||
<Calculation.Layout>
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Без ограничений')}</title>
|
||||
</Head>
|
||||
<Calculation.Form />
|
||||
<Calculation.Settings />
|
||||
<Calculation.Output />
|
||||
</Calculation.Layout>
|
||||
<MediaQuery maxWidth={screens.laptop}>
|
||||
{(match) => {
|
||||
if (match) return <Tabs tabs={tabs} />;
|
||||
|
||||
return (
|
||||
<Calculation.Layout>
|
||||
<Calculation.Form />
|
||||
<Calculation.Settings />
|
||||
<Calculation.Output />
|
||||
</Calculation.Layout>
|
||||
);
|
||||
}}
|
||||
</MediaQuery>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
42
pnpm-lock.yaml
generated
42
pnpm-lock.yaml
generated
@ -177,6 +177,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0(react@18.2.0)
|
||||
react-responsive:
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0(react@18.2.0)
|
||||
styled-components:
|
||||
specifier: ^5.3.11
|
||||
version: 5.3.11(@babel/core@7.23.9)(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0)
|
||||
@ -5899,6 +5902,10 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
/css-mediaquery@0.1.2:
|
||||
resolution: {integrity: sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==}
|
||||
dev: false
|
||||
|
||||
/css-to-react-native@3.2.0:
|
||||
resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==}
|
||||
dependencies:
|
||||
@ -6956,7 +6963,7 @@ packages:
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.3.3)
|
||||
'@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.3.3)
|
||||
eslint: 8.57.0
|
||||
jest: 29.7.0(@types/node@20.11.20)(ts-node@10.9.2)
|
||||
jest: 29.7.0(@types/node@18.19.18)(ts-node@10.9.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
@ -8200,7 +8207,7 @@ packages:
|
||||
'@graphql-tools/json-file-loader': 7.4.18(graphql@16.8.1)
|
||||
'@graphql-tools/load': 7.8.14(graphql@16.8.1)
|
||||
'@graphql-tools/merge': 8.4.2(graphql@16.8.1)
|
||||
'@graphql-tools/url-loader': 7.17.18(@types/node@18.19.18)(graphql@16.8.1)
|
||||
'@graphql-tools/url-loader': 7.17.18(@types/node@20.11.20)(graphql@16.8.1)
|
||||
'@graphql-tools/utils': 9.2.1(graphql@16.8.1)
|
||||
cosmiconfig: 8.0.0
|
||||
graphql: 16.8.1
|
||||
@ -8474,6 +8481,10 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/hyphenate-style-name@1.0.5:
|
||||
resolution: {integrity: sha512-fedL7PRwmeVkgyhu9hLeTBaI6wcGk7JGJswdaRsa5aUbkXI1kr1xZwTPBtaYPpwf56878iDek6VbVnuWMebJmw==}
|
||||
dev: false
|
||||
|
||||
/iconv-lite@0.4.24:
|
||||
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -9320,7 +9331,7 @@ packages:
|
||||
pretty-format: 29.7.0
|
||||
slash: 3.0.0
|
||||
strip-json-comments: 3.1.1
|
||||
ts-node: 10.9.2(@types/node@20.11.20)(typescript@5.3.3)
|
||||
ts-node: 10.9.2(@types/node@18.19.18)(typescript@5.3.3)
|
||||
transitivePeerDependencies:
|
||||
- babel-plugin-macros
|
||||
- supports-color
|
||||
@ -10223,6 +10234,12 @@ packages:
|
||||
object-visit: 1.0.1
|
||||
dev: true
|
||||
|
||||
/matchmediaquery@0.4.2:
|
||||
resolution: {integrity: sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==}
|
||||
dependencies:
|
||||
css-mediaquery: 0.1.2
|
||||
dev: false
|
||||
|
||||
/media-typer@0.3.0:
|
||||
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@ -11829,6 +11846,19 @@ packages:
|
||||
/react-is@18.2.0:
|
||||
resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
|
||||
|
||||
/react-responsive@10.0.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-N6/UiRLGQyGUqrarhBZmrSmHi2FXSD++N5VbSKsBBvWfG0ZV7asvUBluSv5lSzdMyEVjzZ6Y8DL4OHABiztDOg==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
dependencies:
|
||||
hyphenate-style-name: 1.0.5
|
||||
matchmediaquery: 0.4.2
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
shallow-equal: 3.1.0
|
||||
dev: false
|
||||
|
||||
/react@18.2.0:
|
||||
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -12438,6 +12468,10 @@ packages:
|
||||
/setprototypeof@1.2.0:
|
||||
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
||||
|
||||
/shallow-equal@3.1.0:
|
||||
resolution: {integrity: sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==}
|
||||
dev: false
|
||||
|
||||
/shallowequal@1.1.0:
|
||||
resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==}
|
||||
dev: false
|
||||
@ -13241,7 +13275,7 @@ packages:
|
||||
'@babel/core': 7.23.9
|
||||
bs-logger: 0.2.6
|
||||
fast-json-stable-stringify: 2.1.0
|
||||
jest: 29.7.0(@types/node@20.11.20)(ts-node@10.9.2)
|
||||
jest: 29.7.0(@types/node@18.19.18)(ts-node@10.9.2)
|
||||
jest-util: 29.7.0
|
||||
json5: 2.2.3
|
||||
lodash.memoize: 4.1.2
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user