Skip to content

Commit c7f22df

Browse files
committed
docs(demo): Routes added to vue todo
1 parent 0fed335 commit c7f22df

File tree

14 files changed

+552
-19
lines changed

14 files changed

+552
-19
lines changed

examples/vue-todo-app/README.md

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@ A simple Todo application demonstrating the use of `@data-client/vue` for reacti
44

55
## Features
66

7-
- ✅ Add, toggle, and delete todos
7+
- 👥 Browse all users from JSONPlaceholder
8+
- 🧭 **Universal Navigation** - Quick-switch between users from any page
9+
- ✅ Add, toggle, and delete todos for any user
810
- 🔄 Optimistic updates for instant UI feedback
11+
- 🛣️ Vue Router integration for navigation
912
- 🌐 RESTful API integration with JSONPlaceholder
1013
- 💾 Automatic caching and normalization
11-
- 🎨 Modern, responsive UI
14+
- 🎨 Modern, responsive UI with sticky navigation
1215
- ⚡ Built with Vite for fast development
1316

1417
## Tech Stack
1518

1619
- **Vue 3** - Progressive JavaScript framework
20+
- **Vue Router 4** - Official router for Vue.js
1721
- **@data-client/vue** - Reactive normalized state management
1822
- **@data-client/rest** - REST resource definitions
1923
- **TypeScript** - Type safety
@@ -53,15 +57,23 @@ npm run preview
5357
vue-todo-app/
5458
├── src/
5559
│ ├── components/
60+
│ │ ├── AppNavigation.vue # Persistent top navigation
61+
│ │ ├── NavigationContent.vue # User switcher in navigation
5662
│ │ ├── TodoItem.vue # Individual todo item component
5763
│ │ ├── TodoList.vue # Main todo list container
58-
│ │ └── TodoListContent.vue # Todo list content with suspense
64+
│ │ ├── TodoListContent.vue # Todo list content with suspense
65+
│ │ ├── UserHeader.vue # User header in todo page
66+
│ │ └── UserListContent.vue # User list content with suspense
67+
│ ├── pages/
68+
│ │ ├── UserList.vue # Users listing page
69+
│ │ └── UserTodos.vue # User todos page
5970
│ ├── resources/
6071
│ │ ├── PlaceholderBaseResource.ts # Base resource configuration
6172
│ │ ├── TodoResource.ts # Todo entity and endpoints
6273
│ │ └── UserResource.ts # User entity and endpoints
63-
│ ├── App.vue # Root component
64-
│ └── main.ts # App entry point
74+
│ ├── App.vue # Root component with navigation
75+
│ ├── main.ts # App entry point
76+
│ └── router.ts # Vue Router configuration
6577
├── index.html
6678
├── package.json
6779
├── tsconfig.json
@@ -90,6 +102,29 @@ Resources are defined using `@data-client/rest`:
90102

91103
The app uses optimistic updates for instant UI feedback. Changes appear immediately while the API request is in flight.
92104

105+
## Usage
106+
107+
1. **Browse Users**: Start at the home page to see all available users
108+
2. **View Todos**: Click on any user to view their todos
109+
3. **Quick Switch**: Use the top navigation bar to instantly switch between any user's todos
110+
4. **Add Todos**: Type in the input field and press Enter or click "Add"
111+
5. **Toggle Completion**: Click the checkbox to mark todos as complete/incomplete
112+
6. **Delete Todos**: Click the × button to delete a todo
113+
7. **Navigate Home**: Click the "Vue Todo App" brand in the navigation to return to the user list
114+
115+
### Universal Navigation
116+
117+
The app features a persistent navigation bar at the top of every page with:
118+
- **Home Link**: Click the brand logo to return to the user list
119+
- **Quick User Switcher**: Horizontally scrollable list of all users with avatars
120+
- **Active Indicator**: The current user is highlighted in the navigation
121+
- **Responsive**: On mobile, shows only avatars; on desktop, shows names too
122+
123+
## Routes
124+
125+
- `/` - User list page
126+
- `/user/:userId/todos` - Todos for a specific user (e.g., `/user/1/todos`)
127+
93128
## API
94129

