Skip to content

Commit

Permalink
docs: create integrations.md
Browse files Browse the repository at this point in the history
- Удалил точечные упоминание про VK Mini Apps за счёт новой
   доки integrations_vk_mini_apps.md.
- Поправил `styleguide/Components/Section` – не применялся наш
   кастомный `SectionRenderer`.
- Расширил конфигурацию страниц Styleguide параметром `contentTitle`.
  • Loading branch information
inomdzhon committed Jul 25, 2023
1 parent 496d078 commit 7c8426e
Show file tree
Hide file tree
Showing 10 changed files with 284 additions and 194 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ export const AdaptivityProvider = ({
// TODO [>=6]: удалить использование хука (#5049)
/* eslint-disable @typescript-eslint/naming-convention */
const LEGACY_isPerhapsPropsByBridgeTypeAdaptive =
viewWidth !== undefined &&
viewHeight !== undefined &&
sizeX !== undefined &&
sizeY !== undefined;
viewWidth !== undefined && viewHeight !== undefined;
const LEGACY_isPerhapsPropsByBridgeTypeForceMobile =
viewWidth !== undefined && sizeX !== undefined && sizeY !== undefined;
const LEGACY_disableInternalUseBridgeAdaptivity =
Expand Down
8 changes: 0 additions & 8 deletions packages/vkui/src/components/PanelHeaderButton/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,3 @@ import { Icon28SettingsOutline, Icon28Notifications } from '@vkontakte/icons';
}
/>;
```

<!--TODO [>=6]: удалить информацию про VK Mini Apps-->

**VK Mini Apps**

Если вы разрабатываете приложение на платформе [VK Mini Apps](https://vk.com/vkappsdev), то вам
будет недоступен слот `after`, т.к. на их месте отображаются нативные кнопки для управления
приложением.
12 changes: 0 additions & 12 deletions packages/vkui/src/components/View/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ const [activePanel, setActivePanel] = useState('panel1');
- Передать во `View` коллбек `onSwipeBack` — он сработает при завершении анимации свайпа. Поменяйте в нем `activePanel` и обновите `history`.
- Передать во `View` проп `history` — массив из id панелей в порядке открытия. Например, если пользователь из `main` перешел в `profile`, а оттуда попал в `education`, то `history=['main', 'profile', 'education']`.
- Обернуть ваше приложение в `ConfigProvider` — он определит, открыто приложение в webview клиента VK или в браузере (там есть свой swipe back, который будет конфликтовать с нашим). Для проверки в браузере форсируйте определение webview: `<ConfigProvider isWebView>`.
<!--TODO [>=6]: удалить этот пункт, т.к. это информацию про VK Mini Apps-->
- На первой панели должен включаться свайпбек нативного клиента, чтобы пользователь смог выйти из приложения — для этого используют `vk-bridge`. **Если вы делаете стандартное мини-приложение ВКонтакте,** при переходах отправляйте [событие `VKWebAppSetSwipeSettings`](https://dev.vk.com/bridge/VKWebAppSetSwipeSettings) с `history: true` на первой панели или `history: false` на других. **Если тип вашего мини-приложения — `WebviewType.INTERNAL`,** отправляйте событие `VKWebAppEnableSwipeBack` при переходе на первую панель и событие `VKWebAppDisableSwipeBack` при переходе на любую другух.

**Блокировка свайпа (вариант #1)**

Expand All @@ -68,15 +66,12 @@ const App = () => {
};
```

<!--TODO [>=6]: удалить информацию про VK Mini Apps-->

```jsx
import vkBridge from '@vkontakte/vk-bridge';

const App = () => {
const [history, setHistory] = useState(['main']);
const activePanel = history[history.length - 1];
const isFirst = history.length === 1;

const go = React.useCallback((panel) => {
setHistory((prevHistory) => [...prevHistory, panel]);
Expand All @@ -88,13 +83,6 @@ const App = () => {
const handleProfileClick = React.useCallback(() => go('profile'), [go]);
const handleSettingsClick = React.useCallback(() => go('settings'), [go]);

React.useEffect(() => {
// В стандартных мини-приложениях делайте так:
vkBridge.send('VKWebAppSetSwipeSettings', { history: isFirst });
// В мини-приложениях `WebviewType.INTERNAL` делайте так:
vkBridge.send(isFirst ? 'VKWebAppEnableSwipeBack' : 'VKWebAppDisableSwipeBack');
}, [isFirst]);

const [userName, setUserName] = React.useState('');
const [popoutWithRestriction, setPopoutWithRestriction] = React.useState(null);

Expand Down
17 changes: 14 additions & 3 deletions styleguide/Components/Section/SectionRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,18 @@ import SectionHeading from '@rsg-components/SectionHeading';
import PropTypes from 'prop-types';

export const SectionRenderer = (allProps) => {
const { title, name, slug, content, components, sections, depth, description, pagePerSection } =
allProps;
const {
title,
name,
slug,
contentTitle,
content,
components,
sections,
depth,
description,
pagePerSection,
} = allProps;

return (
<section data-testid={`section-${slug}`}>
Expand All @@ -17,7 +27,7 @@ export const SectionRenderer = (allProps) => {
pagePerSection={pagePerSection}
slotProps={allProps}
>
{title || name}
{contentTitle || title || name}
</SectionHeading>
)}
{description && <Markdown text={description} />}
Expand All @@ -32,6 +42,7 @@ SectionRenderer.propTypes = {
name: PropTypes.string,
description: PropTypes.string,
slug: PropTypes.string.isRequired,
contentTitle: PropTypes.string,
content: PropTypes.node,
components: PropTypes.node,
sections: PropTypes.node,
Expand Down
4 changes: 3 additions & 1 deletion styleguide/Components/Section/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import React from 'react';
import Components from '@rsg-components/Components';
import { useStyleGuideContext } from '@rsg-components/Context';
import Examples from '@rsg-components/Examples';
import SectionRenderer from '@rsg-components/Section/SectionRenderer';
import Sections from '@rsg-components/Sections';
import PropTypes from 'prop-types';
import SectionRenderer from './SectionRenderer';

const Section = ({ section, depth }) => {
const {
Expand All @@ -14,6 +14,7 @@ const Section = ({ section, depth }) => {
name,
slug,
filepath,
contentTitle,
content,
components,
sections,
Expand Down Expand Up @@ -45,6 +46,7 @@ const Section = ({ section, depth }) => {
title={title}
slug={slug}
filepath={filepath}
contentTitle={contentTitle}
content={contentJsx}
components={componentsJsx}
sections={sectionsJsx}
Expand Down
13 changes: 13 additions & 0 deletions styleguide/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,19 @@ const baseConfig = {
},
],
},
{
name: 'Интеграции',
sectionDepth: 1,
expand: true,
sections: [
{
title: 'VK Mini Apps',
name: 'integrations-vk-mini-apps',
contentTitle: 'Интеграция с VK Mini Apps',
content: './pages/integrations_vk_mini_apps.md',
},
],
},
{
name: 'Прочее',
expand: true,
Expand Down
233 changes: 233 additions & 0 deletions styleguide/pages/integrations_vk_mini_apps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
[VK Mini Apps](https://vk.com/miniapps) – это кроссплатформенные мини-приложения в экосистеме социальной сети [ВКонтакте](https://vk.com/).

**VKUI** изначально создавался для создания таких приложений. Несмотря на то, что сейчас **VKUI**
это уже больше чем мини-приложения, он до сих пор хорошо подходит для их создания. Ниже
собраны советы по интеграции c **VK Mini Apps**.

Про саму разработку **VK Mini Apps** читайте здесь https://dev.vk.com/mini-apps/overview

## Конфигурация VKUI

> Советуем использовать шаблон https://github.com/VKCOM/create-vk-mini-app. В ней конфигурация
> описанная ниже есть по умолчанию.
Для начала устанавливаем библиотеки:

- [@vkontakte/vk-bridge](https://www.npmjs.com/package/@vkontakte/vk-bridge) **>= 2.9.0**
- [@vkontakte/vk-bridge-react](https://www.npmjs.com/package/@vkontakte/vk-bridge) **>= 1.0.0**

```sh
# для примера используется пакетный менеджер yarn
yarn add @vkontakte/vk-bridge @vkontakte/vk-bridge-react
```

Далее подписываемся на изменения из **VK Bridge**. Для этого используем готовые хуки из
[@vkontakte/vk-bridge-react](https://www.npmjs.com/package/@vkontakte/vk-bridge).

```tsx static
import React from 'react';
import ReactDOM from 'react-dom';
import vkBridge, { parseURLSearchParamsForGetLaunchParams } from '@vkontakte/vk-bridge';
import { useAppearance, useInsets, useAdaptivity } from '@vkontakte/vk-bridge-react';
import { Platform, ConfigProvider, AdaptivityProvider, AppRoot } from '@vkontakte/vkui';
import { transformVKBridgeAdaptivity } from './transformers/transformVKBridgeAdaptivity';
import { App } from './App';

// Инициализируем VK Mini App
vkBridge.send('VKWebAppInit');

const Root = () => {
const vkBridgeAppearance = useAppearance() || undefined; // Вместо undefined можно задать значение по умолчанию
const vkBridgeInsets = useInsets() || undefined; // Вместо undefined можно задать значение по умолчанию
const vkBridgeAdaptivityProps = transformVKBridgeAdaptivity(useAdaptivity()); // Конвертируем значения из VK Bridge в параметры AdaptivityProvider
const { vk_platform } = parseURLSearchParamsForGetLaunchParams(window.location.search); // [опционально] Платформа может передаваться через URL (см. https://dev.vk.com/mini-apps/development/launch-params#vk_platform)

return (
<ConfigProvider
appearance={vkBridgeAppearance}
platform={vk_platform === 'desktop_web' ? 'vkcom' : undefined}
isWebView={vkBridge.isWebView()}
hasCustomPanelHeaderAfter={true} // Резервируем правую часть PanelHeader под кнопки управления VK Mini Apps. Через параметр customPanelHeaderAfterMinWidth можно регулировать ширину этой области (по умолчанию, используется 90)
>
<AdaptivityProvider {...vkBridgeAdaptivityProps}>
{/* Для VK Mini Apps рекомендуем использовать mode="full" (выставлен по умолчанию, для примера указан явно) */}
<AppRoot mode="full" safeAreaInsets={vkBridgeInsets}>
<App />
</AppRoot>
</AdaptivityProvider>
</ConfigProvider>
);
};

ReactDOM.render(<Root />, document.getElementById('root'));
```

Файл `./transformers/transformVKBridgeAdaptivity.ts`

```ts static
import {
type AdaptivityProps,
getViewWidthByViewportWidth,
getViewHeightByViewportHeight,
ViewWidth,
SizeType,
} from '@vkontakte/vkui';
import type { UseAdaptivity } from '@vkontakte/vk-bridge-react';

/**
* Требуется конвертировать данные из VK Bridge в те, что принимает AdaptivityProvider из VKUI.
*/
export const transformVKBridgeAdaptivity = (vkBridgeAdaptivity: UseAdaptivity): AdaptivityProps => {
let viewWidth;
let viewHeight;
let sizeX;
let sizeY;

if (vkBridgeAdaptivity.type === 'adaptive') {
const { viewportWidth, viewportHeight } = vkBridgeAdaptivity;
viewWidth = getViewWidthByViewportWidth(viewportWidth);
viewHeight = getViewHeightByViewportHeight(viewportHeight);
} else if (
vkBridgeAdaptivity.type === 'force_mobile' ||
vkBridgeAdaptivity.type === 'force_mobile_compact'
) {
viewWidth = ViewWidth.MOBILE;
sizeX = SizeType.COMPACT;

if (vkBridgeAdaptivity.type === 'force_mobile_compact') {
sizeY = SizeType.COMPACT;
} else {
sizeY = SizeType.REGULAR;
}
}

return { viewWidth, viewHeight, sizeX, sizeY };
};
```

## Навигация

### [PanelHeader](https://vkcom.github.io/VKUI/#/PanelHeader)

Мини-приложению доступна почти вся площадь экрана, поэтому для корректной работы навигации
необходимо использовать компонент [PanelHeader](https://vkcom.github.io/VKUI/#/PanelHeader) на каждом
экране приложения. Он должен содержать название приложения и значок **Назад** (см.
[PanelHeaderBack](https://vkcom.github.io/VKUI/#/PanelHeaderBack)) на тех экранах, где тот
требуется.

> _Правый верхний угол_ [PanelHeader](https://vkcom.github.io/VKUI/#/PanelHeader) зарезервирован для
> нативного бара с кнопками управления мини-приложенияем, поэтому не рекомендуется использовать
> параметр `after`.
>
> Чтобы автоматически скрывать `after` у [PanelHeader](https://vkcom.github.io/VKUI/#/PanelHeader),
> в [ConfigProvider](https://vkcom.github.io/VKUI/#/ConfigProvider) существует параметр
> `hasCustomPanelHeaderAfter`.
### [View](https://vkcom.github.io/VKUI/#/View)

На стартовой странице мини-приложения необходимо включать свайпбек нативного клиента, чтобы
пользователь смог выйти из мини-приложения. Для этого нужно вызывать определенные методы
**VK Bridge** в зависимости от типа мини-приложения:

- Если вы делаете _стандартное_ мини-приложение ВКонтакте, то при переходах отправляйте
[VKWebAppSetSwipeSettings](https://dev.vk.com/bridge/VKWebAppSetSwipeSettings) с `history: true`
на первой панели и `history: false` на других.
- Если вы делаете _внутреннее_ мини-приложения ВКонтакте, то отправляйте событие
[VKWebAppEnableSwipeBack](https://dev.vk.com/bridge/VKWebAppEnableSwipeBack) при переходе на
первую панель и событие [VKWebAppDisableSwipeBack](https://dev.vk.com/bridge/VKWebAppDisableSwipeBack)
при переходе на любую другую.

```tsx static
import vkBridge from '@vkontakte/vk-bridge';

const SomeViews = () => {
const [history, setHistory] = useState(['main']);
const activePanel = history[history.length - 1];
const isFirst = history.length === 1;

const go = React.useCallback((panel) => setHistory((prevHistory) => [...prevHistory, panel]), []);
const goBack = React.useCallback(() => setHistory((prevHistory) => prevHistory.slice(0, -1)), []);
const handleProfileClick = () => go('profile');
const handleMainClick = () => go('main');

React.useEffect(() => {
// Для стандартных мини-приложений делайте так:
vkBridge.send('VKWebAppSetSwipeSettings', { history: isFirst });
// Для внутренних мини-приложений делайте так:
vkBridge.send(isFirst ? 'VKWebAppEnableSwipeBack' : 'VKWebAppDisableSwipeBack');
}, [isFirst]);

return (
<View history={history} activePanel={activePanel} onSwipeBack={goBack}>
<Panel id="main">
<div onClick={handleProfileClick}>Main</div>
</Panel>
<Panel id="profile">
<div onClick={handleMainClick}>Profile</div>
</Panel>
</View>
);
};
```

> На устройствах с Android нажатие кнопки **Назад** вызывает в WebView событие [history.back](https://developer.mozilla.org/ru/docs/Web/API/Window/history).
> По нажатию этой кнопки официальное приложение сделает возврат на предыдущую страницу вашего
> приложения или закроет его, если вернуться невозможно. Поэтому для корректной навигации необходимо
> обрабатывать нажатие аппаратной клавиши в мини-приложении и реализовывать роутинг. Например при
> помощи библиотеки `react-router`.
## Виброотклик (Taptic Engine)

Для улучшения пользовательского опыта при взаимодействие с вашим мини-приложением, в **VK Bridge**
существует события вызова вибрации на устройстве, если те поддерживается.

[@vkontakte/vk-bridge-react](https://www.npmjs.com/package/@vkontakte/vk-bridge) предоставляет
функцию `runTapticImpactOccurred`, которая отправляет событие [VKWebAppTapticImpactOccurred](https://dev.vk.com/bridge/VKWebAppTapticImpactOccurred),
как раз заранее проверяя, что возможность вызова вибрации доступна на устройстве.

Виброотликом можно воспользоваться, например, при использовании компонента [PullToRefresh](https://vkcom.github.io/VKUI/#/PullToRefresh)
на `onRefresh`.

<!--TODO [>=6] Удалить в коде ниже возврат выполнения функции и комментарий про VKUI v5 -->

```tsx static
import * as React from 'react';
import { runTapticImpactOccurred } from '@vkontakte/vk-bridge-react';

const Users = () => {
const [users, setUsers] = React.useState([
{ id: 1, name: 'Placeholder', avatarUrl: 'https://placehold.co/100' },
]);
const [fetching, setFetching] = React.useState(false);

const onRefresh = React.useCallback(() => {
setFetching(true);
// Вызываем виброотклик
// > Note: в VKUI v5 необходимо возвращать результат выполнения, чтобы
// > чтобы избежать двойного вызова runTapticImpactOccurred()
return runTapticImpactOccurred('light');
}, []);

return (
<View activePanel="users">
<Panel id="users">
<PanelHeader>Пользователи</PanelHeader>

<PullToRefresh onRefresh={onRefresh} isFetching={fetching}>
<Group>
<List>
{users.map(({ id, name, avatarUrl }, i) => {
return (
<Cell key={i} before={<Avatar src={avatarUrl} />}>
{name}
</Cell>
);
})}
</List>
</Group>
</PullToRefresh>
</Panel>
</View>
);
};
```
Loading

0 comments on commit 7c8426e

Please sign in to comment.