convex-feedback-ui 0.2.0-beta.2 → 0.2.0-beta.3
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 +38 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/native/expo/RoadmapRoutes.d.ts +3 -0
- package/dist/native/expo/RoadmapRoutes.d.ts.map +1 -0
- package/dist/native/expo/RoadmapRoutes.js +44 -0
- package/dist/native/expo/RoadmapRoutes.js.map +1 -0
- package/dist/native/expo/RoadmapScreen.d.ts +24 -0
- package/dist/native/expo/RoadmapScreen.d.ts.map +1 -0
- package/dist/native/expo/RoadmapScreen.js +36 -0
- package/dist/native/expo/RoadmapScreen.js.map +1 -0
- package/dist/native/expo/RoadmapStackLayout.d.ts +8 -0
- package/dist/native/expo/RoadmapStackLayout.d.ts.map +1 -0
- package/dist/native/expo/RoadmapStackLayout.js +56 -0
- package/dist/native/expo/RoadmapStackLayout.js.map +1 -0
- package/dist/native/expo/RoutedRoadmapContext.d.ts +4 -0
- package/dist/native/expo/RoutedRoadmapContext.d.ts.map +1 -0
- package/dist/native/expo/RoutedRoadmapContext.js +11 -0
- package/dist/native/expo/RoutedRoadmapContext.js.map +1 -0
- package/dist/native/expo/index.d.ts +4 -1
- package/dist/native/expo/index.d.ts.map +1 -1
- package/dist/native/expo/index.js +4 -1
- package/dist/native/expo/index.js.map +1 -1
- package/dist/native/expo/routes.d.ts +17 -1
- package/dist/native/expo/routes.d.ts.map +1 -1
- package/dist/native/expo/routes.js +46 -0
- package/dist/native/expo/routes.js.map +1 -1
- package/dist/native/expo/types.d.ts +35 -1
- package/dist/native/expo/types.d.ts.map +1 -1
- package/dist/native/shared/ui/RoadmapScreen.d.ts +21 -1
- package/dist/native/shared/ui/RoadmapScreen.d.ts.map +1 -1
- package/dist/native/shared/ui/RoadmapScreen.js +228 -50
- package/dist/native/shared/ui/RoadmapScreen.js.map +1 -1
- package/dist/native/shared/ui/primitives.d.ts +1 -1
- package/dist/native/shared/ui/primitives.d.ts.map +1 -1
- package/dist/native/shared/ui/primitives.js +3 -1
- package/dist/native/shared/ui/primitives.js.map +1 -1
- package/dist/react/roadmap.d.ts.map +1 -1
- package/dist/react/roadmap.js +24 -12
- package/dist/react/roadmap.js.map +1 -1
- package/dist/react/styles.css +123 -0
- package/dist/shared/messages.d.ts.map +1 -1
- package/dist/shared/messages.js +23 -0
- package/dist/shared/messages.js.map +1 -1
- package/dist/shared/types/messages.d.ts +26 -1
- package/dist/shared/types/messages.d.ts.map +1 -1
- package/dist/shared/types/roadmap.d.ts +10 -1
- package/dist/shared/types/roadmap.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/native/expo/README.md +6 -2
- package/src/native/expo/RoadmapRoutes.tsx +133 -0
- package/src/native/expo/RoadmapScreen.tsx +101 -0
- package/src/native/expo/RoadmapStackLayout.tsx +111 -0
- package/src/native/expo/RoutedRoadmapContext.tsx +21 -0
- package/src/native/expo/index.ts +8 -1
- package/src/native/expo/routes.ts +69 -1
- package/src/native/expo/types.ts +43 -0
- package/src/native/native/README.md +4 -3
- package/src/native/shared/ui/RoadmapScreen.tsx +550 -228
- package/src/native/shared/ui/primitives.tsx +3 -0
- package/src/react/roadmap.tsx +145 -85
- package/src/react/styles.css +123 -0
- package/src/shared/messages.ts +24 -0
- package/src/shared/types/messages.ts +38 -1
- package/src/shared/types/roadmap.ts +18 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Stack, type NativeStackNavigationOptions } from "expo-router";
|
|
2
|
+
|
|
3
|
+
import { mergeFeedbackMessages } from "../../shared/messages.js";
|
|
4
|
+
import { mergeFeedbackTheme } from "../../shared/theme.js";
|
|
5
|
+
import { RoadmapProvider } from "../shared/ui/RoadmapScreen.js";
|
|
6
|
+
import { RoutedRoadmapProvider } from "./RoutedRoadmapContext.js";
|
|
7
|
+
import { resolveRoadmapRoutes } from "./routes.js";
|
|
8
|
+
import type { RoadmapStackLayoutProps } from "./types.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Provider and native-stack layout for real Expo Router roadmap pages.
|
|
12
|
+
* Consumers supply the small route files; this component keeps the roadmap
|
|
13
|
+
* query and entry-detail context mounted while a detail page is pushed.
|
|
14
|
+
*/
|
|
15
|
+
export function RoadmapStackLayout({
|
|
16
|
+
hooks,
|
|
17
|
+
messages,
|
|
18
|
+
theme,
|
|
19
|
+
unstyled,
|
|
20
|
+
onEntryOpen,
|
|
21
|
+
onUnauthenticated,
|
|
22
|
+
commentSort,
|
|
23
|
+
maxCommentDepth,
|
|
24
|
+
transformComments,
|
|
25
|
+
renderActor,
|
|
26
|
+
pageSize = hooks.pageSizes.roadmap,
|
|
27
|
+
entryPageSize = hooks.pageSizes.entries,
|
|
28
|
+
routes: routeOverrides,
|
|
29
|
+
screenOptions,
|
|
30
|
+
boardOptions,
|
|
31
|
+
itemOptions,
|
|
32
|
+
androidToolbarIcons = {},
|
|
33
|
+
primaryColor,
|
|
34
|
+
primaryForeground,
|
|
35
|
+
backgroundColor,
|
|
36
|
+
surfaceColor,
|
|
37
|
+
textColor,
|
|
38
|
+
mutedColor,
|
|
39
|
+
borderColor,
|
|
40
|
+
dangerColor,
|
|
41
|
+
}: RoadmapStackLayoutProps) {
|
|
42
|
+
const routes = resolveRoadmapRoutes(routeOverrides);
|
|
43
|
+
const resolvedMessages = mergeFeedbackMessages(messages);
|
|
44
|
+
const resolvedTheme = mergeFeedbackTheme(theme);
|
|
45
|
+
const colors = {
|
|
46
|
+
primaryColor,
|
|
47
|
+
primaryForeground,
|
|
48
|
+
backgroundColor,
|
|
49
|
+
surfaceColor,
|
|
50
|
+
textColor,
|
|
51
|
+
mutedColor,
|
|
52
|
+
borderColor,
|
|
53
|
+
dangerColor,
|
|
54
|
+
};
|
|
55
|
+
const defaults: NativeStackNavigationOptions = {
|
|
56
|
+
headerShown: true,
|
|
57
|
+
headerTransparent: true,
|
|
58
|
+
headerShadowVisible: true,
|
|
59
|
+
headerBackButtonDisplayMode: "minimal",
|
|
60
|
+
headerTintColor: textColor ?? resolvedTheme.colors.text,
|
|
61
|
+
contentStyle: {
|
|
62
|
+
backgroundColor: backgroundColor ?? resolvedTheme.colors.background,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<RoadmapProvider
|
|
68
|
+
hooks={hooks}
|
|
69
|
+
messages={messages}
|
|
70
|
+
theme={theme}
|
|
71
|
+
unstyled={unstyled}
|
|
72
|
+
commentSort={commentSort}
|
|
73
|
+
maxCommentDepth={maxCommentDepth}
|
|
74
|
+
transformComments={transformComments}
|
|
75
|
+
renderActor={renderActor}
|
|
76
|
+
onUnauthenticated={onUnauthenticated}
|
|
77
|
+
>
|
|
78
|
+
<RoutedRoadmapProvider
|
|
79
|
+
value={{
|
|
80
|
+
routes,
|
|
81
|
+
colors,
|
|
82
|
+
androidToolbarIcons,
|
|
83
|
+
pageSize,
|
|
84
|
+
entryPageSize,
|
|
85
|
+
onEntryOpen,
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
<Stack>
|
|
89
|
+
<Stack.Screen
|
|
90
|
+
name={routes.board}
|
|
91
|
+
options={{
|
|
92
|
+
...defaults,
|
|
93
|
+
headerTitle: resolvedMessages.roadmap.title,
|
|
94
|
+
...screenOptions,
|
|
95
|
+
...boardOptions,
|
|
96
|
+
}}
|
|
97
|
+
/>
|
|
98
|
+
<Stack.Screen
|
|
99
|
+
name={routes.item}
|
|
100
|
+
options={{
|
|
101
|
+
...defaults,
|
|
102
|
+
headerTitle: resolvedMessages.roadmap.title,
|
|
103
|
+
...screenOptions,
|
|
104
|
+
...itemOptions,
|
|
105
|
+
}}
|
|
106
|
+
/>
|
|
107
|
+
</Stack>
|
|
108
|
+
</RoutedRoadmapProvider>
|
|
109
|
+
</RoadmapProvider>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
|
|
3
|
+
import type { RoutedRoadmapContextValue } from "./types.js";
|
|
4
|
+
|
|
5
|
+
const RoutedRoadmapContext = createContext<RoutedRoadmapContextValue | null>(
|
|
6
|
+
null,
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
export const RoutedRoadmapProvider = RoutedRoadmapContext.Provider;
|
|
10
|
+
|
|
11
|
+
export function useRoutedRoadmap(): RoutedRoadmapContextValue {
|
|
12
|
+
const value = useContext(RoutedRoadmapContext);
|
|
13
|
+
|
|
14
|
+
if (value === null) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"Routed roadmap screens must be rendered inside RoadmapStackLayout.",
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return value;
|
|
21
|
+
}
|
package/src/native/expo/index.ts
CHANGED
|
@@ -5,11 +5,18 @@ export * from "../../shared/metadata.js";
|
|
|
5
5
|
export * from "../../shared/theme.js";
|
|
6
6
|
export type * from "../../shared/types/index.js";
|
|
7
7
|
export * from "../shared/ui/primitives.js";
|
|
8
|
-
export
|
|
8
|
+
export {
|
|
9
|
+
RoadmapBoardContent,
|
|
10
|
+
RoadmapItemContent,
|
|
11
|
+
RoadmapProvider,
|
|
12
|
+
} from "../shared/ui/RoadmapScreen.js";
|
|
9
13
|
export * from "./metadata.js";
|
|
10
14
|
export * from "./FeedbackScreen.js";
|
|
11
15
|
export * from "./FeedbackCreateStackLayout.js";
|
|
12
16
|
export * from "./FeedbackStackLayout.js";
|
|
13
17
|
export * from "./FeedbackRoutes.js";
|
|
18
|
+
export * from "./RoadmapScreen.js";
|
|
19
|
+
export * from "./RoadmapStackLayout.js";
|
|
20
|
+
export * from "./RoadmapRoutes.js";
|
|
14
21
|
export * from "./routes.js";
|
|
15
22
|
export type * from "./types.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Href } from "expo-router";
|
|
2
|
-
import type {
|
|
2
|
+
import type { RoadmapItem } from "convex-feedback";
|
|
3
|
+
import type { FeedbackRouteNames, RoadmapRouteNames } from "./types.js";
|
|
3
4
|
|
|
4
5
|
export const defaultFeedbackRoutes: FeedbackRouteNames = {
|
|
5
6
|
board: "index",
|
|
@@ -39,3 +40,70 @@ export function feedbackRouteHref(
|
|
|
39
40
|
const pathname = route.startsWith(".") ? route : `./${route}`;
|
|
40
41
|
return params === undefined ? pathname : { pathname, params };
|
|
41
42
|
}
|
|
43
|
+
|
|
44
|
+
export const defaultRoadmapRoutes: RoadmapRouteNames = {
|
|
45
|
+
board: "index",
|
|
46
|
+
item: "[roadmapId]",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export function resolveRoadmapRoutes(
|
|
50
|
+
routes: Partial<RoadmapRouteNames> = {},
|
|
51
|
+
): RoadmapRouteNames {
|
|
52
|
+
const resolved = { ...defaultRoadmapRoutes, ...routes };
|
|
53
|
+
|
|
54
|
+
if (!resolved.item.includes("[roadmapId]")) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
'The routed roadmap item route must contain the "[roadmapId]" dynamic segment.',
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return resolved;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function createRoadmapStackSettings(
|
|
64
|
+
routes?: Partial<RoadmapRouteNames>,
|
|
65
|
+
) {
|
|
66
|
+
return { anchor: resolveRoadmapRoutes(routes).board };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const roadmapStackSettings = createRoadmapStackSettings();
|
|
70
|
+
|
|
71
|
+
export function roadmapRouteHref(
|
|
72
|
+
route: string,
|
|
73
|
+
params?: Record<string, string>,
|
|
74
|
+
): Href {
|
|
75
|
+
const pathname = route.startsWith(".") ? route : `./${route}`;
|
|
76
|
+
return params === undefined ? pathname : { pathname, params };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Pass the already loaded item through a push so a detail route renders immediately. */
|
|
80
|
+
export function roadmapRouteParams(item: RoadmapItem) {
|
|
81
|
+
return { roadmapId: item.id, item: JSON.stringify(item) };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function firstParam(value: string | string[] | undefined): string | undefined {
|
|
85
|
+
return Array.isArray(value) ? value[0] : value;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function parseRoadmapRouteItem(
|
|
89
|
+
value: string | string[] | undefined,
|
|
90
|
+
): RoadmapItem | undefined {
|
|
91
|
+
const serialized = firstParam(value);
|
|
92
|
+
if (!serialized) return undefined;
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const parsed: unknown = JSON.parse(serialized);
|
|
96
|
+
if (!parsed || typeof parsed !== "object") return undefined;
|
|
97
|
+
const item = parsed as Partial<RoadmapItem>;
|
|
98
|
+
if (
|
|
99
|
+
typeof item.id !== "string" ||
|
|
100
|
+
typeof item.title !== "string" ||
|
|
101
|
+
typeof item.status !== "string"
|
|
102
|
+
) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
return item as RoadmapItem;
|
|
106
|
+
} catch {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|
package/src/native/expo/types.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { SearchBarCommands } from "react-native-screens";
|
|
|
5
5
|
import type {
|
|
6
6
|
FeedbackColorProps,
|
|
7
7
|
FeedbackScreenRootProps,
|
|
8
|
+
RoadmapScreenProps,
|
|
8
9
|
} from "../../shared/types";
|
|
9
10
|
|
|
10
11
|
/** Optional wrapper used around Expo Router bottom toolbars. */
|
|
@@ -65,6 +66,13 @@ export interface FeedbackAndroidToolbarIcons {
|
|
|
65
66
|
filter?: ImageSourcePropType;
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
export interface RoadmapAndroidToolbarIcons {
|
|
70
|
+
/** Android image source for back actions. */
|
|
71
|
+
back?: ImageSourcePropType;
|
|
72
|
+
/** Android image source for modal close actions. */
|
|
73
|
+
close?: ImageSourcePropType;
|
|
74
|
+
}
|
|
75
|
+
|
|
68
76
|
export interface FeedbackStackLayoutProps extends FeedbackScreenRootProps {
|
|
69
77
|
/** Partial overrides for the prescribed route file names. */
|
|
70
78
|
routes?: Partial<FeedbackRouteNames>;
|
|
@@ -91,9 +99,44 @@ export interface FeedbackCreateStackLayoutProps {
|
|
|
91
99
|
entryOptions?: FeedbackStackScreenOptions;
|
|
92
100
|
}
|
|
93
101
|
|
|
102
|
+
/** File names used by the routed public roadmap stack. */
|
|
103
|
+
export interface RoadmapRouteNames {
|
|
104
|
+
/** Board route. @default "index" */
|
|
105
|
+
board: string;
|
|
106
|
+
/** Roadmap item route. Must contain `[roadmapId]`. @default "[roadmapId]" */
|
|
107
|
+
item: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type RoadmapStackScreenOptions = Exclude<
|
|
111
|
+
NonNullable<StackScreenProps["options"]>,
|
|
112
|
+
(...args: never[]) => unknown
|
|
113
|
+
>;
|
|
114
|
+
|
|
115
|
+
export interface RoadmapStackLayoutProps extends RoadmapScreenProps {
|
|
116
|
+
/** Partial overrides for the prescribed route file names. */
|
|
117
|
+
routes?: Partial<RoadmapRouteNames>;
|
|
118
|
+
/** Options shared by every roadmap screen. */
|
|
119
|
+
screenOptions?: RoadmapStackScreenOptions;
|
|
120
|
+
/** Roadmap board screen option overrides. */
|
|
121
|
+
boardOptions?: RoadmapStackScreenOptions;
|
|
122
|
+
/** Roadmap item screen option overrides. */
|
|
123
|
+
itemOptions?: RoadmapStackScreenOptions;
|
|
124
|
+
/** Android image sources for toolbar actions. iOS continues to use SF Symbols. */
|
|
125
|
+
androidToolbarIcons?: RoadmapAndroidToolbarIcons;
|
|
126
|
+
}
|
|
127
|
+
|
|
94
128
|
export interface RoutedFeedbackContextValue {
|
|
95
129
|
routes: FeedbackRouteNames;
|
|
96
130
|
colors: FeedbackColorProps;
|
|
97
131
|
androidToolbarIcons: FeedbackAndroidToolbarIcons;
|
|
98
132
|
bottomToolbarWrapper?: BottomToolbarWrapper;
|
|
99
133
|
}
|
|
134
|
+
|
|
135
|
+
export interface RoutedRoadmapContextValue {
|
|
136
|
+
routes: RoadmapRouteNames;
|
|
137
|
+
colors: FeedbackColorProps;
|
|
138
|
+
androidToolbarIcons: RoadmapAndroidToolbarIcons;
|
|
139
|
+
pageSize: number;
|
|
140
|
+
entryPageSize: number;
|
|
141
|
+
onEntryOpen?: RoadmapScreenProps["onEntryOpen"];
|
|
142
|
+
}
|
|
@@ -16,6 +16,7 @@ To replace a control with Expo UI, use render-function children on an interactiv
|
|
|
16
16
|
|
|
17
17
|
Containers such as `FeedbackEntry.Content`, `FeedbackForm.Root`, and `Comment.Children` accept arbitrary children, so custom Expo UI fields/buttons can also be placed directly inside the compound layout.
|
|
18
18
|
|
|
19
|
-
`RoadmapScreen` provides the public roadmap
|
|
20
|
-
the
|
|
21
|
-
when an anonymous visitor votes
|
|
19
|
+
`RoadmapScreen` provides the public roadmap, groups items by delivery stage,
|
|
20
|
+
and renders attached entries with the existing `EntryCard` and `EntryDetail`
|
|
21
|
+
views. Pass `onUnauthenticated` to handle auth when an anonymous visitor votes
|
|
22
|
+
or comments.
|