@gtkx/navigation 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -0
- package/dist/drawer/create-drawer-navigator.d.ts +30 -0
- package/dist/drawer/create-drawer-navigator.d.ts.map +1 -0
- package/dist/drawer/create-drawer-navigator.js +36 -0
- package/dist/drawer/create-drawer-navigator.js.map +1 -0
- package/dist/drawer/drawer-collapsed-context.d.ts +4 -0
- package/dist/drawer/drawer-collapsed-context.d.ts.map +1 -0
- package/dist/drawer/drawer-collapsed-context.js +4 -0
- package/dist/drawer/drawer-collapsed-context.js.map +1 -0
- package/dist/drawer/drawer-item-list.d.ts +6 -0
- package/dist/drawer/drawer-item-list.d.ts.map +1 -0
- package/dist/drawer/drawer-item-list.js +40 -0
- package/dist/drawer/drawer-item-list.js.map +1 -0
- package/dist/drawer/drawer-router.d.ts +5 -0
- package/dist/drawer/drawer-router.d.ts.map +1 -0
- package/dist/drawer/drawer-router.js +49 -0
- package/dist/drawer/drawer-router.js.map +1 -0
- package/dist/drawer/drawer-status.d.ts +4 -0
- package/dist/drawer/drawer-status.d.ts.map +1 -0
- package/dist/drawer/drawer-status.js +11 -0
- package/dist/drawer/drawer-status.js.map +1 -0
- package/dist/drawer/drawer-view.d.ts +11 -0
- package/dist/drawer/drawer-view.d.ts.map +1 -0
- package/dist/drawer/drawer-view.js +67 -0
- package/dist/drawer/drawer-view.js.map +1 -0
- package/dist/drawer/types.d.ts +80 -0
- package/dist/drawer/types.d.ts.map +1 -0
- package/dist/drawer/types.js +2 -0
- package/dist/drawer/types.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/navigation-container.d.ts +15 -0
- package/dist/navigation-container.d.ts.map +1 -0
- package/dist/navigation-container.js +11 -0
- package/dist/navigation-container.js.map +1 -0
- package/dist/shared/header-bar.d.ts +11 -0
- package/dist/shared/header-bar.d.ts.map +1 -0
- package/dist/shared/header-bar.js +11 -0
- package/dist/shared/header-bar.js.map +1 -0
- package/dist/shared/routes.d.ts +6 -0
- package/dist/shared/routes.d.ts.map +1 -0
- package/dist/shared/routes.js +16 -0
- package/dist/shared/routes.js.map +1 -0
- package/dist/shared/scene-page.d.ts +13 -0
- package/dist/shared/scene-page.d.ts.map +1 -0
- package/dist/shared/scene-page.js +5 -0
- package/dist/shared/scene-page.js.map +1 -0
- package/dist/shared/types.d.ts +16 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +2 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/shared/use-loaded-routes.d.ts +3 -0
- package/dist/shared/use-loaded-routes.d.ts.map +1 -0
- package/dist/shared/use-loaded-routes.js +11 -0
- package/dist/shared/use-loaded-routes.js.map +1 -0
- package/dist/shared/use-pop-to-top-on-blur.d.ts +10 -0
- package/dist/shared/use-pop-to-top-on-blur.d.ts.map +1 -0
- package/dist/shared/use-pop-to-top-on-blur.js +27 -0
- package/dist/shared/use-pop-to-top-on-blur.js.map +1 -0
- package/dist/split-view/create-split-view-navigator.d.ts +33 -0
- package/dist/split-view/create-split-view-navigator.d.ts.map +1 -0
- package/dist/split-view/create-split-view-navigator.js +20 -0
- package/dist/split-view/create-split-view-navigator.js.map +1 -0
- package/dist/split-view/split-view-router.d.ts +5 -0
- package/dist/split-view/split-view-router.d.ts.map +1 -0
- package/dist/split-view/split-view-router.js +42 -0
- package/dist/split-view/split-view-router.js.map +1 -0
- package/dist/split-view/split-view.d.ts +12 -0
- package/dist/split-view/split-view.d.ts.map +1 -0
- package/dist/split-view/split-view.js +62 -0
- package/dist/split-view/split-view.js.map +1 -0
- package/dist/split-view/types.d.ts +43 -0
- package/dist/split-view/types.d.ts.map +1 -0
- package/dist/split-view/types.js +2 -0
- package/dist/split-view/types.js.map +1 -0
- package/dist/stack/create-stack-navigator.d.ts +30 -0
- package/dist/stack/create-stack-navigator.d.ts.map +1 -0
- package/dist/stack/create-stack-navigator.js +16 -0
- package/dist/stack/create-stack-navigator.js.map +1 -0
- package/dist/stack/stack-pages.d.ts +11 -0
- package/dist/stack/stack-pages.d.ts.map +1 -0
- package/dist/stack/stack-pages.js +80 -0
- package/dist/stack/stack-pages.js.map +1 -0
- package/dist/stack/stack-view.d.ts +14 -0
- package/dist/stack/stack-view.d.ts.map +1 -0
- package/dist/stack/stack-view.js +101 -0
- package/dist/stack/stack-view.js.map +1 -0
- package/dist/stack/sync-navigation-stack.d.ts +7 -0
- package/dist/stack/sync-navigation-stack.d.ts.map +1 -0
- package/dist/stack/sync-navigation-stack.js +69 -0
- package/dist/stack/sync-navigation-stack.js.map +1 -0
- package/dist/stack/types.d.ts +72 -0
- package/dist/stack/types.d.ts.map +1 -0
- package/dist/stack/types.js +2 -0
- package/dist/stack/types.js.map +1 -0
- package/dist/static-navigation.d.ts +13 -0
- package/dist/static-navigation.d.ts.map +1 -0
- package/dist/static-navigation.js +10 -0
- package/dist/static-navigation.js.map +1 -0
- package/dist/tabs/create-tab-navigator.d.ts +30 -0
- package/dist/tabs/create-tab-navigator.d.ts.map +1 -0
- package/dist/tabs/create-tab-navigator.js +16 -0
- package/dist/tabs/create-tab-navigator.js.map +1 -0
- package/dist/tabs/tab-view.d.ts +11 -0
- package/dist/tabs/tab-view.d.ts.map +1 -0
- package/dist/tabs/tab-view.js +52 -0
- package/dist/tabs/tab-view.js.map +1 -0
- package/dist/tabs/types.d.ts +67 -0
- package/dist/tabs/types.d.ts.map +1 -0
- package/dist/tabs/types.js +2 -0
- package/dist/tabs/types.js.map +1 -0
- package/dist/theme.d.ts +18 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +15 -0
- package/dist/theme.js.map +1 -0
- package/package.json +71 -0
- package/src/drawer/create-drawer-navigator.tsx +123 -0
- package/src/drawer/drawer-collapsed-context.ts +5 -0
- package/src/drawer/drawer-item-list.tsx +74 -0
- package/src/drawer/drawer-router.ts +90 -0
- package/src/drawer/drawer-status.ts +19 -0
- package/src/drawer/drawer-view.tsx +171 -0
- package/src/drawer/types.ts +142 -0
- package/src/index.ts +83 -0
- package/src/navigation-container.tsx +35 -0
- package/src/shared/header-bar.tsx +34 -0
- package/src/shared/routes.ts +25 -0
- package/src/shared/scene-page.tsx +27 -0
- package/src/shared/types.ts +17 -0
- package/src/shared/use-loaded-routes.ts +14 -0
- package/src/shared/use-pop-to-top-on-blur.ts +45 -0
- package/src/split-view/create-split-view-navigator.tsx +99 -0
- package/src/split-view/split-view-router.ts +73 -0
- package/src/split-view/split-view.tsx +137 -0
- package/src/split-view/types.ts +100 -0
- package/src/stack/create-stack-navigator.tsx +80 -0
- package/src/stack/stack-pages.ts +140 -0
- package/src/stack/stack-view.tsx +202 -0
- package/src/stack/sync-navigation-stack.ts +117 -0
- package/src/stack/types.ts +131 -0
- package/src/static-navigation.tsx +26 -0
- package/src/tabs/create-tab-navigator.tsx +69 -0
- package/src/tabs/tab-view.tsx +126 -0
- package/src/tabs/types.ts +120 -0
- package/src/theme.ts +31 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DefaultNavigatorOptions,
|
|
3
|
+
Descriptor,
|
|
4
|
+
NavigationHelpers,
|
|
5
|
+
NavigationProp,
|
|
6
|
+
ParamListBase,
|
|
7
|
+
RouteProp,
|
|
8
|
+
StackActionHelpers,
|
|
9
|
+
StackNavigationState,
|
|
10
|
+
StackRouterOptions,
|
|
11
|
+
} from "@react-navigation/core";
|
|
12
|
+
import type { ReactNode } from "react";
|
|
13
|
+
import type { HeaderOptions } from "../shared/types.js";
|
|
14
|
+
|
|
15
|
+
/** Data carried by the `transitionStart` and `transitionEnd` events. */
|
|
16
|
+
type StackTransitionData = {
|
|
17
|
+
/** Whether the page is hiding (`true`) or showing (`false`). */
|
|
18
|
+
closing: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** Events the stack navigator emits on top of React Navigation's core events. */
|
|
22
|
+
type StackNavigationEventMap = {
|
|
23
|
+
/** A page starts showing or hiding. */
|
|
24
|
+
transitionStart: {
|
|
25
|
+
/** Which direction the page moves in. */
|
|
26
|
+
data: StackTransitionData;
|
|
27
|
+
};
|
|
28
|
+
/** A page finished showing or hiding. */
|
|
29
|
+
transitionEnd: {
|
|
30
|
+
/** Which direction the page moved in. */
|
|
31
|
+
data: StackTransitionData;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** The page below the current one, as handed to a custom `header`. */
|
|
36
|
+
type StackHeaderBack = {
|
|
37
|
+
/** Title of the page below. */
|
|
38
|
+
title: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** Per-screen options of the stack navigator. */
|
|
42
|
+
type StackNavigationOptions = HeaderOptions & {
|
|
43
|
+
/** Renders a custom top bar instead of the default `AdwHeaderBar`. */
|
|
44
|
+
header?: (props: StackHeaderProps) => ReactNode;
|
|
45
|
+
/** Whether the default header bar shows Adwaita's back button; defaults to `true`. */
|
|
46
|
+
headerBackVisible?: boolean;
|
|
47
|
+
/** Whether the user can pop this page with the back button, Escape, Alt+Left or a swipe; defaults to `true`. */
|
|
48
|
+
canPop?: boolean;
|
|
49
|
+
/** Whether pushing and popping this page animates; defaults to `"default"`. */
|
|
50
|
+
animation?: "default" | "none";
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** Props of the stack navigator beyond the options every navigator shares. */
|
|
54
|
+
type StackNavigationConfig = {
|
|
55
|
+
/** Whether pressing Escape pops the visible page; defaults to `true`. */
|
|
56
|
+
popOnEscape?: boolean;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Navigation object of a stack screen. */
|
|
60
|
+
type StackNavigationProp<
|
|
61
|
+
ParamList extends ParamListBase,
|
|
62
|
+
RouteName extends keyof ParamList = keyof ParamList,
|
|
63
|
+
NavigatorID extends string | undefined = undefined,
|
|
64
|
+
> = NavigationProp<
|
|
65
|
+
ParamList,
|
|
66
|
+
RouteName,
|
|
67
|
+
NavigatorID,
|
|
68
|
+
StackNavigationState<ParamList>,
|
|
69
|
+
StackNavigationOptions,
|
|
70
|
+
StackNavigationEventMap
|
|
71
|
+
> & StackActionHelpers<ParamList>;
|
|
72
|
+
|
|
73
|
+
/** Props a stack screen component receives. */
|
|
74
|
+
type StackScreenProps<
|
|
75
|
+
ParamList extends ParamListBase,
|
|
76
|
+
RouteName extends keyof ParamList = keyof ParamList,
|
|
77
|
+
NavigatorID extends string | undefined = undefined,
|
|
78
|
+
> = {
|
|
79
|
+
/** Navigation object of the screen. */
|
|
80
|
+
navigation: StackNavigationProp<ParamList, RouteName, NavigatorID>;
|
|
81
|
+
/** The route the screen renders. */
|
|
82
|
+
route: RouteProp<ParamList, RouteName>;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** Arguments of a custom stack `header` renderer. */
|
|
86
|
+
type StackHeaderProps = {
|
|
87
|
+
/** The route whose header is rendered. */
|
|
88
|
+
route: RouteProp<ParamListBase>;
|
|
89
|
+
/** Navigation object of that route. */
|
|
90
|
+
navigation: StackNavigationProp<ParamListBase>;
|
|
91
|
+
/** Resolved options of that route. */
|
|
92
|
+
options: StackNavigationOptions;
|
|
93
|
+
/** The page below this one in the stack, if any. */
|
|
94
|
+
back?: StackHeaderBack;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/** Navigation helpers of the stack navigator itself. */
|
|
98
|
+
type StackNavigationHelpers = NavigationHelpers<ParamListBase, StackNavigationEventMap> &
|
|
99
|
+
StackActionHelpers<ParamListBase>;
|
|
100
|
+
|
|
101
|
+
/** Descriptor of one stack route. */
|
|
102
|
+
type StackDescriptor = Descriptor<StackNavigationOptions, StackNavigationProp<ParamListBase>, RouteProp<ParamListBase>>;
|
|
103
|
+
/** Descriptors of the stack routes, keyed by route key. */
|
|
104
|
+
type StackDescriptorMap = Record<string, StackDescriptor>;
|
|
105
|
+
|
|
106
|
+
/** Props of the stack `Navigator` component. */
|
|
107
|
+
type StackNavigatorProps = DefaultNavigatorOptions<
|
|
108
|
+
ParamListBase,
|
|
109
|
+
string | undefined,
|
|
110
|
+
StackNavigationState<ParamListBase>,
|
|
111
|
+
StackNavigationOptions,
|
|
112
|
+
StackNavigationEventMap,
|
|
113
|
+
StackNavigationProp<ParamListBase>
|
|
114
|
+
> &
|
|
115
|
+
StackRouterOptions &
|
|
116
|
+
StackNavigationConfig;
|
|
117
|
+
|
|
118
|
+
export type {
|
|
119
|
+
StackDescriptor,
|
|
120
|
+
StackDescriptorMap,
|
|
121
|
+
StackHeaderBack,
|
|
122
|
+
StackHeaderProps,
|
|
123
|
+
StackNavigationConfig,
|
|
124
|
+
StackNavigationEventMap,
|
|
125
|
+
StackNavigationHelpers,
|
|
126
|
+
StackNavigationOptions,
|
|
127
|
+
StackNavigationProp,
|
|
128
|
+
StackNavigatorProps,
|
|
129
|
+
StackScreenProps,
|
|
130
|
+
StackTransitionData,
|
|
131
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } from "react";
|
|
2
|
+
import { NavigationContainer, type NavigationContainerProps } from "./navigation-container.js";
|
|
3
|
+
|
|
4
|
+
/** Props of the component returned by {@link createStaticNavigation}. */
|
|
5
|
+
type StaticNavigationProps = Omit<NavigationContainerProps, "children">;
|
|
6
|
+
|
|
7
|
+
/** The part of a static navigator config {@link createStaticNavigation} consumes. */
|
|
8
|
+
type StaticNavigationTree = {
|
|
9
|
+
/** Returns the component rendering the configured navigator. */
|
|
10
|
+
getComponent: () => ComponentType;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** Turns a static navigator config into a root component: a {@link NavigationContainer} around the tree. */
|
|
14
|
+
function createStaticNavigation(tree: StaticNavigationTree): ComponentType<StaticNavigationProps> {
|
|
15
|
+
const Tree = tree.getComponent();
|
|
16
|
+
|
|
17
|
+
const StaticNavigation = (props: StaticNavigationProps): ReactNode => (
|
|
18
|
+
<NavigationContainer {...props}>
|
|
19
|
+
<Tree />
|
|
20
|
+
</NavigationContainer>
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
return StaticNavigation;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { createStaticNavigation, type StaticNavigationProps, type StaticNavigationTree };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
NavigatorTypeBagBase,
|
|
3
|
+
ParamListBase,
|
|
4
|
+
StaticConfig,
|
|
5
|
+
StaticScreenFactory,
|
|
6
|
+
TabActionHelpers,
|
|
7
|
+
TabNavigationState,
|
|
8
|
+
TabRouterOptions,
|
|
9
|
+
TypedNavigator,
|
|
10
|
+
} from "@react-navigation/core";
|
|
11
|
+
import type { ReactNode } from "react";
|
|
12
|
+
import { createNavigatorFactory, createScreenFactory, TabRouter, useNavigationBuilder } from "@react-navigation/core";
|
|
13
|
+
import type { TabNavigationEventMap, TabNavigationOptions, TabNavigationProp, TabNavigatorProps } from "./types.js";
|
|
14
|
+
import { TabView } from "./tab-view.js";
|
|
15
|
+
|
|
16
|
+
/** Type bag describing the tab navigator to React Navigation's typed APIs. */
|
|
17
|
+
type TabTypeBag<
|
|
18
|
+
ParamList extends ParamListBase = ParamListBase,
|
|
19
|
+
NavigatorID extends string | undefined = string | undefined,
|
|
20
|
+
> = {
|
|
21
|
+
/** Route names and their params. */
|
|
22
|
+
ParamList: ParamList;
|
|
23
|
+
/** Optional navigator id. */
|
|
24
|
+
NavigatorID: NavigatorID;
|
|
25
|
+
/** Shape of the navigator state. */
|
|
26
|
+
State: TabNavigationState<ParamList>;
|
|
27
|
+
/** Per-screen options. */
|
|
28
|
+
ScreenOptions: TabNavigationOptions;
|
|
29
|
+
/** Events the navigator emits. */
|
|
30
|
+
EventMap: TabNavigationEventMap;
|
|
31
|
+
/** Navigation object per route. */
|
|
32
|
+
NavigationList: {
|
|
33
|
+
[RouteName in keyof ParamList]: TabNavigationProp<ParamList, RouteName, NavigatorID>;
|
|
34
|
+
};
|
|
35
|
+
/** The navigator component. */
|
|
36
|
+
Navigator: typeof TabNavigator;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Declares a typed screen of a tab navigator for the static configuration API. */
|
|
40
|
+
const createTabScreen: StaticScreenFactory<TabTypeBag> = createScreenFactory<TabTypeBag>();
|
|
41
|
+
|
|
42
|
+
/** Renders the screens of a {@link createTabNavigator} as pages of an `AdwViewStack` with a view switcher. */
|
|
43
|
+
function TabNavigator({ tabBarPosition, ...options }: TabNavigatorProps): ReactNode {
|
|
44
|
+
const { state, descriptors, navigation, NavigationContent } = useNavigationBuilder<
|
|
45
|
+
TabNavigationState<ParamListBase>,
|
|
46
|
+
TabRouterOptions,
|
|
47
|
+
TabActionHelpers<ParamListBase>,
|
|
48
|
+
TabNavigationOptions,
|
|
49
|
+
TabNavigationEventMap
|
|
50
|
+
>(TabRouter, options);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<NavigationContent>
|
|
54
|
+
<TabView tabBarPosition={tabBarPosition} state={state} navigation={navigation} descriptors={descriptors} />
|
|
55
|
+
</NavigationContent>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Creates a tab navigator rendered with `AdwViewStack` and an `AdwViewSwitcher`. */
|
|
60
|
+
function createTabNavigator<
|
|
61
|
+
const ParamList extends ParamListBase,
|
|
62
|
+
const NavigatorID extends string | undefined = string | undefined,
|
|
63
|
+
const TypeBag extends NavigatorTypeBagBase = TabTypeBag<ParamList, NavigatorID>,
|
|
64
|
+
const Config extends StaticConfig<TypeBag> | undefined = undefined,
|
|
65
|
+
>(config?: Config): TypedNavigator<TypeBag, Config> {
|
|
66
|
+
return createNavigatorFactory(TabNavigator)(config) as TypedNavigator<TypeBag, Config>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { createTabNavigator, createTabScreen, TabNavigator, type TabTypeBag };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { ParamListBase, TabNavigationState } from "@react-navigation/core";
|
|
2
|
+
import type { ReactElement, ReactNode } from "react";
|
|
3
|
+
import * as Adw from "@gtkx/gi/adw";
|
|
4
|
+
import { AdwToolbarView, AdwViewStack, AdwViewSwitcher, AdwViewSwitcherBar, AdwWindowTitle } from "@gtkx/jsx/adw";
|
|
5
|
+
import { CommonActions } from "@react-navigation/core";
|
|
6
|
+
import { useCallback, useState } from "react";
|
|
7
|
+
import type { TabDescriptor, TabDescriptorMap, TabNavigationConfig, TabNavigationHelpers } from "./types.js";
|
|
8
|
+
import { HeaderBar } from "../shared/header-bar.js";
|
|
9
|
+
import { getFocusedRoute, requireDescriptor } from "../shared/routes.js";
|
|
10
|
+
import { ScenePage } from "../shared/scene-page.js";
|
|
11
|
+
import { useLoadedRoutes } from "../shared/use-loaded-routes.js";
|
|
12
|
+
import { usePopToTopOnBlur } from "../shared/use-pop-to-top-on-blur.js";
|
|
13
|
+
|
|
14
|
+
type TabViewProps = TabNavigationConfig & {
|
|
15
|
+
state: TabNavigationState<ParamListBase>;
|
|
16
|
+
navigation: TabNavigationHelpers;
|
|
17
|
+
descriptors: TabDescriptorMap;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type TabHeaderProps = {
|
|
21
|
+
descriptor: TabDescriptor;
|
|
22
|
+
viewSwitcher: ReactElement | undefined;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type TabPageProps = {
|
|
26
|
+
descriptor: TabDescriptor;
|
|
27
|
+
isLoaded: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const useTabSelection = (navigation: TabNavigationHelpers): ((name: string | null) => void) =>
|
|
31
|
+
useCallback((name: string | null) => {
|
|
32
|
+
const current = navigation.getState();
|
|
33
|
+
const focusedKey = getFocusedRoute(current).key;
|
|
34
|
+
const route = current.routes.find((candidate) => candidate.key === name);
|
|
35
|
+
|
|
36
|
+
if (route === undefined || route.key === focusedKey) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const event = navigation.emit({ type: "tabPress", target: route.key, canPreventDefault: true });
|
|
41
|
+
|
|
42
|
+
if (!event.defaultPrevented) {
|
|
43
|
+
navigation.dispatch({ ...CommonActions.navigate(route.name, route.params), target: current.key });
|
|
44
|
+
}
|
|
45
|
+
}, [navigation]);
|
|
46
|
+
|
|
47
|
+
const defaultTabHeader = ({ descriptor, viewSwitcher }: TabHeaderProps): ReactElement => (
|
|
48
|
+
<HeaderBar
|
|
49
|
+
options={descriptor.options}
|
|
50
|
+
titleWidget={viewSwitcher ?? <AdwWindowTitle title={descriptor.options.title ?? descriptor.route.name} />}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const TabHeader = (props: TabHeaderProps): ReactNode => {
|
|
55
|
+
const { descriptor, viewSwitcher } = props;
|
|
56
|
+
const { route, options, navigation } = descriptor;
|
|
57
|
+
|
|
58
|
+
if (options.headerShown === false) {
|
|
59
|
+
return <>{viewSwitcher}</>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<>
|
|
64
|
+
{options.header === undefined
|
|
65
|
+
? defaultTabHeader(props)
|
|
66
|
+
: options.header({ route, navigation, options, viewSwitcher })}
|
|
67
|
+
</>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const TabPage = ({ descriptor, isLoaded }: TabPageProps): ReactNode => {
|
|
72
|
+
const { route, options } = descriptor;
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<ScenePage
|
|
76
|
+
name={route.key}
|
|
77
|
+
title={options.tabBarLabel ?? options.title ?? route.name}
|
|
78
|
+
iconName={options.tabBarIcon}
|
|
79
|
+
badgeNumber={options.tabBarBadge}
|
|
80
|
+
needsAttention={options.needsAttention}
|
|
81
|
+
isLoaded={isLoaded || options.lazy === false}
|
|
82
|
+
render={() => descriptor.render()}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const TabView = ({ state, navigation, descriptors, tabBarPosition = "top" }: TabViewProps): ReactNode => {
|
|
88
|
+
const [stack, setStack] = useState<Adw.ViewStack | null>(null);
|
|
89
|
+
const focused = getFocusedRoute(state);
|
|
90
|
+
const descriptor = requireDescriptor(descriptors, focused.key);
|
|
91
|
+
const loaded = useLoadedRoutes(focused.key, state.preloadedRouteKeys);
|
|
92
|
+
const onVisibleChildChanged = useTabSelection(navigation);
|
|
93
|
+
|
|
94
|
+
const viewSwitcher = stack === null
|
|
95
|
+
? undefined
|
|
96
|
+
: <AdwViewSwitcher stack={stack} policy={Adw.ViewSwitcherPolicy.WIDE} />;
|
|
97
|
+
|
|
98
|
+
const hasSwitcherBar = tabBarPosition === "bottom" && stack !== null;
|
|
99
|
+
usePopToTopOnBlur(state, descriptors, navigation);
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<AdwToolbarView
|
|
103
|
+
topBar={(
|
|
104
|
+
<TabHeader descriptor={descriptor} viewSwitcher={tabBarPosition === "top" ? viewSwitcher : undefined} />
|
|
105
|
+
)}
|
|
106
|
+
bottomBar={hasSwitcherBar ? <AdwViewSwitcherBar stack={stack} reveal /> : undefined}
|
|
107
|
+
>
|
|
108
|
+
<AdwViewStack
|
|
109
|
+
ref={setStack}
|
|
110
|
+
visibleChildName={focused.key}
|
|
111
|
+
enableTransitions={descriptor.options.animation === "fade"}
|
|
112
|
+
onNotifyVisibleChildName={onVisibleChildChanged}
|
|
113
|
+
>
|
|
114
|
+
{state.routes.map((route) => (
|
|
115
|
+
<TabPage
|
|
116
|
+
key={route.key}
|
|
117
|
+
descriptor={requireDescriptor(descriptors, route.key)}
|
|
118
|
+
isLoaded={loaded.has(route.key)}
|
|
119
|
+
/>
|
|
120
|
+
))}
|
|
121
|
+
</AdwViewStack>
|
|
122
|
+
</AdwToolbarView>
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export { TabView };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DefaultNavigatorOptions,
|
|
3
|
+
Descriptor,
|
|
4
|
+
NavigationHelpers,
|
|
5
|
+
NavigationProp,
|
|
6
|
+
ParamListBase,
|
|
7
|
+
RouteProp,
|
|
8
|
+
TabActionHelpers,
|
|
9
|
+
TabNavigationState,
|
|
10
|
+
TabRouterOptions,
|
|
11
|
+
} from "@react-navigation/core";
|
|
12
|
+
import type { ReactNode } from "react";
|
|
13
|
+
import type { HeaderOptions } from "../shared/types.js";
|
|
14
|
+
|
|
15
|
+
/** Events the tab navigator emits on top of React Navigation's core events. */
|
|
16
|
+
type TabNavigationEventMap = {
|
|
17
|
+
/** The user selected a tab in the view switcher. */
|
|
18
|
+
tabPress: {
|
|
19
|
+
/** No payload. */
|
|
20
|
+
data: undefined;
|
|
21
|
+
/** Calling `preventDefault` keeps the current tab. */
|
|
22
|
+
canPreventDefault: true;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** Per-screen options of the tab navigator. */
|
|
27
|
+
type TabNavigationOptions = HeaderOptions & {
|
|
28
|
+
/** Renders a custom top bar instead of the default `AdwHeaderBar`. */
|
|
29
|
+
header?: (props: TabHeaderProps) => ReactNode;
|
|
30
|
+
/** Label shown in the view switcher; defaults to `title`, then to the route name. */
|
|
31
|
+
tabBarLabel?: string;
|
|
32
|
+
/** Icon name shown in the view switcher. */
|
|
33
|
+
tabBarIcon?: string;
|
|
34
|
+
/** Badge number shown in the view switcher; `0` hides the badge. */
|
|
35
|
+
tabBarBadge?: number;
|
|
36
|
+
/** Whether the view switcher highlights the tab as needing attention. */
|
|
37
|
+
needsAttention?: boolean;
|
|
38
|
+
/** Whether the screen mounts on first focus instead of at startup; defaults to `true`. */
|
|
39
|
+
lazy?: boolean;
|
|
40
|
+
/** Whether a nested stack pops to its first screen when this tab loses focus; defaults to `false`. */
|
|
41
|
+
popToTopOnBlur?: boolean;
|
|
42
|
+
/** Whether switching to this tab crossfades; defaults to `"none"`. */
|
|
43
|
+
animation?: "fade" | "none";
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Props of the tab navigator beyond the options every navigator shares. */
|
|
47
|
+
type TabNavigationConfig = {
|
|
48
|
+
/** Where the view switcher sits: in the header bar or in a bar below the content; defaults to `"top"`. */
|
|
49
|
+
tabBarPosition?: "top" | "bottom";
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** Navigation object of a tab screen. */
|
|
53
|
+
type TabNavigationProp<
|
|
54
|
+
ParamList extends ParamListBase,
|
|
55
|
+
RouteName extends keyof ParamList = keyof ParamList,
|
|
56
|
+
NavigatorID extends string | undefined = undefined,
|
|
57
|
+
> = NavigationProp<
|
|
58
|
+
ParamList,
|
|
59
|
+
RouteName,
|
|
60
|
+
NavigatorID,
|
|
61
|
+
TabNavigationState<ParamList>,
|
|
62
|
+
TabNavigationOptions,
|
|
63
|
+
TabNavigationEventMap
|
|
64
|
+
> & TabActionHelpers<ParamList>;
|
|
65
|
+
|
|
66
|
+
/** Props a tab screen component receives. */
|
|
67
|
+
type TabScreenProps<
|
|
68
|
+
ParamList extends ParamListBase,
|
|
69
|
+
RouteName extends keyof ParamList = keyof ParamList,
|
|
70
|
+
NavigatorID extends string | undefined = undefined,
|
|
71
|
+
> = {
|
|
72
|
+
/** Navigation object of the screen. */
|
|
73
|
+
navigation: TabNavigationProp<ParamList, RouteName, NavigatorID>;
|
|
74
|
+
/** The route the screen renders. */
|
|
75
|
+
route: RouteProp<ParamList, RouteName>;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/** Arguments of a custom tab `header` renderer. */
|
|
79
|
+
type TabHeaderProps = {
|
|
80
|
+
/** The focused route. */
|
|
81
|
+
route: RouteProp<ParamListBase>;
|
|
82
|
+
/** Navigation object of the focused route. */
|
|
83
|
+
navigation: TabNavigationProp<ParamListBase>;
|
|
84
|
+
/** Resolved options of the focused route. */
|
|
85
|
+
options: TabNavigationOptions;
|
|
86
|
+
/** The view switcher to place in the header when `tabBarPosition` is `"top"`. */
|
|
87
|
+
viewSwitcher: ReactNode;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** Navigation helpers of the tab navigator itself. */
|
|
91
|
+
type TabNavigationHelpers = NavigationHelpers<ParamListBase, TabNavigationEventMap> & TabActionHelpers<ParamListBase>;
|
|
92
|
+
/** Descriptor of one tab route. */
|
|
93
|
+
type TabDescriptor = Descriptor<TabNavigationOptions, TabNavigationProp<ParamListBase>, RouteProp<ParamListBase>>;
|
|
94
|
+
/** Descriptors of the tab routes, keyed by route key. */
|
|
95
|
+
type TabDescriptorMap = Record<string, TabDescriptor>;
|
|
96
|
+
|
|
97
|
+
/** Props of the tab `Navigator` component. */
|
|
98
|
+
type TabNavigatorProps = DefaultNavigatorOptions<
|
|
99
|
+
ParamListBase,
|
|
100
|
+
string | undefined,
|
|
101
|
+
TabNavigationState<ParamListBase>,
|
|
102
|
+
TabNavigationOptions,
|
|
103
|
+
TabNavigationEventMap,
|
|
104
|
+
TabNavigationProp<ParamListBase>
|
|
105
|
+
> &
|
|
106
|
+
TabRouterOptions &
|
|
107
|
+
TabNavigationConfig;
|
|
108
|
+
|
|
109
|
+
export type {
|
|
110
|
+
TabDescriptor,
|
|
111
|
+
TabDescriptorMap,
|
|
112
|
+
TabHeaderProps,
|
|
113
|
+
TabNavigationConfig,
|
|
114
|
+
TabNavigationEventMap,
|
|
115
|
+
TabNavigationHelpers,
|
|
116
|
+
TabNavigationOptions,
|
|
117
|
+
TabNavigationProp,
|
|
118
|
+
TabNavigatorProps,
|
|
119
|
+
TabScreenProps,
|
|
120
|
+
};
|
package/src/theme.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as Adw from "@gtkx/gi/adw";
|
|
2
|
+
import { useProperty } from "@gtkx/react";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
|
|
5
|
+
/** Style state handed to `useTheme` and to option callbacks as `theme`, mirroring Adwaita's style manager. */
|
|
6
|
+
type AdwaitaTheme = {
|
|
7
|
+
/** Whether the application currently uses the dark color scheme. */
|
|
8
|
+
dark: boolean;
|
|
9
|
+
/** Whether the system requested high-contrast styling. */
|
|
10
|
+
highContrast: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
declare module "@react-navigation/core" {
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
15
|
+
interface Theme extends AdwaitaTheme {}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The light Adwaita theme. */
|
|
19
|
+
const DefaultTheme: AdwaitaTheme = { dark: false, highContrast: false };
|
|
20
|
+
/** The dark Adwaita theme. */
|
|
21
|
+
const DarkTheme: AdwaitaTheme = { dark: true, highContrast: false };
|
|
22
|
+
|
|
23
|
+
const useAdwaitaTheme = (): AdwaitaTheme => {
|
|
24
|
+
const manager = Adw.StyleManager.getDefault();
|
|
25
|
+
const isDark = useProperty(manager, "dark") ?? manager.getDark();
|
|
26
|
+
const isHighContrast = useProperty(manager, "highContrast") ?? manager.getHighContrast();
|
|
27
|
+
|
|
28
|
+
return useMemo(() => ({ dark: isDark, highContrast: isHighContrast }), [isDark, isHighContrast]);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export { type AdwaitaTheme as Theme, DarkTheme, DefaultTheme, useAdwaitaTheme };
|