ui-ux-consultant-cli 1.0.0-beta.1

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.
Files changed (30) hide show
  1. package/assets/ui-ux-consultant/SKILL.md +844 -0
  2. package/assets/ui-ux-consultant/references/accessibility.md +175 -0
  3. package/assets/ui-ux-consultant/references/alt-libraries.md +90 -0
  4. package/assets/ui-ux-consultant/references/animations.md +448 -0
  5. package/assets/ui-ux-consultant/references/catalog/colors.md +91 -0
  6. package/assets/ui-ux-consultant/references/catalog/fonts.md +363 -0
  7. package/assets/ui-ux-consultant/references/catalog/products.md +340 -0
  8. package/assets/ui-ux-consultant/references/catalog/styles.md +165 -0
  9. package/assets/ui-ux-consultant/references/components.md +1116 -0
  10. package/assets/ui-ux-consultant/references/patterns.md +600 -0
  11. package/assets/ui-ux-consultant/references/performance.md +198 -0
  12. package/assets/ui-ux-consultant/references/stacks/astro.md +382 -0
  13. package/assets/ui-ux-consultant/references/stacks/flutter.md +308 -0
  14. package/assets/ui-ux-consultant/references/stacks/html-tailwind.md +415 -0
  15. package/assets/ui-ux-consultant/references/stacks/jetpack-compose.md +333 -0
  16. package/assets/ui-ux-consultant/references/stacks/laravel.md +521 -0
  17. package/assets/ui-ux-consultant/references/stacks/nextjs.md +275 -0
  18. package/assets/ui-ux-consultant/references/stacks/nuxt-ui.md +384 -0
  19. package/assets/ui-ux-consultant/references/stacks/nuxtjs.md +264 -0
  20. package/assets/ui-ux-consultant/references/stacks/react-native.md +346 -0
  21. package/assets/ui-ux-consultant/references/stacks/react.md +268 -0
  22. package/assets/ui-ux-consultant/references/stacks/shadcn.md +485 -0
  23. package/assets/ui-ux-consultant/references/stacks/svelte.md +429 -0
  24. package/assets/ui-ux-consultant/references/stacks/swiftui.md +336 -0
  25. package/assets/ui-ux-consultant/references/stacks/threejs.md +366 -0
  26. package/assets/ui-ux-consultant/references/stacks/vue.md +272 -0
  27. package/assets/ui-ux-consultant/references/theming.md +701 -0
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +130 -0
  30. package/package.json +51 -0