95130
This app uses the [JSONPlaceholder](https://jsonplaceholder.typicode.com) API for demonstration purposes. Note that the API doesn't persist changes, but the app simulates persistence through Data Client's caching.

examples/vue-todo-app/package-lock.json

Lines changed: 23 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/vue-todo-app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@data-client/vue": "^0.3.2",
1616
"@data-client/rest": "0.15.0-beta-20251022142546-a457d1596871fb28f1a91f2531cc259db4d55a9c",
1717
"vue": "^3.5.22",
18+
"vue-router": "^4.6.3",
1819
"uuid": "^13.0.0"
1920
},
2021
"devDependencies": {

examples/vue-todo-app/src/App.vue

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
<template>
22
<div id="app">
3-
<TodoList />
3+
<AppNavigation />
4+
<router-view />
45
</div>
56
</template>
67

78
<script setup lang="ts">
8-
import TodoList from './components/TodoList.vue';
9+
import AppNavigation from './components/AppNavigation.vue';
910
</script>
1011

1112
<style>
@@ -26,7 +27,9 @@ body {
2627
}
2728
2829
#app {
29-
padding: 20px;
30+
display: flex;
31+
flex-direction: column;
32+
min-height: 100vh;
3033
}
3134
</style>
3235

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<template>
2+
<nav class="app-nav">
3+
<div class="nav-container">
4+
<router-link to="/" class="nav-brand">
5+
📝 Vue Todos
6+
</router-link>
7+
8+
<Suspense>
9+
<template #default>
10+
<NavigationContent />
11+
</template>
12+
<template #fallback>
13+
<div class="nav-loading">Loading...</div>
14+
</template>
15+
</Suspense>
16+
</div>
17+
</nav>
18+
</template>
19+
20+
<script setup lang="ts">
21+
import NavigationContent from './NavigationContent.vue';
22+
</script>
23+
24+
<style scoped>
25+
.app-nav {
26+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
27+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
28+
position: sticky;
29+
top: 0;
30+
z-index: 100;
31+
}
32+
33+
.nav-container {
34+
max-width: 1200px;
35+
margin: 0 auto;
36+
padding: 12px 20px;
37+
display: flex;
38+
align-items: center;
39+
gap: 24px;
40+
}
41+
42+
.nav-brand {
43+
color: white;
44+
text-decoration: none;
45+
font-size: 18px;
46+
font-weight: 700;
47+
white-space: nowrap;
48+
transition: opacity 0.2s;
49+
}
50+
51+
.nav-brand:hover {
52+
opacity: 0.9;
53+
}
54+
55+
.nav-loading {
56+
color: white;
57+
font-size: 14px;
58+
opacity: 0.8;
59+
}
60+
</style>
61+
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<template>
2+
<div class="nav-users">
3+
<div class="user-links">
4+
<router-link
5+
v-for="user in users"
6+
:key="user.id"
7+
:to="`/user/${user.id}/todos`"
8+
class="user-link"
9+
:class="{ active: isActive(user.id) }"
10+
:title="user.name"
11+
>
12+
<img :src="user.profileImage" :alt="user.name" class="user-avatar-small" />
13+
<span class="user-link-text">{{ user.name }}</span>
14+
</router-link>
15+
</div>
16+
</div>
17+
</template>
18+
19+
<script setup lang="ts">
20+
import { useRoute } from 'vue-router';
21+
import { useSuspense } from '@data-client/vue';
22+
import { UserResource } from '../resources/UserResource';
23+
24+
const route = useRoute();
25+
const users = await useSuspense(UserResource.getList);
26+
27+
const isActive = (userId: number) => {
28+
return route.params.userId === String(userId);
29+
};
30+
</script>
31+
32+
<style scoped>
33+
.nav-users {
34+
display: flex;
35+
align-items: center;
36+
gap: 16px;
37+
flex: 1;
38+
overflow: hidden;
39+
}
40+
41+
.nav-label {
42+
color: white;
43+
font-size: 14px;
44+
font-weight: 600;
45+
white-space: nowrap;
46+
opacity: 0.9;
47+
}
48+
49+
.user-links {
50+
display: flex;
51+
gap: 8px;
52+
overflow-x: auto;
53+
scrollbar-width: thin;
54+
scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
55+
padding: 4px 0;
56+
}
57+
58+
.user-links::-webkit-scrollbar {
59+
height: 6px;
60+
}
61+
62+
.user-links::-webkit-scrollbar-track {
63+
background: transparent;
64+
}
65+
66+
.user-links::-webkit-scrollbar-thumb {
67+
background: rgba(255, 255, 255, 0.3);
68+
border-radius: 3px;
69+
}
70+
71+
.user-links::-webkit-scrollbar-thumb:hover {
72+
background: rgba(255, 255, 255, 0.5);
73+
}
74+
75+
.user-link {
76+
display: flex;
77+
align-items: center;
78+
gap: 8px;
79+
padding: 6px 12px;
80+
background: rgba(255, 255, 255, 0.15);
81+
border: 2px solid transparent;
82+
border-radius: 20px;
83+
color: white;
84+
text-decoration: none;
85+
font-size: 14px;
86+
font-weight: 500;
87+
white-space: nowrap;
88+
transition: all 0.2s;
89+
}
90+
91+
.user-link:hover {
92+
background: rgba(255, 255, 255, 0.25);
93+
transform: translateY(-1px);
94+
}
95+
96+
.user-link.active {
97+
background: white;
98+
color: #667eea;
99+
border-color: white;
100+
font-weight: 600;
101+
}
102+
103+
.user-avatar-small {
104+
width: 24px;
105+
height: 24px;
106+
border-radius: 50%;
107+
border: 2px solid rgba(255, 255, 255, 0.5);
108+
}
109+
110+
.user-link.active .user-avatar-small {
111+
border-color: #667eea;
112+
}
113+
114+
.user-link-text {
115+
max-width: 120px;
116+
overflow: hidden;
117+
text-overflow: ellipsis;
118+
}
119+
120+
@media (max-width: 768px) {
121+
.user-link-text {
122+
display: none;
123+
}
124+
125+
.user-link {
126+
padding: 6px;
127+
}
128+
}
129+
</style>
130+

0 commit comments

Comments
 (0)