openuispec 0.2.10 → 0.2.11
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 +3 -1
- package/cli/index.ts +4 -2
- package/cli/init.ts +23 -8
- package/docs/cli.md +13 -8
- package/drift/index.ts +41 -15
- package/mcp-server/index.ts +155 -116
- package/mcp-server/screenshot.ts +19 -4
- package/package.json +5 -2
- package/prepare/index.ts +16 -0
- package/scripts/take-all-screenshots.ts +507 -0
- package/status/index.ts +2 -2
- package/examples/social-app/.mcp.json +0 -10
- package/examples/social-app/AGENTS.md +0 -124
- package/examples/social-app/CLAUDE.md +0 -124
- package/examples/social-app/backend/.gitkeep +0 -1
- package/examples/social-app/generated/android/social-app/app/.paparazzi-hashes.json +0 -3
- package/examples/social-app/generated/android/social-app/app/build.gradle.kts +0 -94
- package/examples/social-app/generated/android/social-app/app/src/main/AndroidManifest.xml +0 -26
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/AppContainer.kt +0 -20
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/MainActivity.kt +0 -35
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/SocialAppApplication.kt +0 -13
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/MockData.kt +0 -98
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/AppPreferences.kt +0 -19
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/DataStorePreferencesRepository.kt +0 -68
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/PreferencesRepository.kt +0 -15
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/model/Models.kt +0 -34
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/MainShell.kt +0 -390
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/components/Components.kt +0 -234
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/components/ContractPrimitives.kt +0 -641
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/navigation/RootComponent.kt +0 -113
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/ChatDetailScreen.kt +0 -212
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/CreatePostScreen.kt +0 -113
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/DiscoverScreen.kt +0 -137
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/EditProfileScreen.kt +0 -180
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/HomeFeedScreen.kt +0 -169
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/MessagesInboxScreen.kt +0 -85
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/NotificationsScreen.kt +0 -74
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/PostDetailScreen.kt +0 -293
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/ProfileSelfScreen.kt +0 -116
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SearchResultsScreen.kt +0 -161
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SettingsScreen.kt +0 -164
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SettingsStore.kt +0 -95
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/UserProfileScreen.kt +0 -123
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Color.kt +0 -33
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Shape.kt +0 -41
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Spacing.kt +0 -20
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Theme.kt +0 -82
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Type.kt +0 -60
- package/examples/social-app/generated/android/social-app/app/src/main/res/drawable/ic_launcher_foreground.xml +0 -9
- package/examples/social-app/generated/android/social-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +0 -5
- package/examples/social-app/generated/android/social-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +0 -5
- package/examples/social-app/generated/android/social-app/app/src/main/res/values/strings.xml +0 -91
- package/examples/social-app/generated/android/social-app/app/src/main/res/values/themes.xml +0 -10
- package/examples/social-app/generated/android/social-app/app/src/main/res/values-ru/strings.xml +0 -79
- package/examples/social-app/generated/android/social-app/app/src/main/res/values-uz/strings.xml +0 -79
- package/examples/social-app/generated/android/social-app/app/src/main/xml/AndroidManifest.xml +0 -23
- package/examples/social-app/generated/android/social-app/app/src/test/kotlin/com/social/app/screenshots/HomeFeedScreenshotTest.kt +0 -34
- package/examples/social-app/generated/android/social-app/build.gradle.kts +0 -7
- package/examples/social-app/generated/android/social-app/gradle/libs.versions.toml +0 -50
- package/examples/social-app/generated/android/social-app/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/examples/social-app/generated/android/social-app/gradle/wrapper/gradle-wrapper.properties +0 -8
- package/examples/social-app/generated/android/social-app/gradle.properties +0 -11
- package/examples/social-app/generated/android/social-app/gradlew +0 -248
- package/examples/social-app/generated/android/social-app/settings.gradle.kts +0 -27
- package/examples/social-app/generated/web/social-app/index.html +0 -12
- package/examples/social-app/generated/web/social-app/package-lock.json +0 -2517
- package/examples/social-app/generated/web/social-app/package.json +0 -27
- package/examples/social-app/generated/web/social-app/src/app/App.tsx +0 -58
- package/examples/social-app/generated/web/social-app/src/components/Shell.tsx +0 -259
- package/examples/social-app/generated/web/social-app/src/components/cards.tsx +0 -317
- package/examples/social-app/generated/web/social-app/src/components/ui.tsx +0 -340
- package/examples/social-app/generated/web/social-app/src/flows/CreatePostFlow.tsx +0 -86
- package/examples/social-app/generated/web/social-app/src/i18n.tsx +0 -59
- package/examples/social-app/generated/web/social-app/src/lib/icons.tsx +0 -85
- package/examples/social-app/generated/web/social-app/src/lib/tokens.ts +0 -70
- package/examples/social-app/generated/web/social-app/src/lib/utils.ts +0 -97
- package/examples/social-app/generated/web/social-app/src/locales/en.json +0 -67
- package/examples/social-app/generated/web/social-app/src/locales/ru.json +0 -67
- package/examples/social-app/generated/web/social-app/src/locales/uz.json +0 -67
- package/examples/social-app/generated/web/social-app/src/main.tsx +0 -16
- package/examples/social-app/generated/web/social-app/src/screens/ChatDetailScreen.tsx +0 -90
- package/examples/social-app/generated/web/social-app/src/screens/DiscoverScreen.tsx +0 -86
- package/examples/social-app/generated/web/social-app/src/screens/EditProfileScreen.tsx +0 -57
- package/examples/social-app/generated/web/social-app/src/screens/HomeFeedScreen.tsx +0 -103
- package/examples/social-app/generated/web/social-app/src/screens/MessagesInboxScreen.tsx +0 -52
- package/examples/social-app/generated/web/social-app/src/screens/NotificationsScreen.tsx +0 -41
- package/examples/social-app/generated/web/social-app/src/screens/PostDetailScreen.tsx +0 -115
- package/examples/social-app/generated/web/social-app/src/screens/ProfileSelfScreen.tsx +0 -57
- package/examples/social-app/generated/web/social-app/src/screens/ProfileUserScreen.tsx +0 -76
- package/examples/social-app/generated/web/social-app/src/screens/SearchResultsScreen.tsx +0 -96
- package/examples/social-app/generated/web/social-app/src/screens/SettingsScreen.tsx +0 -79
- package/examples/social-app/generated/web/social-app/src/state/store.ts +0 -592
- package/examples/social-app/generated/web/social-app/src/styles.css +0 -125
- package/examples/social-app/generated/web/social-app/src/vite-env.d.ts +0 -1
- package/examples/social-app/generated/web/social-app/tsconfig.json +0 -22
- package/examples/social-app/generated/web/social-app/tsconfig.node.json +0 -13
- package/examples/social-app/generated/web/social-app/tsconfig.node.tsbuildinfo +0 -1
- package/examples/social-app/generated/web/social-app/tsconfig.tsbuildinfo +0 -1
- package/examples/social-app/generated/web/social-app/vite.config.d.ts +0 -2
- package/examples/social-app/generated/web/social-app/vite.config.js +0 -6
- package/examples/social-app/generated/web/social-app/vite.config.ts +0 -7
- package/examples/social-app/package.json +0 -13
- package/examples/social-app/take-web-screenshots.ts +0 -97
- package/examples/taskflow/.codex/config.toml +0 -4
- package/examples/taskflow/.mcp.json +0 -10
- package/examples/taskflow/AGENTS.md +0 -124
- package/examples/taskflow/CLAUDE.md +0 -124
- package/examples/taskflow/backend/.gitkeep +0 -1
- package/examples/taskflow/generated/android/TaskFlow/README.md +0 -43
- package/examples/taskflow/generated/android/TaskFlow/app/build.gradle.kts +0 -76
- package/examples/taskflow/generated/android/TaskFlow/app/proguard-rules.pro +0 -1
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/AndroidManifest.xml +0 -21
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/MainActivity.kt +0 -19
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/TaskFlowApp.kt +0 -283
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/model/DomainModels.kt +0 -106
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/model/SampleData.kt +0 -57
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/components/Common.kt +0 -109
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/HomeScreen.kt +0 -112
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/ProjectsScreen.kt +0 -61
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/SettingsScreen.kt +0 -82
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/TaskDetailScreen.kt +0 -111
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/sheets/Sheets.kt +0 -77
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Color.kt +0 -30
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Theme.kt +0 -86
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Type.kt +0 -57
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/res/values/strings.xml +0 -155
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/res/values/themes.xml +0 -4
- package/examples/taskflow/generated/android/TaskFlow/build.gradle.kts +0 -5
- package/examples/taskflow/generated/android/TaskFlow/gradle/gradle-daemon-jvm.properties +0 -12
- package/examples/taskflow/generated/android/TaskFlow/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/examples/taskflow/generated/android/TaskFlow/gradle/wrapper/gradle-wrapper.properties +0 -7
- package/examples/taskflow/generated/android/TaskFlow/gradle.properties +0 -4
- package/examples/taskflow/generated/android/TaskFlow/gradlew +0 -18
- package/examples/taskflow/generated/android/TaskFlow/gradlew.bat +0 -12
- package/examples/taskflow/generated/android/TaskFlow/settings.gradle.kts +0 -18
- package/examples/taskflow/generated/ios/TaskFlow/README.md +0 -21
- package/examples/taskflow/generated/ios/TaskFlow/Resources/en.lproj/Localizable.strings +0 -115
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/App/TaskFlowApp.swift +0 -24
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Components/AppChrome.swift +0 -150
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Flows/TaskEditorSheet.swift +0 -220
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Models/DomainModels.swift +0 -122
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/CalendarView.swift +0 -21
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/HomeView.swift +0 -201
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProfileEditView.swift +0 -48
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProjectDetailView.swift +0 -59
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProjectsView.swift +0 -63
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/SettingsView.swift +0 -85
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/TaskDetailView.swift +0 -219
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Support/AppModel.swift +0 -320
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Support/AppSupport.swift +0 -41
- package/examples/taskflow/generated/ios/TaskFlow/project.yml +0 -31
- package/examples/taskflow/generated/web/TaskFlow/README.md +0 -19
- package/examples/taskflow/generated/web/TaskFlow/index.html +0 -12
- package/examples/taskflow/generated/web/TaskFlow/package-lock.json +0 -1908
- package/examples/taskflow/generated/web/TaskFlow/package.json +0 -24
- package/examples/taskflow/generated/web/TaskFlow/src/App.tsx +0 -58
- package/examples/taskflow/generated/web/TaskFlow/src/AppShell.tsx +0 -55
- package/examples/taskflow/generated/web/TaskFlow/src/components/Common.tsx +0 -82
- package/examples/taskflow/generated/web/TaskFlow/src/components/Modals.tsx +0 -191
- package/examples/taskflow/generated/web/TaskFlow/src/components/Nav.tsx +0 -41
- package/examples/taskflow/generated/web/TaskFlow/src/generated/messages.ts +0 -131
- package/examples/taskflow/generated/web/TaskFlow/src/hooks.ts +0 -25
- package/examples/taskflow/generated/web/TaskFlow/src/i18n.ts +0 -39
- package/examples/taskflow/generated/web/TaskFlow/src/locales.en.json +0 -111
- package/examples/taskflow/generated/web/TaskFlow/src/main.tsx +0 -13
- package/examples/taskflow/generated/web/TaskFlow/src/screens/HomeScreen.tsx +0 -111
- package/examples/taskflow/generated/web/TaskFlow/src/screens/ProjectsScreen.tsx +0 -82
- package/examples/taskflow/generated/web/TaskFlow/src/screens/SettingsScreens.tsx +0 -132
- package/examples/taskflow/generated/web/TaskFlow/src/screens/TaskDetail.tsx +0 -105
- package/examples/taskflow/generated/web/TaskFlow/src/store.ts +0 -216
- package/examples/taskflow/generated/web/TaskFlow/src/styles.css +0 -617
- package/examples/taskflow/generated/web/TaskFlow/src/types.ts +0 -64
- package/examples/taskflow/generated/web/TaskFlow/src/utils.ts +0 -78
- package/examples/taskflow/generated/web/TaskFlow/tsconfig.json +0 -21
- package/examples/taskflow/generated/web/TaskFlow/vite.config.ts +0 -6
- package/examples/todo-orbit/.codex/config.toml +0 -4
- package/examples/todo-orbit/.mcp.json +0 -10
- package/examples/todo-orbit/AGENTS.md +0 -124
- package/examples/todo-orbit/CLAUDE.md +0 -124
- package/examples/todo-orbit/backend/.gitkeep +0 -1
- package/examples/todo-orbit/generated/android/Todo Orbit/README.md +0 -14
- package/examples/todo-orbit/generated/android/Todo Orbit/app/build.gradle.kts +0 -58
- package/examples/todo-orbit/generated/android/Todo Orbit/app/proguard-rules.pro +0 -1
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/AndroidManifest.xml +0 -20
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/MainActivity.kt +0 -14
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/TodoOrbitApp.kt +0 -345
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/AppLogic.kt +0 -231
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/Models.kt +0 -169
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/Strings.kt +0 -8
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/components/CommonComponents.kt +0 -236
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/AnalyticsScreen.kt +0 -193
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/SettingsScreen.kt +0 -102
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/TasksScreen.kt +0 -347
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/sheets/EditorSheets.kt +0 -347
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/theme/TodoOrbitTheme.kt +0 -59
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values/strings.xml +0 -149
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values-ru/strings.xml +0 -155
- package/examples/todo-orbit/generated/android/Todo Orbit/build.gradle.kts +0 -4
- package/examples/todo-orbit/generated/android/Todo Orbit/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/gradle/wrapper/gradle-wrapper.properties +0 -7
- package/examples/todo-orbit/generated/android/Todo Orbit/gradle.properties +0 -4
- package/examples/todo-orbit/generated/android/Todo Orbit/gradlew +0 -248
- package/examples/todo-orbit/generated/android/Todo Orbit/gradlew.bat +0 -93
- package/examples/todo-orbit/generated/android/Todo Orbit/settings.gradle.kts +0 -18
- package/examples/todo-orbit/generated/ios/Todo Orbit/.screenshot-uitest/Sources/ScreenshotUITest.swift +0 -36
- package/examples/todo-orbit/generated/ios/Todo Orbit/README.md +0 -29
- package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/en.lproj/Localizable.strings +0 -119
- package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/ru.lproj/Localizable.strings +0 -119
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/App/TodoOrbitApp.swift +0 -50
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/OrbitChrome.swift +0 -204
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/SchedulePreviewView.swift +0 -126
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/TrendChartView.swift +0 -70
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/RecurringRuleSheet.swift +0 -126
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/TaskEditorSheet.swift +0 -61
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Models/DomainModels.swift +0 -238
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/AnalyticsView.swift +0 -94
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/SettingsView.swift +0 -76
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/TasksHomeView.swift +0 -364
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Support/AppModel.swift +0 -324
- package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/project.pbxproj +0 -439
- package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/xcshareddata/xcschemes/TodoOrbit.xcscheme +0 -89
- package/examples/todo-orbit/generated/ios/Todo Orbit/project.yml +0 -32
- package/examples/todo-orbit/generated/web/Todo Orbit/index.html +0 -16
- package/examples/todo-orbit/generated/web/Todo Orbit/package-lock.json +0 -1087
- package/examples/todo-orbit/generated/web/Todo Orbit/package.json +0 -24
- package/examples/todo-orbit/generated/web/Todo Orbit/src/App.tsx +0 -2167
- package/examples/todo-orbit/generated/web/Todo Orbit/src/main.tsx +0 -13
- package/examples/todo-orbit/generated/web/Todo Orbit/src/styles.css +0 -926
- package/examples/todo-orbit/generated/web/Todo Orbit/tsconfig.json +0 -19
- package/examples/todo-orbit/generated/web/Todo Orbit/vite.config.ts +0 -6
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "taskflow-web",
|
|
3
|
-
"private": true,
|
|
4
|
-
"version": "0.1.0",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"dev": "vite",
|
|
8
|
-
"build": "tsc --noEmit && vite build",
|
|
9
|
-
"preview": "vite preview"
|
|
10
|
-
},
|
|
11
|
-
"dependencies": {
|
|
12
|
-
"react": "^19.2.0",
|
|
13
|
-
"react-dom": "^19.2.0",
|
|
14
|
-
"react-router-dom": "^7.3.0",
|
|
15
|
-
"zustand": "^5.0.3"
|
|
16
|
-
},
|
|
17
|
-
"devDependencies": {
|
|
18
|
-
"@types/react": "^19.2.2",
|
|
19
|
-
"@types/react-dom": "^19.2.2",
|
|
20
|
-
"@vitejs/plugin-react": "^5.0.4",
|
|
21
|
-
"typescript": "^5.8.3",
|
|
22
|
-
"vite": "^6.3.5"
|
|
23
|
-
}
|
|
24
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import { AppShell } from "./AppShell";
|
|
3
|
-
import { AssignModal, ProjectModal, TaskModal } from "./components/Modals";
|
|
4
|
-
import { useResolvedTheme, useWindowWidth } from "./hooks";
|
|
5
|
-
import { useAppStore } from "./store";
|
|
6
|
-
|
|
7
|
-
export default function App() {
|
|
8
|
-
const [taskModal, setTaskModal] = useState<{ mode: "create" } | { mode: "edit"; taskId: string } | null>(null);
|
|
9
|
-
const [projectModalOpen, setProjectModalOpen] = useState(false);
|
|
10
|
-
const [assignModalTaskId, setAssignModalTaskId] = useState<string | null>(null);
|
|
11
|
-
const [sidebarVisible, setSidebarVisible] = useState(true);
|
|
12
|
-
|
|
13
|
-
const width = useWindowWidth();
|
|
14
|
-
const sizeClass = width <= 600 ? "compact" : width <= 1024 ? "regular" : "expanded";
|
|
15
|
-
const theme = useResolvedTheme(useAppStore((state) => state.preferences.theme));
|
|
16
|
-
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
document.documentElement.dataset.theme = theme;
|
|
19
|
-
}, [theme]);
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
const onKeyDown = (event: KeyboardEvent) => {
|
|
23
|
-
if (!(event.metaKey || event.ctrlKey)) return;
|
|
24
|
-
const key = event.key.toLowerCase();
|
|
25
|
-
if (key === "n") {
|
|
26
|
-
event.preventDefault();
|
|
27
|
-
setTaskModal({ mode: "create" });
|
|
28
|
-
}
|
|
29
|
-
if (key === "k") {
|
|
30
|
-
event.preventDefault();
|
|
31
|
-
document.getElementById("task-search")?.focus();
|
|
32
|
-
}
|
|
33
|
-
if (key === "b") {
|
|
34
|
-
event.preventDefault();
|
|
35
|
-
setSidebarVisible((value) => !value);
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
window.addEventListener("keydown", onKeyDown);
|
|
39
|
-
return () => window.removeEventListener("keydown", onKeyDown);
|
|
40
|
-
}, []);
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<div className="app-frame">
|
|
44
|
-
<AppShell
|
|
45
|
-
sizeClass={sizeClass}
|
|
46
|
-
sidebarVisible={sidebarVisible}
|
|
47
|
-
onCreateTask={() => setTaskModal({ mode: "create" })}
|
|
48
|
-
onCreateProject={() => setProjectModalOpen(true)}
|
|
49
|
-
onEditTask={(taskId) => setTaskModal({ mode: "edit", taskId })}
|
|
50
|
-
onAssignTask={(taskId) => setAssignModalTaskId(taskId)}
|
|
51
|
-
/>
|
|
52
|
-
|
|
53
|
-
{taskModal ? <TaskModal modal={taskModal} onClose={() => setTaskModal(null)} /> : null}
|
|
54
|
-
{projectModalOpen ? <ProjectModal onClose={() => setProjectModalOpen(false)} /> : null}
|
|
55
|
-
{assignModalTaskId ? <AssignModal taskId={assignModalTaskId} onClose={() => setAssignModalTaskId(null)} /> : null}
|
|
56
|
-
</div>
|
|
57
|
-
);
|
|
58
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
|
|
2
|
-
import { useAppStore } from "./store";
|
|
3
|
-
import { BottomNav, SidebarNav } from "./components/Nav";
|
|
4
|
-
import { HomeScreen } from "./screens/HomeScreen";
|
|
5
|
-
import { TaskDetailRoute } from "./screens/TaskDetail";
|
|
6
|
-
import { ProjectsScreen, ProjectDetailRoute } from "./screens/ProjectsScreen";
|
|
7
|
-
import { CalendarScreen, ProfileScreen, SettingsScreen } from "./screens/SettingsScreens";
|
|
8
|
-
import type { SizeClass } from "./types";
|
|
9
|
-
|
|
10
|
-
export function AppShell(props: {
|
|
11
|
-
sizeClass: SizeClass;
|
|
12
|
-
sidebarVisible: boolean;
|
|
13
|
-
onCreateTask: () => void;
|
|
14
|
-
onCreateProject: () => void;
|
|
15
|
-
onEditTask: (taskId: string) => void;
|
|
16
|
-
onAssignTask: (taskId: string) => void;
|
|
17
|
-
}) {
|
|
18
|
-
const location = useLocation();
|
|
19
|
-
const user = useAppStore((state) => state.user);
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<div className={`shell-layout size-${props.sizeClass}`}>
|
|
23
|
-
{props.sizeClass !== "compact" && (props.sizeClass === "expanded" || props.sidebarVisible) ? (
|
|
24
|
-
<SidebarNav currentPath={location.pathname} />
|
|
25
|
-
) : null}
|
|
26
|
-
<main className="screen-shell">
|
|
27
|
-
<Routes>
|
|
28
|
-
<Route path="/" element={<Navigate to="/tasks" replace />} />
|
|
29
|
-
<Route
|
|
30
|
-
path="/tasks"
|
|
31
|
-
element={
|
|
32
|
-
<HomeScreen
|
|
33
|
-
sizeClass={props.sizeClass}
|
|
34
|
-
onCreateTask={props.onCreateTask}
|
|
35
|
-
onEditTask={props.onEditTask}
|
|
36
|
-
onAssignTask={props.onAssignTask}
|
|
37
|
-
/>
|
|
38
|
-
}
|
|
39
|
-
/>
|
|
40
|
-
<Route
|
|
41
|
-
path="/tasks/:taskId"
|
|
42
|
-
element={<TaskDetailRoute sizeClass={props.sizeClass} onEditTask={props.onEditTask} onAssignTask={props.onAssignTask} />}
|
|
43
|
-
/>
|
|
44
|
-
<Route path="/projects" element={<ProjectsScreen onCreateProject={props.onCreateProject} />} />
|
|
45
|
-
<Route path="/projects/:projectId" element={<ProjectDetailRoute />} />
|
|
46
|
-
<Route path="/calendar" element={<CalendarScreen />} />
|
|
47
|
-
<Route path="/settings" element={<SettingsScreen />} />
|
|
48
|
-
<Route path="/profile" element={<ProfileScreen />} />
|
|
49
|
-
</Routes>
|
|
50
|
-
</main>
|
|
51
|
-
{props.sizeClass === "compact" ? <BottomNav currentPath={location.pathname} /> : null}
|
|
52
|
-
<div className="floating-user-chip">{user.firstName}</div>
|
|
53
|
-
</div>
|
|
54
|
-
);
|
|
55
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
|
-
import { useAppStore } from "../store";
|
|
3
|
-
import type { Project, Task } from "../types";
|
|
4
|
-
import { initials, relativeDate } from "../utils";
|
|
5
|
-
|
|
6
|
-
export function SwitchRow(props: {
|
|
7
|
-
label: string;
|
|
8
|
-
helper?: string;
|
|
9
|
-
value: boolean;
|
|
10
|
-
onChange: (value: boolean) => void;
|
|
11
|
-
}) {
|
|
12
|
-
return (
|
|
13
|
-
<label className="toggle-row">
|
|
14
|
-
<div>
|
|
15
|
-
<strong>{props.label}</strong>
|
|
16
|
-
{props.helper ? <small>{props.helper}</small> : null}
|
|
17
|
-
</div>
|
|
18
|
-
<input type="checkbox" checked={props.value} onChange={(event) => props.onChange(event.target.checked)} />
|
|
19
|
-
</label>
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function Field(props: { label: string; children: ReactNode }) {
|
|
24
|
-
return (
|
|
25
|
-
<label className="field-block">
|
|
26
|
-
<span className="field-label">{props.label}</span>
|
|
27
|
-
{props.children}
|
|
28
|
-
</label>
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function DetailRow(props: { label: string; value: string; action?: () => void }) {
|
|
33
|
-
const content = (
|
|
34
|
-
<>
|
|
35
|
-
<strong>{props.label}</strong>
|
|
36
|
-
<span>{props.value}</span>
|
|
37
|
-
</>
|
|
38
|
-
);
|
|
39
|
-
return props.action ? (
|
|
40
|
-
<button className="detail-row" onClick={props.action}>
|
|
41
|
-
{content}
|
|
42
|
-
</button>
|
|
43
|
-
) : (
|
|
44
|
-
<div className="detail-row">{content}</div>
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function StatCard(props: { label: string; value: string }) {
|
|
49
|
-
return (
|
|
50
|
-
<div className="stat-card">
|
|
51
|
-
<span>{props.label}</span>
|
|
52
|
-
<strong>{props.value}</strong>
|
|
53
|
-
</div>
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function TaskRow(props: {
|
|
58
|
-
task: Task;
|
|
59
|
-
project?: Project;
|
|
60
|
-
selected: boolean;
|
|
61
|
-
onSelect: () => void;
|
|
62
|
-
}) {
|
|
63
|
-
const toggleTask = useAppStore((state) => state.toggleTask);
|
|
64
|
-
return (
|
|
65
|
-
<button className={props.selected ? "task-row selected" : "task-row"} onClick={props.onSelect}>
|
|
66
|
-
<label className="check-shell" onClick={(event) => event.stopPropagation()}>
|
|
67
|
-
<input type="checkbox" checked={props.task.status === "done"} onChange={() => toggleTask(props.task.id)} />
|
|
68
|
-
</label>
|
|
69
|
-
<div className="task-copy">
|
|
70
|
-
<strong>{props.task.title}</strong>
|
|
71
|
-
<p>
|
|
72
|
-
{props.project?.name ?? "Inbox"} · {props.task.dueDate ? relativeDate(props.task.dueDate) : "No due date"}
|
|
73
|
-
</p>
|
|
74
|
-
</div>
|
|
75
|
-
<span className={`badge badge-${props.task.priority}`}>{props.task.priority.toUpperCase()}</span>
|
|
76
|
-
</button>
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function Avatar({ name, large = false }: { name: string; large?: boolean }) {
|
|
81
|
-
return <div className={large ? "avatar large" : "avatar"}>{initials(name)}</div>;
|
|
82
|
-
}
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { t } from "../i18n";
|
|
3
|
-
import { useAppStore } from "../store";
|
|
4
|
-
import type { Priority, ProjectDraft, TaskDraft } from "../types";
|
|
5
|
-
import { Field, Avatar } from "./Common";
|
|
6
|
-
import { priorityLabel } from "../i18n";
|
|
7
|
-
|
|
8
|
-
export function TaskModal(props: {
|
|
9
|
-
modal: { mode: "create" } | { mode: "edit"; taskId: string };
|
|
10
|
-
onClose: () => void;
|
|
11
|
-
}) {
|
|
12
|
-
const preferences = useAppStore((state) => state.preferences);
|
|
13
|
-
const projects = useAppStore((state) => state.projects);
|
|
14
|
-
const tasks = useAppStore((state) => state.tasks);
|
|
15
|
-
const createTask = useAppStore((state) => state.createTask);
|
|
16
|
-
const updateTask = useAppStore((state) => state.updateTask);
|
|
17
|
-
const editTaskId = props.modal.mode === "edit" ? props.modal.taskId : undefined;
|
|
18
|
-
const editingTask = editTaskId ? tasks.find((task) => task.id === editTaskId) : undefined;
|
|
19
|
-
|
|
20
|
-
const [draft, setDraft] = useState<TaskDraft>(
|
|
21
|
-
editingTask
|
|
22
|
-
? {
|
|
23
|
-
title: editingTask.title,
|
|
24
|
-
description: editingTask.description ?? "",
|
|
25
|
-
projectId: editingTask.projectId ?? "",
|
|
26
|
-
priority: editingTask.priority,
|
|
27
|
-
dueDate: editingTask.dueDate ?? "",
|
|
28
|
-
tags: editingTask.tags.join(", "),
|
|
29
|
-
assignToSelf: Boolean(editingTask.assignee)
|
|
30
|
-
}
|
|
31
|
-
: {
|
|
32
|
-
title: "",
|
|
33
|
-
description: "",
|
|
34
|
-
projectId: "",
|
|
35
|
-
priority: preferences.defaultPriority,
|
|
36
|
-
dueDate: "",
|
|
37
|
-
tags: "",
|
|
38
|
-
assignToSelf: true
|
|
39
|
-
}
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<dialog open className="modal">
|
|
44
|
-
<div className="modal-card">
|
|
45
|
-
<div className="modal-header">
|
|
46
|
-
<h3>{props.modal.mode === "edit" ? t("edit_task.title") : t("create_task.title")}</h3>
|
|
47
|
-
<button className="ghost-button" onClick={props.onClose}>{t("common.cancel")}</button>
|
|
48
|
-
</div>
|
|
49
|
-
<div className="modal-content">
|
|
50
|
-
<Field label={t("create_task.field_title")}>
|
|
51
|
-
<input value={draft.title} placeholder={t("create_task.field_title_placeholder")} onChange={(event) => setDraft({ ...draft, title: event.target.value })} />
|
|
52
|
-
</Field>
|
|
53
|
-
<Field label={t("create_task.field_description")}>
|
|
54
|
-
<textarea rows={4} value={draft.description} placeholder={t("create_task.field_description_placeholder")} onChange={(event) => setDraft({ ...draft, description: event.target.value })} />
|
|
55
|
-
</Field>
|
|
56
|
-
<div className="two-up">
|
|
57
|
-
<Field label={t("create_task.field_project")}>
|
|
58
|
-
<select value={draft.projectId} onChange={(event) => setDraft({ ...draft, projectId: event.target.value })}>
|
|
59
|
-
<option value="">{t("create_task.field_project_placeholder")}</option>
|
|
60
|
-
{projects.map((project) => (
|
|
61
|
-
<option key={project.id} value={project.id}>{project.name}</option>
|
|
62
|
-
))}
|
|
63
|
-
</select>
|
|
64
|
-
</Field>
|
|
65
|
-
<Field label={t("create_task.field_priority")}>
|
|
66
|
-
<select value={draft.priority} onChange={(event) => setDraft({ ...draft, priority: event.target.value as Priority })}>
|
|
67
|
-
<option value="low">{priorityLabel("low")}</option>
|
|
68
|
-
<option value="medium">{priorityLabel("medium")}</option>
|
|
69
|
-
<option value="high">{priorityLabel("high")}</option>
|
|
70
|
-
<option value="urgent">{priorityLabel("urgent")}</option>
|
|
71
|
-
</select>
|
|
72
|
-
</Field>
|
|
73
|
-
</div>
|
|
74
|
-
<div className="two-up">
|
|
75
|
-
<Field label={t("create_task.field_due_date")}>
|
|
76
|
-
<input type="date" value={draft.dueDate} onChange={(event) => setDraft({ ...draft, dueDate: event.target.value })} />
|
|
77
|
-
</Field>
|
|
78
|
-
<Field label={t("create_task.field_tags")}>
|
|
79
|
-
<input value={draft.tags} placeholder={t("create_task.field_tags_placeholder")} onChange={(event) => setDraft({ ...draft, tags: event.target.value })} />
|
|
80
|
-
</Field>
|
|
81
|
-
</div>
|
|
82
|
-
<label className="toggle-row">
|
|
83
|
-
<input type="checkbox" checked={draft.assignToSelf} onChange={(event) => setDraft({ ...draft, assignToSelf: event.target.checked })} />
|
|
84
|
-
<span>{t("create_task.field_assign_to_me")}</span>
|
|
85
|
-
</label>
|
|
86
|
-
</div>
|
|
87
|
-
<div className="action-row">
|
|
88
|
-
<button className="secondary-button" onClick={props.onClose}>{t("common.cancel")}</button>
|
|
89
|
-
<button
|
|
90
|
-
className="primary-button"
|
|
91
|
-
disabled={draft.title.trim().length < 3}
|
|
92
|
-
onClick={() => {
|
|
93
|
-
if (props.modal.mode === "edit" && editingTask) updateTask(editingTask.id, draft);
|
|
94
|
-
else createTask(draft);
|
|
95
|
-
props.onClose();
|
|
96
|
-
}}
|
|
97
|
-
>
|
|
98
|
-
{props.modal.mode === "edit" ? t("edit_task.save") : t("create_task.save")}
|
|
99
|
-
</button>
|
|
100
|
-
</div>
|
|
101
|
-
</div>
|
|
102
|
-
</dialog>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function ProjectModal(props: { onClose: () => void }) {
|
|
107
|
-
const createProject = useAppStore((state) => state.createProject);
|
|
108
|
-
const [draft, setDraft] = useState<ProjectDraft>({ name: "", color: "#5B4FE8", icon: "folder" });
|
|
109
|
-
|
|
110
|
-
return (
|
|
111
|
-
<dialog open className="modal">
|
|
112
|
-
<div className="modal-card compact">
|
|
113
|
-
<div className="modal-header">
|
|
114
|
-
<h3>{t("projects.dialog_title")}</h3>
|
|
115
|
-
<button className="ghost-button" onClick={props.onClose}>{t("common.cancel")}</button>
|
|
116
|
-
</div>
|
|
117
|
-
<div className="modal-content">
|
|
118
|
-
<Field label={t("projects.field_name")}>
|
|
119
|
-
<input value={draft.name} placeholder={t("projects.field_name_placeholder")} onChange={(event) => setDraft({ ...draft, name: event.target.value })} />
|
|
120
|
-
</Field>
|
|
121
|
-
<div className="two-up">
|
|
122
|
-
<Field label={t("projects.field_color")}>
|
|
123
|
-
<input value={draft.color} onChange={(event) => setDraft({ ...draft, color: event.target.value })} />
|
|
124
|
-
</Field>
|
|
125
|
-
<Field label={t("projects.field_icon")}>
|
|
126
|
-
<select value={draft.icon} onChange={(event) => setDraft({ ...draft, icon: event.target.value })}>
|
|
127
|
-
{["folder", "briefcase", "rocket", "star", "heart", "lightbulb"].map((icon) => (
|
|
128
|
-
<option key={icon} value={icon}>{icon}</option>
|
|
129
|
-
))}
|
|
130
|
-
</select>
|
|
131
|
-
</Field>
|
|
132
|
-
</div>
|
|
133
|
-
</div>
|
|
134
|
-
<div className="action-row">
|
|
135
|
-
<button className="secondary-button" onClick={props.onClose}>{t("common.cancel")}</button>
|
|
136
|
-
<button
|
|
137
|
-
className="primary-button"
|
|
138
|
-
onClick={() => {
|
|
139
|
-
if (!draft.name.trim()) return;
|
|
140
|
-
createProject(draft);
|
|
141
|
-
props.onClose();
|
|
142
|
-
}}
|
|
143
|
-
>
|
|
144
|
-
{t("common.create")}
|
|
145
|
-
</button>
|
|
146
|
-
</div>
|
|
147
|
-
</div>
|
|
148
|
-
</dialog>
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export function AssignModal(props: { taskId: string; onClose: () => void }) {
|
|
153
|
-
const users = useAppStore((state) => state.users);
|
|
154
|
-
const assignTask = useAppStore((state) => state.assignTask);
|
|
155
|
-
const [query, setQuery] = useState("");
|
|
156
|
-
const visibleUsers = users.filter((user) => `${user.name} ${user.email}`.toLowerCase().includes(query.toLowerCase()));
|
|
157
|
-
|
|
158
|
-
return (
|
|
159
|
-
<dialog open className="modal">
|
|
160
|
-
<div className="modal-card compact">
|
|
161
|
-
<div className="modal-header">
|
|
162
|
-
<h3>{t("task_detail.assign_to")}</h3>
|
|
163
|
-
<button className="ghost-button" onClick={props.onClose}>{t("common.cancel")}</button>
|
|
164
|
-
</div>
|
|
165
|
-
<div className="modal-content">
|
|
166
|
-
<Field label={t("task_detail.search_people")}>
|
|
167
|
-
<input value={query} onChange={(event) => setQuery(event.target.value)} placeholder={t("task_detail.search_people_placeholder")} />
|
|
168
|
-
</Field>
|
|
169
|
-
</div>
|
|
170
|
-
<div className="card-stack">
|
|
171
|
-
{visibleUsers.map((user) => (
|
|
172
|
-
<button
|
|
173
|
-
key={user.id}
|
|
174
|
-
className="profile-card"
|
|
175
|
-
onClick={() => {
|
|
176
|
-
assignTask(props.taskId, user.id);
|
|
177
|
-
props.onClose();
|
|
178
|
-
}}
|
|
179
|
-
>
|
|
180
|
-
<Avatar name={user.name} />
|
|
181
|
-
<div>
|
|
182
|
-
<strong>{user.name}</strong>
|
|
183
|
-
<p>{user.email}</p>
|
|
184
|
-
</div>
|
|
185
|
-
</button>
|
|
186
|
-
))}
|
|
187
|
-
</div>
|
|
188
|
-
</div>
|
|
189
|
-
</dialog>
|
|
190
|
-
);
|
|
191
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { NavLink } from "react-router-dom";
|
|
2
|
-
import { t } from "../i18n";
|
|
3
|
-
|
|
4
|
-
export function SidebarNav(props: { currentPath: string }) {
|
|
5
|
-
return (
|
|
6
|
-
<aside className="sidebar-shell">
|
|
7
|
-
<div className="brand-lockup">
|
|
8
|
-
<div className="brand-mark">TF</div>
|
|
9
|
-
<div>
|
|
10
|
-
<p className="eyebrow">OpenUISpec</p>
|
|
11
|
-
<h2>TaskFlow</h2>
|
|
12
|
-
</div>
|
|
13
|
-
</div>
|
|
14
|
-
<nav className="nav-list">
|
|
15
|
-
<NavItem to="/tasks" active={props.currentPath.startsWith("/tasks")}>{t("nav.tasks")}</NavItem>
|
|
16
|
-
<NavItem to="/projects" active={props.currentPath.startsWith("/projects")}>{t("nav.projects")}</NavItem>
|
|
17
|
-
<NavItem to="/calendar" active={props.currentPath.startsWith("/calendar")}>{t("nav.calendar")}</NavItem>
|
|
18
|
-
<NavItem to="/settings" active={props.currentPath.startsWith("/settings") || props.currentPath.startsWith("/profile")}>{t("nav.settings")}</NavItem>
|
|
19
|
-
</nav>
|
|
20
|
-
</aside>
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function BottomNav(props: { currentPath: string }) {
|
|
25
|
-
return (
|
|
26
|
-
<nav className="bottom-nav">
|
|
27
|
-
<NavItem to="/tasks" active={props.currentPath.startsWith("/tasks")}>{t("nav.tasks")}</NavItem>
|
|
28
|
-
<NavItem to="/projects" active={props.currentPath.startsWith("/projects")}>{t("nav.projects")}</NavItem>
|
|
29
|
-
<NavItem to="/calendar" active={props.currentPath.startsWith("/calendar")}>{t("nav.calendar")}</NavItem>
|
|
30
|
-
<NavItem to="/settings" active={props.currentPath.startsWith("/settings") || props.currentPath.startsWith("/profile")}>{t("nav.settings")}</NavItem>
|
|
31
|
-
</nav>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function NavItem(props: { to: string; active: boolean; children: string }) {
|
|
36
|
-
return (
|
|
37
|
-
<NavLink className={props.active ? "nav-item active" : "nav-item"} to={props.to}>
|
|
38
|
-
{props.children}
|
|
39
|
-
</NavLink>
|
|
40
|
-
);
|
|
41
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
export const messages = {
|
|
2
|
-
"nav.tasks": "Tasks",
|
|
3
|
-
"nav.projects": "Projects",
|
|
4
|
-
"nav.calendar": "Calendar",
|
|
5
|
-
"nav.settings": "Settings",
|
|
6
|
-
"home.greeting.morning": "Good morning, {name}",
|
|
7
|
-
"home.greeting.afternoon": "Good afternoon, {name}",
|
|
8
|
-
"home.greeting.evening": "Good evening, {name}",
|
|
9
|
-
"home.search_label": "Search tasks",
|
|
10
|
-
"home.search_placeholder": "Search by title, tag, or project...",
|
|
11
|
-
"home.filter.all": "All",
|
|
12
|
-
"home.filter.today": "Today",
|
|
13
|
-
"home.filter.upcoming": "Upcoming",
|
|
14
|
-
"home.filter.done": "Done",
|
|
15
|
-
"home.empty_title": "All caught up!",
|
|
16
|
-
"home.empty_body": "No tasks match this filter. Tap + to add one.",
|
|
17
|
-
"home.new_task": "New task",
|
|
18
|
-
"home.mark_complete": "Mark {title} complete",
|
|
19
|
-
"home.task_count.none": "No tasks today",
|
|
20
|
-
"home.task_count.one": "{count} task today",
|
|
21
|
-
"home.task_count.other": "{count} tasks today",
|
|
22
|
-
"task_detail.status": "Status",
|
|
23
|
-
"task_detail.priority": "Priority",
|
|
24
|
-
"task_detail.due": "Due",
|
|
25
|
-
"task_detail.description": "Description",
|
|
26
|
-
"task_detail.details": "Details",
|
|
27
|
-
"task_detail.project": "Project",
|
|
28
|
-
"task_detail.assignee": "Assignee",
|
|
29
|
-
"task_detail.unassigned": "Unassigned",
|
|
30
|
-
"task_detail.tags": "Tags",
|
|
31
|
-
"task_detail.created": "Created",
|
|
32
|
-
"task_detail.edit": "Edit task",
|
|
33
|
-
"task_detail.reopen": "Reopen task",
|
|
34
|
-
"task_detail.complete": "Mark complete",
|
|
35
|
-
"task_detail.delete": "Delete task",
|
|
36
|
-
"task_detail.delete_title": "Delete task?",
|
|
37
|
-
"task_detail.delete_message": "This action cannot be undone. The task \"{title}\" will be permanently removed.",
|
|
38
|
-
"task_detail.task_updated": "Task updated",
|
|
39
|
-
"task_detail.update_error": "Couldn't update status",
|
|
40
|
-
"task_detail.task_deleted": "Task deleted",
|
|
41
|
-
"task_detail.assign_to": "Assign to",
|
|
42
|
-
"task_detail.search_people": "Search people",
|
|
43
|
-
"task_detail.search_people_placeholder": "Name or email...",
|
|
44
|
-
"create_task.title": "New task",
|
|
45
|
-
"create_task.save": "Save",
|
|
46
|
-
"create_task.saving": "Saving...",
|
|
47
|
-
"create_task.field_title": "Title",
|
|
48
|
-
"create_task.field_title_placeholder": "What needs to be done?",
|
|
49
|
-
"create_task.field_description": "Description",
|
|
50
|
-
"create_task.field_description_placeholder": "Add details, notes, or context...",
|
|
51
|
-
"create_task.field_project": "Project",
|
|
52
|
-
"create_task.field_project_placeholder": "Select a project",
|
|
53
|
-
"create_task.field_priority": "Priority",
|
|
54
|
-
"create_task.field_due_date": "Due date",
|
|
55
|
-
"create_task.field_due_date_placeholder": "No due date",
|
|
56
|
-
"create_task.field_tags": "Tags",
|
|
57
|
-
"create_task.field_tags_placeholder": "Add tags separated by commas",
|
|
58
|
-
"create_task.field_tags_helper": "Press comma or Enter to add a tag",
|
|
59
|
-
"create_task.field_assign_to_me": "Assign to me",
|
|
60
|
-
"create_task.success": "Task created",
|
|
61
|
-
"create_task.error_title": "Couldn't create task",
|
|
62
|
-
"edit_task.title": "Edit task",
|
|
63
|
-
"edit_task.save": "Save",
|
|
64
|
-
"edit_task.saving": "Saving...",
|
|
65
|
-
"edit_task.field_title": "Title",
|
|
66
|
-
"edit_task.field_description": "Description",
|
|
67
|
-
"edit_task.field_priority": "Priority",
|
|
68
|
-
"edit_task.field_due_date": "Due date",
|
|
69
|
-
"edit_task.success": "Task updated",
|
|
70
|
-
"edit_task.error_title": "Couldn't update task",
|
|
71
|
-
"projects.title": "Projects",
|
|
72
|
-
"projects.new_project": "New project",
|
|
73
|
-
"projects.task_count.none": "No tasks",
|
|
74
|
-
"projects.task_count.one": "{count} task",
|
|
75
|
-
"projects.task_count.other": "{count} tasks",
|
|
76
|
-
"projects.empty_title": "No projects yet",
|
|
77
|
-
"projects.empty_body": "Create a project to organize your tasks.",
|
|
78
|
-
"projects.dialog_title": "New project",
|
|
79
|
-
"projects.field_name": "Project name",
|
|
80
|
-
"projects.field_name_placeholder": "e.g., Product Launch",
|
|
81
|
-
"projects.field_color": "Color",
|
|
82
|
-
"projects.field_icon": "Icon",
|
|
83
|
-
"projects.created": "Project created",
|
|
84
|
-
"project_detail.empty_title": "No tasks in this project",
|
|
85
|
-
"project_detail.empty_body": "Add a task to get started.",
|
|
86
|
-
"settings.preferences": "Preferences",
|
|
87
|
-
"settings.theme": "Theme",
|
|
88
|
-
"settings.theme_system": "System",
|
|
89
|
-
"settings.theme_light": "Light",
|
|
90
|
-
"settings.theme_dark": "Dark",
|
|
91
|
-
"settings.theme_warm": "Warm",
|
|
92
|
-
"settings.default_priority": "Default priority",
|
|
93
|
-
"settings.notifications": "Push notifications",
|
|
94
|
-
"settings.reminders": "Due date reminders",
|
|
95
|
-
"settings.reminders_helper": "Get notified 1 hour before a task is due",
|
|
96
|
-
"settings.data": "Data",
|
|
97
|
-
"settings.export": "Export data",
|
|
98
|
-
"settings.export_success": "Export sent to your email",
|
|
99
|
-
"settings.delete_account": "Delete account",
|
|
100
|
-
"settings.delete_title": "Delete your account?",
|
|
101
|
-
"settings.delete_message": "This will permanently delete your account and all your data. This action cannot be undone.",
|
|
102
|
-
"settings.delete_confirm": "Delete my account",
|
|
103
|
-
"settings.app_version": "TaskFlow v1.0.0",
|
|
104
|
-
"settings.app_credit": "Built with OpenUISpec",
|
|
105
|
-
"profile.change_photo": "Change photo",
|
|
106
|
-
"profile.field_name": "Name",
|
|
107
|
-
"profile.field_email": "Email",
|
|
108
|
-
"profile.save": "Save changes",
|
|
109
|
-
"profile.success": "Profile updated",
|
|
110
|
-
"calendar.title": "Calendar",
|
|
111
|
-
"calendar.coming_soon": "Coming in a future version",
|
|
112
|
-
"priority.low": "Low",
|
|
113
|
-
"priority.medium": "Medium",
|
|
114
|
-
"priority.high": "High",
|
|
115
|
-
"priority.urgent": "Urgent",
|
|
116
|
-
"status.todo": "To do",
|
|
117
|
-
"status.in_progress": "In progress",
|
|
118
|
-
"status.done": "Done",
|
|
119
|
-
"media_player.loading": "Loading media...",
|
|
120
|
-
"media_player.play": "Play",
|
|
121
|
-
"media_player.pause": "Pause",
|
|
122
|
-
"media_player.retry": "Retry",
|
|
123
|
-
"validation.required": "This field is required",
|
|
124
|
-
"validation.min_length": "Must be at least {min} characters",
|
|
125
|
-
"validation.tag_format": "Tags may only contain letters, numbers, and hyphens",
|
|
126
|
-
"common.cancel": "Cancel",
|
|
127
|
-
"common.delete": "Delete",
|
|
128
|
-
"common.create": "Create"
|
|
129
|
-
} as const;
|
|
130
|
-
|
|
131
|
-
export type MessageKey = keyof typeof messages;
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import type { ThemeMode } from "./types";
|
|
3
|
-
|
|
4
|
-
export function useResolvedTheme(theme: ThemeMode) {
|
|
5
|
-
const [prefersDark, setPrefersDark] = useState(false);
|
|
6
|
-
useEffect(() => {
|
|
7
|
-
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
8
|
-
const update = () => setPrefersDark(media.matches);
|
|
9
|
-
update();
|
|
10
|
-
media.addEventListener("change", update);
|
|
11
|
-
return () => media.removeEventListener("change", update);
|
|
12
|
-
}, []);
|
|
13
|
-
if (theme === "system") return prefersDark ? "dark" : "light";
|
|
14
|
-
return theme;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function useWindowWidth() {
|
|
18
|
-
const [width, setWidth] = useState(window.innerWidth);
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
const onResize = () => setWidth(window.innerWidth);
|
|
21
|
-
window.addEventListener("resize", onResize);
|
|
22
|
-
return () => window.removeEventListener("resize", onResize);
|
|
23
|
-
}, []);
|
|
24
|
-
return width;
|
|
25
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { messages } from "./generated/messages";
|
|
2
|
-
|
|
3
|
-
export function t(key: keyof typeof messages | string) {
|
|
4
|
-
return messages[key as keyof typeof messages] ?? key;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function interpolate(template: string, values: Record<string, string>) {
|
|
8
|
-
return template.replace(/\{(\w+)\}/g, (_, key) => values[key] ?? "");
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function greeting(name: string) {
|
|
12
|
-
const hour = new Date().getHours();
|
|
13
|
-
const key = hour < 12 ? "home.greeting.morning" : hour < 18 ? "home.greeting.afternoon" : "home.greeting.evening";
|
|
14
|
-
return interpolate(t(key), { name });
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function taskCountLabel(count: number) {
|
|
18
|
-
if (count === 0) return t("home.task_count.none");
|
|
19
|
-
if (count === 1) return interpolate(t("home.task_count.one"), { count: "1" });
|
|
20
|
-
return interpolate(t("home.task_count.other"), { count: String(count) });
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function projectTaskCountLabel(count: number) {
|
|
24
|
-
if (count === 0) return t("projects.task_count.none");
|
|
25
|
-
if (count === 1) return interpolate(t("projects.task_count.one"), { count: "1" });
|
|
26
|
-
return interpolate(t("projects.task_count.other"), { count: String(count) });
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function priorityLabel(priority: string) {
|
|
30
|
-
return t(`priority.${priority}`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function statusLabel(status: string) {
|
|
34
|
-
return t(`status.${status}`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function filterLabel(filter: string) {
|
|
38
|
-
return t(`home.filter.${filter}`);
|
|
39
|
-
}
|