Skip to content

Commit ab012e6

Browse files
more improvements
1 parent cdc0a44 commit ab012e6

6 files changed

Lines changed: 175 additions & 92 deletions

File tree

demos/react-convex-todolist/src/app/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { createRoot } from 'react-dom/client';
2-
import CssBaseline from '@mui/material/CssBaseline';
3-
import { RouterProvider } from 'react-router-dom';
41
import { ConvexAuthProvider } from '@convex-dev/auth/react';
2+
import CssBaseline from '@mui/material/CssBaseline';
53
import { ConvexReactClient } from 'convex/react';
4+
import { createRoot } from 'react-dom/client';
5+
import { RouterProvider } from 'react-router-dom';
66
import { SystemProvider } from '../components/providers/SystemProvider';
77
import { ThemeProviderContainer } from '../components/providers/ThemeProviderContainer';
88
import { router } from './router';

demos/react-convex-todolist/src/app/views/layout.tsx

Lines changed: 67 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import ChecklistRtlIcon from '@mui/icons-material/ChecklistRtl';
22
import LogoutIcon from '@mui/icons-material/Logout';
3+
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
4+
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
35
import MenuIcon from '@mui/icons-material/Menu';
4-
import NorthIcon from '@mui/icons-material/North';
56
import SignalWifiOffIcon from '@mui/icons-material/SignalWifiOff';
6-
import SouthIcon from '@mui/icons-material/South';
77
import TerminalIcon from '@mui/icons-material/Terminal';
88
import WifiIcon from '@mui/icons-material/Wifi';
99
import {
@@ -88,42 +88,78 @@ export default function ViewsLayout({ children }: { children: React.ReactNode })
8888
<Typography variant="h6">{title}</Typography>
8989
</Box>
9090
<Box
91+
component="span"
9192
sx={{
92-
display: { xs: 'none', sm: 'inline-flex' },
93+
display: 'inline-flex',
94+
flexDirection: 'column',
9395
alignItems: 'center',
9496
flexShrink: 0,
95-
mr: 1.25
97+
mr: 1.5
9698
}}
99+
aria-label={[
100+
syncStatus?.connected ? 'Connected' : 'Disconnected',
101+
syncStatus?.dataFlowStatus.uploading ? 'Uploading' : null,
102+
syncStatus?.dataFlowStatus.downloading ? 'Downloading' : null
103+
]
104+
.filter(Boolean)
105+
.join('. ')}
97106
>
98-
<NorthIcon
99-
fontSize="small"
100-
color={syncStatus?.dataFlowStatus.uploading ? 'success' : 'disabled'}
107+
<Box
101108
sx={{
102-
mr: -1.5,
103-
opacity: syncStatus?.dataFlowStatus.uploading ? 1 : 0.45
109+
height: { xs: 0, sm: 14 },
110+
minHeight: { xs: 0, sm: 14 },
111+
display: { xs: 'none', sm: 'flex' },
112+
alignItems: 'flex-end',
113+
justifyContent: 'center',
114+
mb: -0.75,
115+
lineHeight: 0
104116
}}
105-
/>
106-
<SouthIcon
107-
fontSize="small"
108-
color={syncStatus?.dataFlowStatus.downloading ? 'success' : 'disabled'}
109-
sx={{ opacity: syncStatus?.dataFlowStatus.downloading ? 1 : 0.45 }}
110-
/>
111-
</Box>
112-
<Box
113-
component="span"
114-
sx={{
115-
display: 'inline-flex',
116-
alignItems: 'center',
117-
mr: 1.5,
118-
color: syncStatus?.connected ? 'success.main' : 'error.main'
119-
}}
120-
aria-label={syncStatus?.connected ? 'Connected' : 'Disconnected'}
121-
>
122-
{syncStatus?.connected ? (
123-
<WifiIcon sx={{ fontSize: '1.35rem' }} />
124-
) : (
125-
<SignalWifiOffIcon sx={{ fontSize: '1.35rem' }} />
126-
)}
117+
>
118+
<KeyboardArrowUpIcon
119+
sx={{
120+
fontSize: '1rem',
121+
color: 'success.main',
122+
display: 'block',
123+
opacity: syncStatus?.dataFlowStatus.uploading ? 1 : 0,
124+
visibility: syncStatus?.dataFlowStatus.uploading ? 'visible' : 'hidden'
125+
}}
126+
/>
127+
</Box>
128+
<Box
129+
sx={{
130+
display: 'flex',
131+
alignItems: 'center',
132+
lineHeight: 0,
133+
color: syncStatus?.connected ? 'success.main' : 'error.main'
134+
}}
135+
>
136+
{syncStatus?.connected ? (
137+
<WifiIcon sx={{ fontSize: '1.35rem' }} />
138+
) : (
139+
<SignalWifiOffIcon sx={{ fontSize: '1.35rem' }} />
140+
)}
141+
</Box>
142+
<Box
143+
sx={{
144+
height: { xs: 0, sm: 14 },
145+
minHeight: { xs: 0, sm: 14 },
146+
display: { xs: 'none', sm: 'flex' },
147+
alignItems: 'flex-start',
148+
justifyContent: 'center',
149+
mt: -0.75,
150+
lineHeight: 0
151+
}}
152+
>
153+
<KeyboardArrowDownIcon
154+
sx={{
155+
fontSize: '1rem',
156+
color: 'success.main',
157+
display: 'block',
158+
opacity: syncStatus?.dataFlowStatus.downloading ? 1 : 0,
159+
visibility: syncStatus?.dataFlowStatus.downloading ? 'visible' : 'hidden'
160+
}}
161+
/>
162+
</Box>
127163
</Box>
128164
<Button color="primary" variant="outlined" onClick={handleSignOut} startIcon={<LogoutIcon />} sx={{ ml: 2 }}>
129165
Logout

demos/react-convex-todolist/src/app/views/todo-lists/TodoEditModalRoute.tsx

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { TODO_LISTS_ROUTE } from '@/app/router';
22
import { LISTS_TABLE } from '@/library/powersync/AppSchema';
33
import CloseIcon from '@mui/icons-material/Close';
44
import {
5+
Box,
56
CircularProgress,
67
Dialog,
78
DialogContent,
@@ -18,7 +19,7 @@ import { TodoListsEditor } from './TodoListsEditor';
1819

1920
export default function TodoEditModalRoute() {
2021
const theme = useTheme();
21-
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
22+
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
2223
const navigate = useNavigate();
2324
const { id } = useParams<{ id: string }>();
2425

@@ -51,9 +52,26 @@ export default function TodoEditModalRoute() {
5152
display: 'flex',
5253
flexDirection: 'column',
5354
...(fullScreen
54-
? { borderRadius: 0, height: '100%', maxHeight: '100%' }
55+
? {
56+
borderRadius: 0,
57+
height: '100%',
58+
maxHeight: '100%',
59+
minHeight: '100dvh',
60+
maxWidth: '100%'
61+
}
5562
: {
56-
maxHeight: 'min(88vh, 840px)',
63+
minHeight: {
64+
sm: 'min(50vh, 440px)',
65+
md: 'min(56vh, 520px)',
66+
lg: 'min(60vh, 600px)',
67+
xl: 'min(62vh, 680px)'
68+
},
69+
maxHeight: {
70+
sm: 'min(91vh, 880px)',
71+
md: 'min(93vh, 1000px)',
72+
lg: 'min(95vh, 1160px)',
73+
xl: 'min(96vh, 1280px)'
74+
},
5775
borderRadius: 2,
5876
border: '1px solid',
5977
borderColor: 'divider'
@@ -89,7 +107,8 @@ export default function TodoEditModalRoute() {
89107
display: 'flex',
90108
flexDirection: 'column',
91109
flex: '1 1 auto',
92-
overflow: 'auto',
110+
minHeight: 0,
111+
overflow: 'hidden',
93112
bgcolor: 'background.default',
94113
borderBottomLeftRadius: (t) => (fullScreen ? 0 : Number(t.shape.borderRadius)),
95114
borderBottomRightRadius: (t) => (fullScreen ? 0 : Number(t.shape.borderRadius)),
@@ -99,8 +118,16 @@ export default function TodoEditModalRoute() {
99118
}
100119
}}
101120
>
102-
<Suspense fallback={<CircularProgress sx={{ alignSelf: 'center', my: 4 }} />}>
103-
<TodoListsEditor listId={id} />
121+
<Suspense
122+
fallback={
123+
<Box sx={{ flex: 1, minHeight: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', py: 6 }}>
124+
<CircularProgress />
125+
</Box>
126+
}
127+
>
128+
<Box sx={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
129+
<TodoListsEditor listId={id} />
130+
</Box>
104131
</Suspense>
105132
</DialogContent>
106133
</Dialog>

demos/react-convex-todolist/src/app/views/todo-lists/TodoListsEditor.tsx

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -86,20 +86,53 @@ export function TodoListsEditor(props: TodoListsEditorProps) {
8686
sx={{
8787
display: 'flex',
8888
flexDirection: 'column',
89-
minHeight: { xs: '50vh', md: 'min(70vh, 560px)' }
89+
flex: 1,
90+
minHeight: 0,
91+
width: '100%'
9092
}}
9193
>
92-
<List dense={false} sx={{ flex: '1 1 auto', pb: 0 }}>
93-
{todos.map((r) => (
94-
<TodoItemWidget
95-
key={r.id}
96-
description={r.description}
97-
onDelete={() => deleteTodo(r.id)}
98-
isComplete={r.completed === 1}
99-
toggleCompletion={() => toggleCompletion(r, r.completed !== 1)}
100-
/>
101-
))}
102-
</List>
94+
<Box
95+
sx={{
96+
flex: '1 1 auto',
97+
minHeight: 0,
98+
overflow: 'auto'
99+
}}
100+
>
101+
{todos.length === 0 ? (
102+
<Box
103+
sx={{
104+
minHeight: '100%',
105+
display: 'flex',
106+
flexDirection: 'column',
107+
alignItems: 'center',
108+
justifyContent: 'center',
109+
gap: 1,
110+
px: 2,
111+
py: 6,
112+
boxSizing: 'border-box'
113+
}}
114+
>
115+
<Typography variant="subtitle1" color="text.secondary" textAlign="center" sx={{ fontWeight: 700 }}>
116+
No todos yet
117+
</Typography>
118+
<Typography variant="body2" color="text.secondary" textAlign="center" sx={{ maxWidth: 360 }}>
119+
Add your first task with the field below. It syncs through PowerSync when you are online.
120+
</Typography>
121+
</Box>
122+
) : (
123+
<List dense={false} sx={{ pb: 0 }}>
124+
{todos.map((r) => (
125+
<TodoItemWidget
126+
key={r.id}
127+
description={r.description}
128+
onDelete={() => deleteTodo(r.id)}
129+
isComplete={r.completed === 1}
130+
toggleCompletion={() => toggleCompletion(r, r.completed !== 1)}
131+
/>
132+
))}
133+
</List>
134+
)}
135+
</Box>
103136
<OutlinedComposer
104137
value={newTodoText}
105138
onChange={setNewTodoText}
@@ -109,12 +142,10 @@ export function TodoListsEditor(props: TodoListsEditorProps) {
109142
submitAriaLabel="Add todo"
110143
autoFocus
111144
formSx={{
112-
position: 'sticky',
113-
bottom: 0,
114145
flexShrink: 0,
115-
pt: 0.5,
146+
pt: 1.5,
116147
pb: 0,
117-
mt: 'auto'
148+
width: '100%'
118149
}}
119150
/>
120151
</Box>

demos/react-convex-todolist/src/components/providers/SystemProvider.tsx

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,36 @@
11
import { NavigationPanelContextProvider } from '@/components/navigation/NavigationPanelContext';
22
import { AppSchema } from '@/library/powersync/AppSchema';
33
import { DemoConnector } from '@/library/powersync/DemoConnector';
4+
import { useAuthToken } from '@convex-dev/auth/react';
45
import { CircularProgress } from '@mui/material';
56
import { PowerSyncContext } from '@powersync/react';
67
import { PowerSyncDatabase } from '@powersync/web';
8+
import { useConvex } from 'convex/react';
79
import Logger from 'js-logger';
810
import React, { Suspense } from 'react';
9-
import { useAuthToken } from '@convex-dev/auth/react';
10-
import { useConvex } from 'convex/react';
1111

12-
export const db = new PowerSyncDatabase({
12+
// Linting thinks this is a hook due to it's name
13+
Logger.useDefaults(); // eslint-disable-line
14+
Logger.setLevel(Logger.DEBUG);
15+
16+
export const powerSync = new PowerSyncDatabase({
1317
database: {
1418
dbFilename: 'example-v2.db'
1519
},
1620
schema: AppSchema,
1721
logger: Logger
1822
});
1923

20-
// Make db accessible on the console for debugging
21-
(window as any).db = db;
24+
// For console testing purposes
25+
(window as any)._powersync = powerSync;
2226

2327
const ConnectorContext = React.createContext<DemoConnector | null>(null);
2428
export const useConnector = () => React.useContext(ConnectorContext);
2529

2630
const AuthAwareSystemProvider = ({ children }: { children: React.ReactNode }) => {
2731
const authToken = useAuthToken();
2832
const convexClient = useConvex();
29-
const [connector] = React.useState(new DemoConnector());
30-
const [powerSync] = React.useState(db);
31-
32-
// Provide the Convex client to the connector for direct mutation calls
33-
React.useEffect(() => {
34-
connector.setConvexClient(convexClient);
35-
}, [convexClient, connector]);
33+
const [connector] = React.useState(() => new DemoConnector({ convexClient }));
3634

3735
// Update connector with current auth token
3836
React.useEffect(() => {
@@ -42,17 +40,6 @@ const AuthAwareSystemProvider = ({ children }: { children: React.ReactNode }) =>
4240
connector.setAuthToken(authToken);
4341
}, [authToken, connector]);
4442

45-
React.useEffect(() => {
46-
// Linting thinks this is a hook due to it's name
47-
Logger.useDefaults(); // eslint-disable-line
48-
Logger.setLevel(Logger.DEBUG);
49-
50-
// For console testing purposes
51-
(window as any)._powersync = powerSync;
52-
53-
powerSync.init();
54-
}, [powerSync]);
55-
5643
React.useEffect(() => {
5744
if (authToken) {
5845
// Connect PowerSync when authenticated
@@ -61,7 +48,7 @@ const AuthAwareSystemProvider = ({ children }: { children: React.ReactNode }) =>
6148
// Disconnect PowerSync when not authenticated
6249
powerSync.disconnect();
6350
}
64-
}, [authToken, powerSync, connector]);
51+
}, [authToken, connector]);
6552

6653
return (
6754
<Suspense fallback={<CircularProgress />}>

0 commit comments

Comments
 (0)