@@ -0,0 +1,333 @@
1
+ # Jetpack Compose Reference
2
+
3
+ ## When to Read
4
+ Read this file when building Android apps with Jetpack Compose (Kotlin) — composables, state, ViewModel, LazyColumn, navigation, Material 3 theming, or accessibility.
5
+
6
+ ---
7
+
8
+ ## Recommended Libraries
9
+
10
+ | Library | Purpose |
11
+ |---|---|
12
+ | Coil | Image loading (`AsyncImage`) |
13
+ | Hilt | Dependency injection |
14
+ | Room | Local database with Flow |
15
+ | Retrofit + OkHttp | HTTP networking |
16
+ | Accompanist | Compose utilities (pager, permissions) |
17
+ | Voyager | Alternative navigation library |
18
+ | DataStore | Preferences persistence (replaces SharedPreferences) |
19
+ | Kotlin Serialization | JSON parsing |
20
+
21
+ ---
22
+
23
+ ## Style Recommendations
24
+
25
+ - Material 3 everywhere — not M2; `MaterialTheme` from `androidx.compose.material3`
26
+ - Dynamic color (`dynamicColorScheme`) on Android 12+ — fallback for older devices
27
+ - 8dp spacing scale: 4, 8, 12, 16, 24, 32, 48
28
+ - `MaterialTheme.typography` tokens — not hardcoded `sp` values
29
+ - `MaterialTheme.colorScheme` tokens — not raw `Color(0xFF...)` in composables
30
+ - Shape: `MaterialTheme.shapes.medium` for cards, `.small` for chips
31
+
32
+ ---
33
+
34
+ ## Core Paradigm
35
+
36
+ UI = composable functions. State hoisting: lift state up to the lowest common ancestor that needs it. `remember` for local ephemeral state; `ViewModel` + `StateFlow` for lifecycle-aware state.
37
+
38
+ ---
39
+
40
+ ## Top UX Patterns (with Code)
41
+
42
+ ### ViewModel with StateFlow
43
+ ```kotlin
44
+ class UserViewModel : ViewModel() {
45
+ private val _users = MutableStateFlow<List<User>>(emptyList())
46
+ val users: StateFlow<List<User>> = _users.asStateFlow()
47
+
48
+ private val _isLoading = MutableStateFlow(false)
49
+ val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
50
+
51
+ fun loadUsers() = viewModelScope.launch {
52
+ _isLoading.value = true
53
+ try {
54
+ _users.value = userRepository.getAll()
55
+ } finally {
56
+ _isLoading.value = false
57
+ }
58
+ }
59
+ }
60
+
61
+ // In composable:
62
+ @Composable
63
+ fun UserListScreen(viewModel: UserViewModel = hiltViewModel()) {
64
+ val users by viewModel.users.collectAsStateWithLifecycle()
65
+ val isLoading by viewModel.isLoading.collectAsStateWithLifecycle()
66
+
67
+ LaunchedEffect(Unit) { viewModel.loadUsers() }
68
+
69
+ if (isLoading) {
70
+ Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
71
+ CircularProgressIndicator()
72
+ }
73
+ } else {
74
+ UserList(users = users)
75
+ }
76
+ }
77
+ ```
78
+
79
+ ### Scaffold + TopAppBar + FAB
80
+ ```kotlin
81
+ Scaffold(
82
+ topBar = {
83
+ TopAppBar(
84
+ title = { Text("Dashboard") },
85
+ navigationIcon = {
86
+ IconButton(onClick = { navController.navigateUp() }) {
87
+ Icon(Icons.Default.ArrowBack, contentDescription = "Back")
88
+ }
89
+ },
90
+ actions = {
91
+ IconButton(onClick = { /* open settings */ }) {
92
+ Icon(Icons.Default.Settings, contentDescription = "Settings")
93
+ }
94
+ }
95
+ )
96
+ },
97
+ floatingActionButton = {
98
+ FloatingActionButton(onClick = onAdd) {
99
+ Icon(Icons.Default.Add, contentDescription = "Add item")
100
+ }
101
+ }
102
+ ) { paddingValues ->
103
+ LazyColumn(
104
+ modifier = Modifier.padding(paddingValues),
105
+ contentPadding = PaddingValues(16.dp),
106
+ verticalArrangement = Arrangement.spacedBy(8.dp)
107
+ ) {
108
+ items(users, key = { it.id }) { user ->
109
+ UserCard(user = user)
110
+ }
111
+ }
112
+ }
113
+ ```
114
+
115
+ ### LazyColumn (always for long lists)
116
+ ```kotlin
117
+ LazyColumn(
118
+ contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
119
+ verticalArrangement = Arrangement.spacedBy(8.dp)
120
+ ) {
121
+ items(items = users, key = { it.id }) { user ->
122
+ UserCard(user = user)
123
+ }
124
+ item {
125
+ Spacer(Modifier.height(80.dp)) // clearance for FAB
126
+ }
127
+ }
128
+ // Never use Column for long lists — renders all items eagerly
129
+ ```
130
+
131
+ ### Material 3 theming with dynamic color
132
+ ```kotlin
133
+ @Composable
134
+ fun AppTheme(
135
+ darkTheme: Boolean = isSystemInDarkTheme(),
136
+ content: @Composable () -> Unit
137
+ ) {
138
+ val colorScheme = when {
139
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
140
+ val context = LocalContext.current
141
+ if (darkTheme) dynamicDarkColorScheme(context)
142
+ else dynamicLightColorScheme(context)
143
+ }
144
+ darkTheme -> darkColorScheme(primary = Color(0xFF2563EB))
145
+ else -> lightColorScheme(primary = Color(0xFF2563EB))
146
+ }
147
+
148
+ MaterialTheme(
149
+ colorScheme = colorScheme,
150
+ typography = Typography(),
151
+ content = content
152
+ )
153
+ }
154
+ ```
155
+
156
+ ### Navigation (Compose Navigation)
157
+ ```kotlin
158
+ val navController = rememberNavController()
159
+
160
+ NavHost(navController = navController, startDestination = "home") {
161
+ composable("home") {
162
+ HomeScreen(
163
+ onNavigateToDetail = { id -> navController.navigate("detail/$id") }
164
+ )
165
+ }
166
+ composable(
167
+ route = "detail/{id}",
168
+ arguments = listOf(navArgument("id") { type = NavType.StringType })
169
+ ) { backStackEntry ->
170
+ val id = backStackEntry.arguments?.getString("id") ?: return@composable
171
+ DetailScreen(id = id, onBack = { navController.navigateUp() })
172
+ }
173
+ }
174
+ ```
175
+
176
+ ### State hoisting pattern
177
+ ```kotlin
178
+ // Stateful (screen-level)
179
+ @Composable
180
+ fun SearchScreen(viewModel: SearchViewModel = hiltViewModel()) {
181
+ val query by viewModel.query.collectAsStateWithLifecycle()
182
+ SearchBar(
183
+ query = query,
184
+ onQueryChange = viewModel::onQueryChange,
185
+ onSearch = viewModel::search
186
+ )
187
+ }
188
+
189
+ // Stateless (reusable)
190
+ @Composable
191
+ fun SearchBar(
192
+ query: String,
193
+ onQueryChange: (String) -> Unit,
194
+ onSearch: () -> Unit,
195
+ modifier: Modifier = Modifier
196
+ ) {
197
+ OutlinedTextField(
198
+ value = query,
199
+ onValueChange = onQueryChange,
200
+ modifier = modifier.fillMaxWidth(),
201
+ placeholder = { Text("Search…") },
202
+ trailingIcon = {
203
+ IconButton(onClick = onSearch) {
204
+ Icon(Icons.Default.Search, contentDescription = "Search")
205
+ }
206
+ },
207
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
208
+ keyboardActions = KeyboardActions(onSearch = { onSearch() })
209
+ )
210
+ }
211
+ ```
212
+
213
+ ### Accessible icon
214
+ ```kotlin
215
+ Icon(
216
+ imageVector = Icons.Default.Delete,
217
+ contentDescription = "Delete item", // never null on interactive icons
218
+ modifier = Modifier
219
+ .clickable(
220
+ role = Role.Button,
221
+ onClickLabel = "Delete this item"
222
+ ) { onDelete() }
223
+ )
224
+ ```
225
+
226
+ ### derivedStateOf for expensive computation
227
+ ```kotlin
228
+ val filteredItems by remember {
229
+ derivedStateOf {
230
+ // Only recomputes when searchQuery or items changes
231
+ items.filter { it.name.contains(searchQuery, ignoreCase = true) }
232
+ }
233
+ }
234
+ ```
235
+
236
+ ### Pull-to-refresh
237
+ ```kotlin
238
+ val pullRefreshState = rememberPullRefreshState(
239
+ refreshing = isRefreshing,
240
+ onRefresh = { viewModel.refresh() }
241
+ )
242
+
243
+ Box(Modifier.pullRefresh(pullRefreshState)) {
244
+ LazyColumn { /* items */ }
245
+ PullRefreshIndicator(
246
+ refreshing = isRefreshing,
247
+ state = pullRefreshState,
248
+ modifier = Modifier.align(Alignment.TopCenter)
249
+ )
250
+ }
251
+ ```
252
+
253
+ ---
254
+
255
+ ## Best Practices by Category
256
+
257
+ ### Composables
258
+ - Keep composables small and single-purpose — extract at ~30 lines
259
+ - Stateless composables take data + callbacks — easier to test and preview
260
+ - `modifier: Modifier = Modifier` as last parameter on every composable
261
+ - `@Preview` annotations with realistic data — not empty states only
262
+ - `@Stable` or `@Immutable` on data classes passed to composables for skipping recomposition
263
+
264
+ ### State
265
+ - `remember { mutableStateOf() }` only for local transient state (toggle, input draft)
266
+ - ViewModel + `StateFlow` for anything that survives config changes
267
+ - `collectAsStateWithLifecycle()` not `collectAsState()` — stops collection in background
268
+ - `rememberSaveable` for state that must survive process death (text field drafts)
269
+ - Single source of truth: don't duplicate state between ViewModel and composable
270
+
271
+ ### Lists
272
+ - `LazyColumn` / `LazyRow` for any list that might scroll
273
+ - `key = { item.id }` in `items()` — stable keys prevent unnecessary recomposition on reorder
274
+ - `contentPadding` on lazy lists to avoid clipping at edges
275
+ - `Arrangement.spacedBy(dp)` instead of per-item `Spacer`
276
+ - `StickyHeader` inside `LazyColumn` for grouped lists
277
+
278
+ ### Navigation
279
+ - One `NavHost` at the root of the app
280
+ - Pass only IDs between destinations — fetch data in the destination's ViewModel
281
+ - `popUpTo` + `saveState` for bottom nav tabs (preserve back stacks)
282
+ - `BackHandler` for custom back behavior in leaf screens
283
+
284
+ ### Material 3 Components
285
+ - `Card` not `Surface` + manual shape/elevation — `Card` handles semantics
286
+ - `FilledButton` for primary action, `OutlinedButton` secondary, `TextButton` tertiary
287
+ - `SnackbarHostState` + `Scaffold`'s `snackbarHost` for feedback messages
288
+ - `AlertDialog` for confirmations — not custom dialogs
289
+ - `ModalBottomSheet` for secondary flows (not navigation)
290
+
291
+ ### Accessibility
292
+ - `contentDescription` on every `Icon` that's interactive — `null` only for decorative icons
293
+ - `Modifier.semantics { role = Role.Button }` when using `clickable` on non-Button
294
+ - `Modifier.clearAndSetSemantics { }` to replace auto-generated semantics
295
+ - Min touch target: 48dp × 48dp — use `Modifier.minimumInteractiveComponentSize()`
296
+ - Test with TalkBack on real device
297
+
298
+ ### Animation
299
+ - `AnimatedVisibility` for enter/exit transitions
300
+ - `animateContentSize()` for layout size changes
301
+ - `updateTransition` for state-machine animations
302
+ - `Crossfade` for content swapping
303
+ - `spring()` for natural motion; `tween()` for controlled duration
304
+
305
+ ---
306
+
307
+ ## Common Anti-Patterns
308
+
309
+ 1. `Column` with long lists — renders all items eagerly; freezes UI; use `LazyColumn`
310
+ 2. No `key` in `items()` — poor diff performance on reorder/insert; React-like index issues
311
+ 3. Heavy computation in composable body — runs on every recomposition; use `remember { derivedStateOf { } }`
312
+ 4. `mutableStateListOf` in ViewModel — doesn't survive config change; use `StateFlow<List<T>>`
313
+ 5. Missing `contentDescription` on icons — TalkBack announces "unlabeled button"
314
+ 6. `collectAsState()` without lifecycle awareness — continues collection in background, wastes battery
315
+ 7. State inside `LazyListScope` lambdas — `remember` doesn't work inside `items {}` blocks
316
+ 8. `navController` passed deep into composables — pass lambdas instead; keeps composables testable
317
+ 9. Hardcoded colors/dimensions — breaks theming; use `MaterialTheme.colorScheme` / `MaterialTheme.spacing`
318
+ 10. Missing `modifier` parameter on reusable composables — callers can't adjust layout
319
+
320
+ ---
321
+
322
+ ## Performance Checklist
323
+
324
+ - [ ] `LazyColumn`/`LazyRow` for lists (not `Column`/`Row`)
325
+ - [ ] `key = { item.id }` in all `items()` calls
326
+ - [ ] `collectAsStateWithLifecycle()` not `collectAsState()` (lifecycle-aware)
327
+ - [ ] `remember { derivedStateOf { } }` for expensive computations
328
+ - [ ] `@Stable` or `@Immutable` on data classes passed to composables
329
+ - [ ] `Modifier.fillMaxWidth()` before `weight()` in nested layouts
330
+ - [ ] `rememberSaveable` for user input that must survive process death
331
+ - [ ] `minimumInteractiveComponentSize()` on all clickable elements (48dp min)
332
+ - [ ] Profile with Layout Inspector + Composition trace in Android Studio
333
+ - [ ] Baseline Profiles configured for startup performance