Skip to content

Commit 852474b

Browse files
yuki-minakokoek-miyakeantfu
authored
feat: add workspace todo-list (#242)
Co-authored-by: kokoe <lettuce@lettuce.sakura.ne.jp> Co-authored-by: Kazuyuki Miyake <k-miyake@zengeeks.io> Co-authored-by: Anthony Fu <github@antfu.me>
1 parent 902a27d commit 852474b

File tree

53 files changed

+6505
-3
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+6505
-3
lines changed

components/ContentNavItem.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const props = withDefaults(
55
defineProps<{
66
item: ContentNavigationItem
77
level?: number
8+
current?: string
89
}>(),
910
{
1011
level: 0,
@@ -24,7 +25,7 @@ const paddingLeft = computed(() => `${0.5 + props.level * 0.8}rem`)
2425
</script>
2526

2627
<template>
27-
<div v-if="resolved" class="content-nav-item">
28+
<div v-if="resolved && (!(resolved.meta as any)?.unlisted || current?.startsWith(resolved.path))" class="content-nav-item">
2829
<template v-if="resolved.children?.length">
2930
<details :open="route.path.includes(resolved.path)">
3031
<summary>
@@ -45,6 +46,7 @@ const paddingLeft = computed(() => `${0.5 + props.level * 0.8}rem`)
4546
v-for="child of resolved.children"
4647
:key="child.path"
4748
:item="child"
49+
:current="current"
4850
:level="props.level + 1"
4951
/>
5052
</div>

components/PanelDocs.vue

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ const { data: page } = useAsyncData(`${locale.value}-${route.path}`, () => {
1313
.first()
1414
})
1515
const { data: navigation } = useAsyncData(`${locale.value}-navigation`, () => {
16-
return queryCollectionNavigation(collection.value)
16+
return queryCollectionNavigation(collection.value, [
17+
'title',
18+
'meta',
19+
'path',
20+
])
1721
})
1822
const { data: surroundings } = useAsyncData(`${locale.value}-${route.path}-surroundings`, () => {
1923
return queryCollectionItemSurroundings(collection.value, route.path, {
@@ -162,7 +166,13 @@ router.beforeEach(() => {
162166

163167
absolute left-0 right-0 top-0 max-h-60vh overflow-y-auto bg-base py2 backdrop-blur-10 important-bg-opacity-80
164168
>
165-
<ContentNavItem v-for="item of navigation" :key="item.path" :item="item" />
169+
<ContentNavItem
170+
v-for="item of navigation"
171+
:key="item.path"
172+
:item="item"
173+
:current="route.path"
174+
:level="1"
175+
/>
166176
</div>
167177
</Transition>
168178
</div>
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
<script setup lang="ts">
2+
import { ref, computed } from 'vue';
3+
import TodoList from './components/TodoList.vue'
4+
5+
/**
6+
* Data
7+
*/
8+
const todos = ref<Todo[]>([
9+
{
10+
id: 1,
11+
done: false,
12+
title: "Vue Fes Japan 2025のチケット販売開始の宣伝をする",
13+
note: "XとBlueskyで宣伝する。\n会社のslackでも宣伝する。",
14+
dueDate: "2025-10-24",
15+
},
16+
{
17+
id: 2,
18+
done: true,
19+
title: "Vue Fes Japan ボランティアスタッフに応募する",
20+
note: "",
21+
dueDate: "",
22+
},
23+
]);
24+
const showUnDoneOnly = ref(false);
25+
const isCreateModalOpen = ref(false);
26+
const inputTitile = ref('')
27+
const inputNote = ref('')
28+
const inputDate = ref('')
29+
30+
/**
31+
* Computed
32+
*/
33+
const filteredTodos = computed(() => {
34+
if (!showUnDoneOnly.value) {
35+
return todos.value;
36+
}
37+
38+
return todos.value.filter(todo => !todo.done);
39+
});
40+
41+
/**
42+
* Methods
43+
*/
44+
const updateDone = (id: number, done: boolean) => {
45+
const targetTodo = todos.value.find(todo => todo.id === id)
46+
47+
if (targetTodo) {
48+
targetTodo.done = done
49+
}
50+
}
51+
52+
const handleSubmit = () => {
53+
const newTodo: Todo = {
54+
id: Date.now(),
55+
done: false,
56+
title: inputTitile.value,
57+
note: inputNote.value,
58+
dueDate: inputDate.value
59+
}
60+
61+
todos.value = [
62+
newTodo,
63+
...todos.value
64+
]
65+
}
66+
67+
/**
68+
* Type
69+
*/
70+
type Todo = {
71+
id: number;
72+
done: boolean;
73+
title: string;
74+
note: string;
75+
dueDate: string;
76+
};
77+
</script>
78+
79+
<template>
80+
<div class="container">
81+
<header class="header">
82+
<div class="header-left">
83+
<h1>Vue TODO Application</h1>
84+
</div>
85+
<div class="header-right">
86+
👤
87+
<span>Vue Fes Japan</span>
88+
</div>
89+
</header>
90+
91+
<main>
92+
<div class="actions">
93+
<div>
94+
<div class="search-controls">
95+
<label>
96+
<input
97+
v-model="showUnDoneOnly"
98+
type="checkbox"
99+
/>
100+
未完了のみ表示
101+
</label>
102+
</div>
103+
</div>
104+
<button type="button" @click="isCreateModalOpen = true">新規作成</button>
105+
</div>
106+
107+
<TodoList :todos="filteredTodos" @update-done="updateDone"/>
108+
109+
110+
<!-- 新規作成モーダル -->
111+
<CreateModal
112+
v-if="isCreateModalOpen"
113+
v-model="isCreateModalOpen"
114+
>
115+
<form>
116+
<div>
117+
<label for="title">タイトル</label>
118+
<input id="title" v-model="inputTitile" type="text" required />
119+
</div>
120+
121+
<div>
122+
<label for="note">メモ</label>
123+
<textarea id="note" v-model="inputNote" rows="2" />
124+
</div>
125+
126+
<div>
127+
<label for="dueDate">期限</label>
128+
<input id="dueDate" v-model="inputDate" type="date" />
129+
</div>
130+
131+
<div>
132+
<button type="button" @click="handleSubmit">登録</button>
133+
</div>
134+
</form>
135+
</CreateModal>
136+
</main>
137+
138+
<footer class="footer">
139+
<p>Vue Fes Tokyo 2025</p>
140+
</footer>
141+
</div>
142+
</template>
143+
144+
<style scoped>
145+
.container {
146+
padding: 1rem 0 2.5rem;
147+
display: flex;
148+
flex-direction: column;
149+
gap: 2.5rem;
150+
min-height: 100vh;
151+
}
152+
153+
/* ------- header start ------- */
154+
.header {
155+
display: grid;
156+
grid-template-columns: 1fr auto;
157+
gap: 0.25rem;
158+
align-items: flex-end;
159+
}
160+
161+
.header-right {
162+
display: grid;
163+
grid-auto-flow: column;
164+
align-items: center;
165+
gap: 0.25rem;
166+
}
167+
168+
.header h1 {
169+
font-size: 1.5rem;
170+
font-weight: bold;
171+
}
172+
173+
.header img {
174+
width: 1.5rem;
175+
height: 1.5rem;
176+
}
177+
178+
.header span {
179+
font-size: 0.875rem;
180+
}
181+
/* ------- header last ------- */
182+
183+
/* ------- actions start ------- */
184+
.actions {
185+
display: grid;
186+
grid-template-columns: 1fr auto;
187+
gap: 0.5rem;
188+
align-items: flex-end;
189+
}
190+
191+
.search-controls {
192+
display: inline-grid;
193+
grid-auto-flow: column;
194+
align-items: center;
195+
gap: 0.5rem;
196+
font-size: 0.875rem;
197+
justify-content: start;
198+
}
199+
200+
.search-controls label {
201+
display: grid;
202+
grid-auto-flow: column;
203+
align-items: center;
204+
gap: 0.5rem;
205+
}
206+
207+
button {
208+
padding: 0.375rem 1rem;
209+
border-radius: 0.375rem;
210+
border: none;
211+
font-size: 0.875rem;
212+
background-color: #02C169;
213+
color: #fff;
214+
cursor: pointer;
215+
}
216+
217+
button:hover {
218+
background-color: #029E58;
219+
}
220+
/* ------- actions last ------- */
221+
222+
/* ------- form start ------- */
223+
form {
224+
display: grid;
225+
grid-auto-rows: min-content;
226+
gap: 1rem;
227+
font-size: 0.875rem;
228+
height: 100%;
229+
}
230+
231+
form > div {
232+
display: grid;
233+
gap: 0.25rem;
234+
}
235+
236+
input,
237+
textarea {
238+
padding: 0.375rem 0.5rem;
239+
border: 1px solid #ccc;
240+
border-radius: 0.25rem;
241+
}
242+
243+
/* ------- form last ------- */
244+
245+
/* footer */
246+
.footer {
247+
text-align: center;
248+
color: #666;
249+
}
250+
</style>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<script setup lang="ts">
2+
3+
/**
4+
* DefineModel
5+
*/
6+
const isCreateModalOpen = defineModel()
7+
8+
</script>
9+
10+
<template>
11+
<div class="modal">
12+
<div class="modal-content">
13+
<div class="modal-header">
14+
<h2>新規作成モーダル</h2>
15+
16+
<button aria-label="ダイアログを閉じる" @click="isCreateModalOpen = false">
17+
閉じる
18+
</button>
19+
</div>
20+
21+
<slot />
22+
</div>
23+
</div>
24+
</template>
25+
26+
<style scoped>
27+
.modal {
28+
position: fixed;
29+
top: 0;
30+
bottom: 0;
31+
right: 0;
32+
height: 100vh;
33+
width: 24rem;
34+
max-width: 100%;
35+
border: none;
36+
background: #fff;
37+
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1);
38+
z-index: 1000;
39+
overflow-y: auto;
40+
display: grid;
41+
grid-template-rows: auto 1fr;
42+
}
43+
44+
.dark .modal {
45+
background: #020420;
46+
}
47+
48+
.modal-content {
49+
display: grid;
50+
grid-template-rows: auto 1fr;
51+
gap: 1.5rem;
52+
padding: 1rem;
53+
}
54+
55+
.modal-header {
56+
display: grid;
57+
grid-template-columns: 1fr auto;
58+
align-items: center;
59+
}
60+
61+
.modal-header h2 {
62+
font-size: 1.125rem;
63+
font-weight: bold;
64+
}
65+
66+
67+
button {
68+
padding: 0.375rem 1rem;
69+
border-radius: 0.375rem;
70+
border: none;
71+
font-size: 0.875rem;
72+
background-color: #02C169;
73+
color: #fff;
74+
cursor: pointer;
75+
}
76+
77+
button:hover {
78+
background-color: #029E58;
79+
}
80+
</style>

0 commit comments

Comments
 (0)