@even-toolkit/create-even-app 1.1.0
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/index.js +159 -0
- package/package.json +28 -0
- package/templates/chat/README.md +27 -0
- package/templates/chat/index.html +12 -0
- package/templates/chat/package.json +34 -0
- package/templates/chat/src/App.tsx +61 -0
- package/templates/chat/src/app.css +54 -0
- package/templates/chat/src/contexts/ChatContext.tsx +99 -0
- package/templates/chat/src/glass/AppGlasses.tsx +70 -0
- package/templates/chat/src/glass/screens/home.ts +24 -0
- package/templates/chat/src/glass/selectors.ts +9 -0
- package/templates/chat/src/glass/shared.ts +8 -0
- package/templates/chat/src/glass/splash.ts +25 -0
- package/templates/chat/src/main.tsx +13 -0
- package/templates/chat/src/screens/ChatScreen.tsx +69 -0
- package/templates/chat/src/screens/Settings.tsx +88 -0
- package/templates/chat/src/types.ts +13 -0
- package/templates/chat/src/vite-env.d.ts +1 -0
- package/templates/chat/template.json +7 -0
- package/templates/chat/tsconfig.json +20 -0
- package/templates/chat/tsconfig.node.json +13 -0
- package/templates/chat/vite.config.ts +12 -0
- package/templates/dashboard/README.md +17 -0
- package/templates/dashboard/index.html +12 -0
- package/templates/dashboard/package.json +34 -0
- package/templates/dashboard/src/App.tsx +27 -0
- package/templates/dashboard/src/app.css +54 -0
- package/templates/dashboard/src/glass/AppGlasses.tsx +53 -0
- package/templates/dashboard/src/glass/screens/home.ts +23 -0
- package/templates/dashboard/src/glass/selectors.ts +9 -0
- package/templates/dashboard/src/glass/shared.ts +8 -0
- package/templates/dashboard/src/glass/splash.ts +22 -0
- package/templates/dashboard/src/main.tsx +13 -0
- package/templates/dashboard/src/screens/ChartsScreen.tsx +99 -0
- package/templates/dashboard/src/screens/OverviewScreen.tsx +102 -0
- package/templates/dashboard/src/screens/SettingsScreen.tsx +60 -0
- package/templates/dashboard/src/vite-env.d.ts +1 -0
- package/templates/dashboard/template.json +7 -0
- package/templates/dashboard/tsconfig.json +20 -0
- package/templates/dashboard/tsconfig.node.json +13 -0
- package/templates/dashboard/vite.config.ts +12 -0
- package/templates/media/README.md +27 -0
- package/templates/media/index.html +12 -0
- package/templates/media/package.json +34 -0
- package/templates/media/src/App.tsx +24 -0
- package/templates/media/src/app.css +54 -0
- package/templates/media/src/contexts/MediaContext.tsx +108 -0
- package/templates/media/src/glass/AppGlasses.tsx +59 -0
- package/templates/media/src/glass/screens/home.ts +24 -0
- package/templates/media/src/glass/selectors.ts +9 -0
- package/templates/media/src/glass/shared.ts +8 -0
- package/templates/media/src/glass/splash.ts +25 -0
- package/templates/media/src/layouts/shell.tsx +39 -0
- package/templates/media/src/main.tsx +13 -0
- package/templates/media/src/screens/AudioScreen.tsx +78 -0
- package/templates/media/src/screens/GalleryScreen.tsx +98 -0
- package/templates/media/src/screens/Settings.tsx +86 -0
- package/templates/media/src/screens/UploadScreen.tsx +95 -0
- package/templates/media/src/types.ts +29 -0
- package/templates/media/src/vite-env.d.ts +1 -0
- package/templates/media/template.json +7 -0
- package/templates/media/tsconfig.json +20 -0
- package/templates/media/tsconfig.node.json +13 -0
- package/templates/media/vite.config.ts +12 -0
- package/templates/minimal/README.md +27 -0
- package/templates/minimal/index.html +12 -0
- package/templates/minimal/package.json +34 -0
- package/templates/minimal/src/App.tsx +50 -0
- package/templates/minimal/src/app.css +54 -0
- package/templates/minimal/src/glass/AppGlasses.tsx +54 -0
- package/templates/minimal/src/glass/screens/home.ts +24 -0
- package/templates/minimal/src/glass/selectors.ts +9 -0
- package/templates/minimal/src/glass/shared.ts +8 -0
- package/templates/minimal/src/glass/splash.ts +25 -0
- package/templates/minimal/src/main.tsx +13 -0
- package/templates/minimal/src/vite-env.d.ts +1 -0
- package/templates/minimal/template.json +7 -0
- package/templates/minimal/tsconfig.json +20 -0
- package/templates/minimal/tsconfig.node.json +13 -0
- package/templates/minimal/vite.config.ts +12 -0
- package/templates/notes/README.md +27 -0
- package/templates/notes/index.html +12 -0
- package/templates/notes/package.json +34 -0
- package/templates/notes/src/App.tsx +25 -0
- package/templates/notes/src/app.css +54 -0
- package/templates/notes/src/contexts/NotesContext.tsx +140 -0
- package/templates/notes/src/glass/AppGlasses.tsx +58 -0
- package/templates/notes/src/glass/screens/home.ts +24 -0
- package/templates/notes/src/glass/selectors.ts +9 -0
- package/templates/notes/src/glass/shared.ts +8 -0
- package/templates/notes/src/glass/splash.ts +24 -0
- package/templates/notes/src/layouts/shell.tsx +36 -0
- package/templates/notes/src/main.tsx +13 -0
- package/templates/notes/src/screens/NoteDetail.tsx +104 -0
- package/templates/notes/src/screens/NoteForm.tsx +84 -0
- package/templates/notes/src/screens/NoteList.tsx +108 -0
- package/templates/notes/src/screens/Settings.tsx +88 -0
- package/templates/notes/src/types.ts +14 -0
- package/templates/notes/src/vite-env.d.ts +1 -0
- package/templates/notes/template.json +7 -0
- package/templates/notes/tsconfig.json +20 -0
- package/templates/notes/tsconfig.node.json +13 -0
- package/templates/notes/vite.config.ts +12 -0
- package/templates/tracker/README.md +27 -0
- package/templates/tracker/index.html +12 -0
- package/templates/tracker/package.json +34 -0
- package/templates/tracker/src/App.tsx +24 -0
- package/templates/tracker/src/app.css +54 -0
- package/templates/tracker/src/contexts/TrackerContext.tsx +193 -0
- package/templates/tracker/src/glass/AppGlasses.tsx +64 -0
- package/templates/tracker/src/glass/screens/home.ts +24 -0
- package/templates/tracker/src/glass/selectors.ts +9 -0
- package/templates/tracker/src/glass/shared.ts +8 -0
- package/templates/tracker/src/glass/splash.ts +24 -0
- package/templates/tracker/src/layouts/shell.tsx +37 -0
- package/templates/tracker/src/main.tsx +13 -0
- package/templates/tracker/src/screens/HistoryScreen.tsx +106 -0
- package/templates/tracker/src/screens/NewEntryScreen.tsx +135 -0
- package/templates/tracker/src/screens/Settings.tsx +135 -0
- package/templates/tracker/src/screens/TodayScreen.tsx +147 -0
- package/templates/tracker/src/types.ts +34 -0
- package/templates/tracker/src/vite-env.d.ts +1 -0
- package/templates/tracker/template.json +7 -0
- package/templates/tracker/tsconfig.json +20 -0
- package/templates/tracker/tsconfig.node.json +13 -0
- package/templates/tracker/vite.config.ts +12 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { StrictMode } from 'react'
|
|
2
|
+
import { createRoot } from 'react-dom/client'
|
|
3
|
+
import { BrowserRouter } from 'react-router'
|
|
4
|
+
import { App } from './App'
|
|
5
|
+
import './app.css'
|
|
6
|
+
|
|
7
|
+
createRoot(document.getElementById('root')!).render(
|
|
8
|
+
<StrictMode>
|
|
9
|
+
<BrowserRouter>
|
|
10
|
+
<App />
|
|
11
|
+
</BrowserRouter>
|
|
12
|
+
</StrictMode>,
|
|
13
|
+
)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useParams, useNavigate } from 'react-router'
|
|
2
|
+
import { Card, Badge, Button, EmptyState, useDrawerHeader } from 'even-toolkit/web'
|
|
3
|
+
import { IcEdit, IcTrash } from 'even-toolkit/web/icons/svg-icons'
|
|
4
|
+
import { useNotes } from '../contexts/NotesContext'
|
|
5
|
+
|
|
6
|
+
const CATEGORY_BADGE_VARIANT = {
|
|
7
|
+
Personal: 'positive',
|
|
8
|
+
Work: 'accent',
|
|
9
|
+
Ideas: 'neutral',
|
|
10
|
+
} as const
|
|
11
|
+
|
|
12
|
+
function formatDate(timestamp: number): string {
|
|
13
|
+
return new Date(timestamp).toLocaleDateString('en-US', {
|
|
14
|
+
weekday: 'short',
|
|
15
|
+
month: 'short',
|
|
16
|
+
day: 'numeric',
|
|
17
|
+
year: 'numeric',
|
|
18
|
+
hour: 'numeric',
|
|
19
|
+
minute: '2-digit',
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function NoteDetail() {
|
|
24
|
+
const { id } = useParams<{ id: string }>()
|
|
25
|
+
const navigate = useNavigate()
|
|
26
|
+
const { notes, deleteNote } = useNotes()
|
|
27
|
+
|
|
28
|
+
const note = notes.find((n) => n.id === id)
|
|
29
|
+
|
|
30
|
+
useDrawerHeader({
|
|
31
|
+
title: note?.title ?? 'Note',
|
|
32
|
+
backTo: '/',
|
|
33
|
+
right: note ? (
|
|
34
|
+
<div className="flex items-center gap-1">
|
|
35
|
+
<Button variant="ghost" size="icon" onClick={() => navigate(`/note/${note.id}/edit`)}>
|
|
36
|
+
<IcEdit width={20} height={20} />
|
|
37
|
+
</Button>
|
|
38
|
+
<Button
|
|
39
|
+
variant="ghost"
|
|
40
|
+
size="icon"
|
|
41
|
+
onClick={() => {
|
|
42
|
+
deleteNote(note.id)
|
|
43
|
+
navigate('/')
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
<IcTrash width={20} height={20} />
|
|
47
|
+
</Button>
|
|
48
|
+
</div>
|
|
49
|
+
) : undefined,
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
if (!note) {
|
|
53
|
+
return (
|
|
54
|
+
<main className="px-3 pt-4 pb-8">
|
|
55
|
+
<EmptyState
|
|
56
|
+
title="Note not found"
|
|
57
|
+
description="This note may have been deleted."
|
|
58
|
+
action={{ label: 'Back to Notes', onClick: () => navigate('/') }}
|
|
59
|
+
/>
|
|
60
|
+
</main>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<main className="px-3 pt-4 pb-8 space-y-3">
|
|
66
|
+
<div className="flex items-center gap-2">
|
|
67
|
+
<Badge variant={CATEGORY_BADGE_VARIANT[note.category]}>{note.category}</Badge>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<Card className="p-4 space-y-3">
|
|
71
|
+
<p className="text-[15px] tracking-[-0.15px] text-text whitespace-pre-wrap leading-relaxed">
|
|
72
|
+
{note.content}
|
|
73
|
+
</p>
|
|
74
|
+
</Card>
|
|
75
|
+
|
|
76
|
+
<Card className="p-4 space-y-1.5">
|
|
77
|
+
<div className="flex items-center justify-between">
|
|
78
|
+
<span className="text-[11px] tracking-[-0.11px] text-text-dim">Created</span>
|
|
79
|
+
<span className="text-[11px] tracking-[-0.11px] text-text">{formatDate(note.createdAt)}</span>
|
|
80
|
+
</div>
|
|
81
|
+
<div className="flex items-center justify-between">
|
|
82
|
+
<span className="text-[11px] tracking-[-0.11px] text-text-dim">Updated</span>
|
|
83
|
+
<span className="text-[11px] tracking-[-0.11px] text-text">{formatDate(note.updatedAt)}</span>
|
|
84
|
+
</div>
|
|
85
|
+
</Card>
|
|
86
|
+
|
|
87
|
+
<div className="flex gap-2">
|
|
88
|
+
<Button className="flex-1" onClick={() => navigate(`/note/${note.id}/edit`)}>
|
|
89
|
+
Edit Note
|
|
90
|
+
</Button>
|
|
91
|
+
<Button
|
|
92
|
+
variant="danger"
|
|
93
|
+
className="flex-1"
|
|
94
|
+
onClick={() => {
|
|
95
|
+
deleteNote(note.id)
|
|
96
|
+
navigate('/')
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
Delete
|
|
100
|
+
</Button>
|
|
101
|
+
</div>
|
|
102
|
+
</main>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
2
|
+
import { useParams, useNavigate } from 'react-router'
|
|
3
|
+
import { Input, Textarea, Select, Button, Card, useDrawerHeader } from 'even-toolkit/web'
|
|
4
|
+
import { useNotes } from '../contexts/NotesContext'
|
|
5
|
+
import { CATEGORIES, type NoteCategory } from '../types'
|
|
6
|
+
|
|
7
|
+
const CATEGORY_OPTIONS = CATEGORIES.map((c) => ({ value: c, label: c }))
|
|
8
|
+
|
|
9
|
+
export function NoteForm() {
|
|
10
|
+
const { id } = useParams<{ id: string }>()
|
|
11
|
+
const navigate = useNavigate()
|
|
12
|
+
const { notes, addNote, updateNote } = useNotes()
|
|
13
|
+
|
|
14
|
+
const existing = id ? notes.find((n) => n.id === id) : undefined
|
|
15
|
+
const isEdit = Boolean(existing)
|
|
16
|
+
|
|
17
|
+
const [title, setTitle] = useState(existing?.title ?? '')
|
|
18
|
+
const [content, setContent] = useState(existing?.content ?? '')
|
|
19
|
+
const [category, setCategory] = useState<NoteCategory>(existing?.category ?? 'Personal')
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (existing) {
|
|
23
|
+
setTitle(existing.title)
|
|
24
|
+
setContent(existing.content)
|
|
25
|
+
setCategory(existing.category)
|
|
26
|
+
}
|
|
27
|
+
}, [existing])
|
|
28
|
+
|
|
29
|
+
useDrawerHeader({
|
|
30
|
+
title: isEdit ? 'Edit Note' : 'New Note',
|
|
31
|
+
backTo: isEdit && id ? `/note/${id}` : '/',
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const canSave = title.trim().length > 0
|
|
35
|
+
|
|
36
|
+
function handleSave() {
|
|
37
|
+
if (!canSave) return
|
|
38
|
+
if (isEdit && id) {
|
|
39
|
+
updateNote(id, title.trim(), content.trim(), category)
|
|
40
|
+
navigate(`/note/${id}`)
|
|
41
|
+
} else {
|
|
42
|
+
const note = addNote(title.trim(), content.trim(), category)
|
|
43
|
+
navigate(`/note/${note.id}`)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<main className="px-3 pt-4 pb-8 space-y-3">
|
|
49
|
+
<Card className="p-4 space-y-3">
|
|
50
|
+
<div className="space-y-1.5">
|
|
51
|
+
<label className="text-[11px] tracking-[-0.11px] text-text-dim block">Title</label>
|
|
52
|
+
<Input
|
|
53
|
+
value={title}
|
|
54
|
+
onChange={(e) => setTitle(e.target.value)}
|
|
55
|
+
placeholder="Note title"
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div className="space-y-1.5">
|
|
60
|
+
<label className="text-[11px] tracking-[-0.11px] text-text-dim block">Content</label>
|
|
61
|
+
<Textarea
|
|
62
|
+
value={content}
|
|
63
|
+
onChange={(e) => setContent(e.target.value)}
|
|
64
|
+
placeholder="Write your note..."
|
|
65
|
+
rows={8}
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div className="space-y-1.5">
|
|
70
|
+
<label className="text-[11px] tracking-[-0.11px] text-text-dim block">Category</label>
|
|
71
|
+
<Select
|
|
72
|
+
options={CATEGORY_OPTIONS}
|
|
73
|
+
value={category}
|
|
74
|
+
onValueChange={(v) => setCategory(v as NoteCategory)}
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
</Card>
|
|
78
|
+
|
|
79
|
+
<Button className="w-full" onClick={handleSave} disabled={!canSave}>
|
|
80
|
+
{isEdit ? 'Save Changes' : 'Create Note'}
|
|
81
|
+
</Button>
|
|
82
|
+
</main>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { useNavigate } from 'react-router'
|
|
2
|
+
import { SearchBar, CategoryFilter, ListItem, Badge, EmptyState, Button, ScreenHeader, useDrawerHeader } from 'even-toolkit/web'
|
|
3
|
+
import { IcPlus, IcFeatQuickNote } from 'even-toolkit/web/icons/svg-icons'
|
|
4
|
+
import { useNotes } from '../contexts/NotesContext'
|
|
5
|
+
import { ALL_FILTER, CATEGORIES, type CategoryFilter as CategoryFilterType } from '../types'
|
|
6
|
+
|
|
7
|
+
const FILTER_OPTIONS = [ALL_FILTER, ...CATEGORIES]
|
|
8
|
+
|
|
9
|
+
const CATEGORY_BADGE_VARIANT = {
|
|
10
|
+
Personal: 'positive',
|
|
11
|
+
Work: 'accent',
|
|
12
|
+
Ideas: 'neutral',
|
|
13
|
+
} as const
|
|
14
|
+
|
|
15
|
+
function formatRelativeTime(timestamp: number): string {
|
|
16
|
+
const diff = Date.now() - timestamp
|
|
17
|
+
const minutes = Math.floor(diff / 60000)
|
|
18
|
+
if (minutes < 1) return 'Just now'
|
|
19
|
+
if (minutes < 60) return `${minutes}m ago`
|
|
20
|
+
const hours = Math.floor(minutes / 60)
|
|
21
|
+
if (hours < 24) return `${hours}h ago`
|
|
22
|
+
const days = Math.floor(hours / 24)
|
|
23
|
+
if (days < 7) return `${days}d ago`
|
|
24
|
+
return new Date(timestamp).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function NoteList() {
|
|
28
|
+
const navigate = useNavigate()
|
|
29
|
+
const {
|
|
30
|
+
filteredNotes,
|
|
31
|
+
searchQuery,
|
|
32
|
+
setSearchQuery,
|
|
33
|
+
selectedCategory,
|
|
34
|
+
setSelectedCategory,
|
|
35
|
+
deleteNote,
|
|
36
|
+
compactView,
|
|
37
|
+
} = useNotes()
|
|
38
|
+
|
|
39
|
+
useDrawerHeader({
|
|
40
|
+
right: (
|
|
41
|
+
<Button variant="ghost" size="icon" onClick={() => navigate('/new')}>
|
|
42
|
+
<IcPlus width={20} height={20} />
|
|
43
|
+
</Button>
|
|
44
|
+
),
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<main className="px-3 pt-4 pb-8 space-y-3">
|
|
49
|
+
<ScreenHeader
|
|
50
|
+
title="Notes"
|
|
51
|
+
subtitle={`${filteredNotes.length} note${filteredNotes.length !== 1 ? 's' : ''}`}
|
|
52
|
+
/>
|
|
53
|
+
|
|
54
|
+
<SearchBar
|
|
55
|
+
value={searchQuery}
|
|
56
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
57
|
+
placeholder="Search notes..."
|
|
58
|
+
/>
|
|
59
|
+
|
|
60
|
+
<CategoryFilter
|
|
61
|
+
categories={FILTER_OPTIONS}
|
|
62
|
+
selected={selectedCategory}
|
|
63
|
+
onSelect={(c) => setSelectedCategory(c as CategoryFilterType)}
|
|
64
|
+
/>
|
|
65
|
+
|
|
66
|
+
{filteredNotes.length === 0 ? (
|
|
67
|
+
<EmptyState
|
|
68
|
+
icon={<IcFeatQuickNote width={48} height={48} />}
|
|
69
|
+
title={searchQuery || selectedCategory !== ALL_FILTER ? 'No matching notes' : 'No notes yet'}
|
|
70
|
+
description={
|
|
71
|
+
searchQuery || selectedCategory !== ALL_FILTER
|
|
72
|
+
? 'Try adjusting your search or category filter.'
|
|
73
|
+
: 'Tap the + button to create your first note.'
|
|
74
|
+
}
|
|
75
|
+
action={
|
|
76
|
+
!searchQuery && selectedCategory === ALL_FILTER
|
|
77
|
+
? { label: 'New Note', onClick: () => navigate('/new') }
|
|
78
|
+
: undefined
|
|
79
|
+
}
|
|
80
|
+
/>
|
|
81
|
+
) : (
|
|
82
|
+
<div className="rounded-[6px] overflow-hidden divide-y divide-border">
|
|
83
|
+
{filteredNotes.map((note) => (
|
|
84
|
+
<ListItem
|
|
85
|
+
key={note.id}
|
|
86
|
+
title={note.title}
|
|
87
|
+
subtitle={
|
|
88
|
+
compactView
|
|
89
|
+
? undefined
|
|
90
|
+
: note.content.split('\n')[0].slice(0, 80) + (note.content.length > 80 ? '...' : '')
|
|
91
|
+
}
|
|
92
|
+
trailing={
|
|
93
|
+
<div className="flex items-center gap-2">
|
|
94
|
+
<Badge variant={CATEGORY_BADGE_VARIANT[note.category]}>{note.category}</Badge>
|
|
95
|
+
<span className="text-[11px] tracking-[-0.11px] text-text-dim whitespace-nowrap">
|
|
96
|
+
{formatRelativeTime(note.updatedAt)}
|
|
97
|
+
</span>
|
|
98
|
+
</div>
|
|
99
|
+
}
|
|
100
|
+
onPress={() => navigate(`/note/${note.id}`)}
|
|
101
|
+
onDelete={() => deleteNote(note.id)}
|
|
102
|
+
/>
|
|
103
|
+
))}
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
</main>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { SettingsGroup, Toggle, ListItem, Card, Button, Divider, useDrawerHeader } from 'even-toolkit/web'
|
|
3
|
+
import { useNotes } from '../contexts/NotesContext'
|
|
4
|
+
|
|
5
|
+
export function Settings() {
|
|
6
|
+
const { notes, compactView, setCompactView } = useNotes()
|
|
7
|
+
const [confirmClear, setConfirmClear] = useState(false)
|
|
8
|
+
|
|
9
|
+
useDrawerHeader({ title: 'Settings', backTo: '/' })
|
|
10
|
+
|
|
11
|
+
function handleExport() {
|
|
12
|
+
const data = JSON.stringify(notes, null, 2)
|
|
13
|
+
const blob = new Blob([data], { type: 'application/json' })
|
|
14
|
+
const url = URL.createObjectURL(blob)
|
|
15
|
+
const a = document.createElement('a')
|
|
16
|
+
a.href = url
|
|
17
|
+
a.download = `notes-export-${Date.now()}.json`
|
|
18
|
+
a.click()
|
|
19
|
+
URL.revokeObjectURL(url)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function handleClearAll() {
|
|
23
|
+
if (!confirmClear) {
|
|
24
|
+
setConfirmClear(true)
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
localStorage.removeItem('{{APP_NAME}}-notes')
|
|
28
|
+
window.location.reload()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<main className="px-3 pt-4 pb-8 space-y-6">
|
|
33
|
+
<SettingsGroup label="Display">
|
|
34
|
+
<Card className="p-4">
|
|
35
|
+
<div className="flex items-center justify-between">
|
|
36
|
+
<div>
|
|
37
|
+
<p className="text-[15px] tracking-[-0.15px] text-text">Compact View</p>
|
|
38
|
+
<p className="text-[11px] tracking-[-0.11px] text-text-dim mt-0.5">
|
|
39
|
+
Hide note previews in the list
|
|
40
|
+
</p>
|
|
41
|
+
</div>
|
|
42
|
+
<Toggle checked={compactView} onChange={setCompactView} />
|
|
43
|
+
</div>
|
|
44
|
+
</Card>
|
|
45
|
+
</SettingsGroup>
|
|
46
|
+
|
|
47
|
+
<SettingsGroup label="Data">
|
|
48
|
+
<Card className="divide-y divide-border">
|
|
49
|
+
<ListItem
|
|
50
|
+
title="Export Notes"
|
|
51
|
+
subtitle={`Export all ${notes.length} notes as JSON`}
|
|
52
|
+
onPress={handleExport}
|
|
53
|
+
/>
|
|
54
|
+
<div>
|
|
55
|
+
<ListItem
|
|
56
|
+
title={confirmClear ? 'Tap again to confirm' : 'Clear All Notes'}
|
|
57
|
+
subtitle="Permanently delete all notes"
|
|
58
|
+
onPress={handleClearAll}
|
|
59
|
+
/>
|
|
60
|
+
{confirmClear && (
|
|
61
|
+
<div className="px-4 pb-3">
|
|
62
|
+
<Button
|
|
63
|
+
variant="ghost"
|
|
64
|
+
size="sm"
|
|
65
|
+
className="w-full"
|
|
66
|
+
onClick={() => setConfirmClear(false)}
|
|
67
|
+
>
|
|
68
|
+
Cancel
|
|
69
|
+
</Button>
|
|
70
|
+
</div>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
</Card>
|
|
74
|
+
</SettingsGroup>
|
|
75
|
+
|
|
76
|
+
<SettingsGroup label="About">
|
|
77
|
+
<Card className="p-4 space-y-1.5">
|
|
78
|
+
<p className="text-[15px] tracking-[-0.15px] text-text">{{DISPLAY_NAME}}</p>
|
|
79
|
+
<p className="text-[13px] tracking-[-0.13px] text-text-dim">Version 1.0.0</p>
|
|
80
|
+
<Divider className="my-2" />
|
|
81
|
+
<p className="text-[11px] tracking-[-0.11px] text-text-dim">
|
|
82
|
+
A notes app for Even Realities G2 smart glasses. All data is stored locally in your browser.
|
|
83
|
+
</p>
|
|
84
|
+
</Card>
|
|
85
|
+
</SettingsGroup>
|
|
86
|
+
</main>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type NoteCategory = 'Personal' | 'Work' | 'Ideas'
|
|
2
|
+
|
|
3
|
+
export interface Note {
|
|
4
|
+
id: string
|
|
5
|
+
title: string
|
|
6
|
+
content: string
|
|
7
|
+
category: NoteCategory
|
|
8
|
+
createdAt: number
|
|
9
|
+
updatedAt: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const CATEGORIES: NoteCategory[] = ['Personal', 'Work', 'Ideas']
|
|
13
|
+
export const ALL_FILTER = 'All'
|
|
14
|
+
export type CategoryFilter = typeof ALL_FILTER | NoteCategory
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "notes",
|
|
3
|
+
"displayName": "Notes App",
|
|
4
|
+
"description": "A notes and productivity CRUD app with categories, search, and glass display",
|
|
5
|
+
"tags": ["productivity", "crud", "notes"],
|
|
6
|
+
"components": ["DrawerShell", "SearchBar", "CategoryFilter", "ListItem", "Badge", "EmptyState", "Card", "Input", "Textarea", "Select", "Button", "SettingsGroup", "Toggle", "ScreenHeader"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"baseUrl": ".",
|
|
16
|
+
"paths": { "@/*": ["./src/*"] }
|
|
17
|
+
},
|
|
18
|
+
"include": ["src"],
|
|
19
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
20
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import react from '@vitejs/plugin-react'
|
|
3
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
plugins: [react(), tailwindcss()],
|
|
8
|
+
resolve: {
|
|
9
|
+
alias: { '@': path.resolve(__dirname, './src') },
|
|
10
|
+
dedupe: ['react', 'react-dom', 'react-router', '@evenrealities/even_hub_sdk', '@jappyjan/even-better-sdk', 'upng-js'],
|
|
11
|
+
},
|
|
12
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# {{DISPLAY_NAME}}
|
|
2
|
+
|
|
3
|
+
A G2 smart glasses app built with [even-toolkit](https://www.npmjs.com/package/even-toolkit).
|
|
4
|
+
|
|
5
|
+
## Development
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
npm run dev
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Open [http://localhost:5173](http://localhost:5173) in your browser.
|
|
13
|
+
|
|
14
|
+
## Test with Simulator
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx @evenrealities/evenhub-simulator@latest http://localhost:5173
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Build for Even Hub
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm run build
|
|
24
|
+
npx @evenrealities/evenhub-cli pack app.json dist
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Upload the generated `.ehpk` file to the Even Hub.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
|
6
|
+
<title>{{DISPLAY_NAME}}</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{APP_NAME}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite --port 5173 --host",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@evenrealities/even_hub_sdk": "^0.0.9",
|
|
13
|
+
"@jappyjan/even-better-sdk": "^0.0.11",
|
|
14
|
+
"class-variance-authority": "^0.7.1",
|
|
15
|
+
"clsx": "^2.1.0",
|
|
16
|
+
"even-toolkit": "^1.5.0",
|
|
17
|
+
"react": "^19.0.0",
|
|
18
|
+
"react-dom": "^19.0.0",
|
|
19
|
+
"react-is": "^19.2.4",
|
|
20
|
+
"react-router": "^7.0.0",
|
|
21
|
+
"tailwind-merge": "^3.0.0",
|
|
22
|
+
"upng-js": "^2.1.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
26
|
+
"@types/react": "^19.0.0",
|
|
27
|
+
"@types/react-dom": "^19.0.0",
|
|
28
|
+
"@types/node": "^22.0.0",
|
|
29
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
30
|
+
"tailwindcss": "^4.0.0",
|
|
31
|
+
"typescript": "~5.4.0",
|
|
32
|
+
"vite": "^5.4.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Routes, Route } from 'react-router'
|
|
2
|
+
import { Shell } from './layouts/shell'
|
|
3
|
+
import { TrackerProvider } from './contexts/TrackerContext'
|
|
4
|
+
import { TodayScreen } from './screens/TodayScreen'
|
|
5
|
+
import { HistoryScreen } from './screens/HistoryScreen'
|
|
6
|
+
import { NewEntryScreen } from './screens/NewEntryScreen'
|
|
7
|
+
import { Settings } from './screens/Settings'
|
|
8
|
+
import { AppGlasses } from './glass/AppGlasses'
|
|
9
|
+
|
|
10
|
+
export function App() {
|
|
11
|
+
return (
|
|
12
|
+
<TrackerProvider>
|
|
13
|
+
<Routes>
|
|
14
|
+
<Route element={<Shell />}>
|
|
15
|
+
<Route path="/" element={<TodayScreen />} />
|
|
16
|
+
<Route path="/history" element={<HistoryScreen />} />
|
|
17
|
+
<Route path="/new" element={<NewEntryScreen />} />
|
|
18
|
+
<Route path="/settings" element={<Settings />} />
|
|
19
|
+
</Route>
|
|
20
|
+
</Routes>
|
|
21
|
+
<AppGlasses />
|
|
22
|
+
</TrackerProvider>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "even-toolkit/web/theme-light.css";
|
|
3
|
+
@import "even-toolkit/web/typography.css";
|
|
4
|
+
@import "even-toolkit/web/utilities.css";
|
|
5
|
+
@source "../src";
|
|
6
|
+
@source "../node_modules/even-toolkit/web";
|
|
7
|
+
@source "../node_modules/even-toolkit/dist";
|
|
8
|
+
|
|
9
|
+
@theme {
|
|
10
|
+
--color-bg: var(--color-bg);
|
|
11
|
+
--color-surface: var(--color-surface);
|
|
12
|
+
--color-surface-light: var(--color-surface-light);
|
|
13
|
+
--color-surface-lighter: var(--color-surface-lighter);
|
|
14
|
+
--color-border: var(--color-border);
|
|
15
|
+
--color-border-light: var(--color-border-light);
|
|
16
|
+
--color-text: var(--color-text);
|
|
17
|
+
--color-text-dim: var(--color-text-dim);
|
|
18
|
+
--color-text-muted: var(--color-text-muted);
|
|
19
|
+
--color-text-highlight: var(--color-text-highlight);
|
|
20
|
+
--color-accent: var(--color-accent);
|
|
21
|
+
--color-accent-alpha: var(--color-accent-alpha);
|
|
22
|
+
--color-accent-warning: var(--color-accent-warning);
|
|
23
|
+
--color-positive: var(--color-positive);
|
|
24
|
+
--color-positive-alpha: var(--color-positive-alpha);
|
|
25
|
+
--color-negative: var(--color-negative);
|
|
26
|
+
--color-negative-alpha: var(--color-negative-alpha);
|
|
27
|
+
--color-overlay: var(--color-overlay);
|
|
28
|
+
--color-input-bg: var(--color-input-bg);
|
|
29
|
+
--radius-default: var(--radius-default);
|
|
30
|
+
--font-display: var(--font-display);
|
|
31
|
+
--font-body: var(--font-body);
|
|
32
|
+
--font-mono: var(--font-mono);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
html, body {
|
|
36
|
+
background: var(--color-bg);
|
|
37
|
+
color: var(--color-text);
|
|
38
|
+
min-height: 100dvh;
|
|
39
|
+
font-family: var(--font-display);
|
|
40
|
+
-webkit-font-smoothing: antialiased;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#root {
|
|
44
|
+
max-width: 430px;
|
|
45
|
+
margin: 0 auto;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.scrollbar-hide {
|
|
49
|
+
-ms-overflow-style: none;
|
|
50
|
+
scrollbar-width: none;
|
|
51
|
+
}
|
|
52
|
+
.scrollbar-hide::-webkit-scrollbar {
|
|
53
|
+
display: none;
|
|
54
|
+
}
|