deskforge 1.0.0 → 1.0.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.
- package/COMPLETION_REPORT.md +426 -0
- package/COMPONENT_API.md +529 -0
- package/DELIVERY_SUMMARY.md +455 -0
- package/IMPLEMENTATION_GUIDE.md +385 -0
- package/IMPORT_GUIDE.md +404 -0
- package/QUICK_REFERENCE.md +352 -0
- package/README.md +245 -31
- package/README_UI_COMPLETE.md +395 -0
- package/TYPE_DEFINITIONS.md +671 -0
- package/UI_GETTING_STARTED.md +523 -0
- package/examples/deskforge-app/App.ts +7 -11
- package/examples/deskforge-app/screens/CounterApp.ts +181 -0
- package/examples/deskforge-app/screens/GalleryApp.ts +267 -0
- package/examples/deskforge-app/screens/NotesApp.ts +376 -0
- package/examples/deskforge-app/screens/SettingsApp.ts +226 -0
- package/examples/deskforge-app/screens/TodoApp.ts +288 -0
- package/examples/deskforge-app/screens/index.ts +50 -0
- package/package.json +1 -1
- package/packages/cli/dist/commands/start.d.ts +7 -1
- package/packages/cli/dist/commands/start.d.ts.map +1 -1
- package/packages/cli/dist/commands/start.js +75 -21
- package/packages/cli/dist/commands/start.js.map +1 -1
- package/packages/cli/src/commands/start.ts +93 -21
- package/packages/cli/test/start.test.js +24 -0
- package/packages/core/dist/index.d.ts +1 -0
- package/packages/core/dist/index.d.ts.map +1 -1
- package/packages/core/dist/index.js +1 -0
- package/packages/core/dist/index.js.map +1 -1
- package/packages/core/dist/style/StyleObject.d.ts +115 -0
- package/packages/core/dist/style/StyleObject.d.ts.map +1 -0
- package/packages/core/dist/style/StyleObject.js +6 -0
- package/packages/core/dist/style/StyleObject.js.map +1 -0
- package/packages/core/dist/style/StyleSheet.d.ts +7 -0
- package/packages/core/dist/style/StyleSheet.d.ts.map +1 -0
- package/packages/core/dist/style/StyleSheet.js +13 -0
- package/packages/core/dist/style/StyleSheet.js.map +1 -0
- package/packages/core/dist/style/index.d.ts +3 -0
- package/packages/core/dist/style/index.d.ts.map +1 -0
- package/packages/core/dist/style/index.js +21 -0
- package/packages/core/dist/style/index.js.map +1 -0
- package/packages/core/src/index.ts +2 -1
- package/packages/core/src/style/StyleObject.ts +134 -0
- package/packages/core/src/style/StyleSheet.ts +19 -0
- package/packages/core/src/style/index.ts +5 -0
- package/packages/db/src/db.ts +4 -4
- package/packages/deskforge/src/index.ts +95 -0
- package/packages/file/dist/file.d.ts +1 -1
- package/packages/file/dist/file.d.ts.map +1 -1
- package/packages/file/dist/file.js +75 -3
- package/packages/file/dist/file.js.map +1 -1
- package/packages/file/src/file.ts +96 -4
- package/packages/file/test/file.test.js +23 -0
- package/packages/router/dist/index.d.ts +1 -0
- package/packages/router/dist/index.d.ts.map +1 -1
- package/packages/router/dist/index.js +1 -0
- package/packages/router/dist/index.js.map +1 -1
- package/packages/router/dist/navigation.d.ts +23 -0
- package/packages/router/dist/navigation.d.ts.map +1 -0
- package/packages/router/dist/navigation.js +61 -0
- package/packages/router/dist/navigation.js.map +1 -0
- package/packages/router/src/index.ts +2 -1
- package/packages/router/src/navigation.ts +86 -0
- package/packages/router/test/router.test.js +21 -1
- package/packages/theme/src/theme.ts +23 -0
- package/packages/ui/src/base/Button.ts +67 -0
- package/packages/ui/src/base/Image.ts +59 -0
- package/packages/ui/src/base/Pressable.ts +69 -0
- package/packages/ui/src/base/Text.ts +42 -0
- package/packages/ui/src/base/TextInput.ts +113 -0
- package/packages/ui/src/base/TouchableOpacity.ts +52 -0
- package/packages/ui/src/base/View.ts +43 -0
- package/packages/ui/src/index.ts +40 -25
- package/packages/ui/src/input/CheckBox.ts +69 -0
- package/packages/ui/src/input/Slider.ts +19 -2
- package/packages/ui/src/input/Switch.ts +68 -0
- package/packages/ui/src/layout/KeyboardAvoidingView.ts +65 -0
- package/packages/ui/src/layout/SafeAreaView.ts +58 -0
- package/packages/ui/src/list/FlatList.ts +111 -0
- package/packages/ui/src/list/ScrollView.ts +80 -0
- package/packages/ui/src/list/SectionList.ts +120 -0
- package/packages/ui/src/overlay/ActivityIndicator.ts +70 -0
- package/packages/ui/src/overlay/Modal.ts +28 -6
- package/packages/ui/src/platform/Platform.ts +119 -0
- package/packages/ui/src/platform/StatusBar.ts +63 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeskForge UI Components - Implementation Guide
|
|
3
|
+
*
|
|
4
|
+
* This document explains the core principles and usage patterns
|
|
5
|
+
* for building apps with DeskForge's functional UI components.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
# DeskForge UI Components - Implementation Guide
|
|
9
|
+
|
|
10
|
+
## Core Philosophy
|
|
11
|
+
|
|
12
|
+
DeskForge UI is built on a single principle:
|
|
13
|
+
> **Everything is a pure function that returns a DFNode**
|
|
14
|
+
|
|
15
|
+
No JSX, no HTML, no XML. No className strings. No CSS files. No hooks.
|
|
16
|
+
|
|
17
|
+
## The Pattern
|
|
18
|
+
|
|
19
|
+
Every UI component follows this exact pattern:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { createNode } from '@deskforge/core'
|
|
23
|
+
import type { DFNode, StyleObject } from '@deskforge/core'
|
|
24
|
+
|
|
25
|
+
export function ComponentName(
|
|
26
|
+
prop1: Type,
|
|
27
|
+
prop2?: Type,
|
|
28
|
+
children: DFNode[] = []
|
|
29
|
+
): DFNode {
|
|
30
|
+
return createNode(
|
|
31
|
+
'ComponentType',
|
|
32
|
+
{
|
|
33
|
+
prop1,
|
|
34
|
+
prop2: prop2 ?? defaultValue,
|
|
35
|
+
} as Record<string, unknown>,
|
|
36
|
+
children
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage Examples
|
|
42
|
+
|
|
43
|
+
### View - Primary Container
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
View({ flex: 1, backgroundColor: '#fff' }, [
|
|
47
|
+
Text('Hello World'),
|
|
48
|
+
Text('Second line'),
|
|
49
|
+
])
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Text - Simple Text Rendering
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
Text('Simple text', { fontSize: 16, fontWeight: 'bold' })
|
|
56
|
+
Text(42, { color: '#0077ff' }) // Numbers auto-convert
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Pressable - Tappable Container
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
Pressable(
|
|
63
|
+
{ padding: 16, borderRadius: 8 },
|
|
64
|
+
{
|
|
65
|
+
onPress: () => handlePress(),
|
|
66
|
+
onLongPress: () => handleLongPress(),
|
|
67
|
+
disabled: false,
|
|
68
|
+
},
|
|
69
|
+
[
|
|
70
|
+
Text('Tap me', { color: '#0077ff' }),
|
|
71
|
+
]
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Button - Quick Button Shorthand
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
Button('Submit', { backgroundColor: '#2ecc71', padding: 12 }, {
|
|
79
|
+
onPress: () => submitForm(),
|
|
80
|
+
})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### TextInput - Text Input Field
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
TextInput({ flex: 1, padding: 12, borderWidth: 1, borderColor: '#ccc' }, {
|
|
87
|
+
placeholder: 'Enter text',
|
|
88
|
+
value: state.text,
|
|
89
|
+
onChangeText: (text) => state.text = text,
|
|
90
|
+
keyboardType: 'email-address',
|
|
91
|
+
secureTextEntry: false,
|
|
92
|
+
})
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### FlatList - Efficient List Rendering
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// Simple list
|
|
99
|
+
FlatList(
|
|
100
|
+
items,
|
|
101
|
+
(item) => Text(item.name),
|
|
102
|
+
{ flex: 1 }
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
// List with configuration
|
|
106
|
+
FlatList(
|
|
107
|
+
items,
|
|
108
|
+
(item, index) => Pressable(
|
|
109
|
+
{ padding: 12, borderBottomWidth: 1, borderBottomColor: '#eee' },
|
|
110
|
+
{ onPress: () => selectItem(item) },
|
|
111
|
+
[
|
|
112
|
+
Text(item.title, { fontSize: 16, fontWeight: 'bold' }),
|
|
113
|
+
Text(item.subtitle, { color: '#999' }),
|
|
114
|
+
]
|
|
115
|
+
),
|
|
116
|
+
{ flex: 1 },
|
|
117
|
+
{
|
|
118
|
+
ItemSeparatorComponent: () => View({ height: 1, backgroundColor: '#f0f0f0' }, []),
|
|
119
|
+
onEndReached: () => loadMore(),
|
|
120
|
+
onEndReachedThreshold: 0.5,
|
|
121
|
+
}
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Modal - Full-Screen Overlay
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
Modal(
|
|
129
|
+
isOpen,
|
|
130
|
+
() => setIsOpen(false),
|
|
131
|
+
{ flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'center', alignItems: 'center' },
|
|
132
|
+
[
|
|
133
|
+
View({
|
|
134
|
+
backgroundColor: '#fff',
|
|
135
|
+
borderRadius: 12,
|
|
136
|
+
padding: 20,
|
|
137
|
+
width: '85%',
|
|
138
|
+
}, [
|
|
139
|
+
Text('Modal Title', { fontSize: 20, fontWeight: 'bold' }),
|
|
140
|
+
Text('Modal content here', { marginTop: 12, color: '#666' }),
|
|
141
|
+
]),
|
|
142
|
+
],
|
|
143
|
+
{ animationType: 'fade' }
|
|
144
|
+
)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Switch - Toggle Control
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
Switch(
|
|
151
|
+
isDarkMode,
|
|
152
|
+
(value) => setDarkMode(value),
|
|
153
|
+
{
|
|
154
|
+
onTintColor: '#0077ff',
|
|
155
|
+
thumbTintColor: '#fff',
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Slider - Range Input
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
Slider({ height: 40, padding: 20 }, {
|
|
164
|
+
value: volume,
|
|
165
|
+
minimumValue: 0,
|
|
166
|
+
maximumValue: 100,
|
|
167
|
+
step: 5,
|
|
168
|
+
onValueChange: (v) => setVolume(v),
|
|
169
|
+
minimumTrackTintColor: '#0077ff',
|
|
170
|
+
})
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Image - Image Rendering
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// From file path
|
|
177
|
+
Image('/assets/logo.png', { width: 100, height: 100 })
|
|
178
|
+
|
|
179
|
+
// From URI
|
|
180
|
+
Image({ uri: 'https://example.com/image.png' }, {
|
|
181
|
+
width: 200,
|
|
182
|
+
height: 200,
|
|
183
|
+
borderRadius: 12,
|
|
184
|
+
})
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Platform - Cross-Platform Support
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
// Detect OS
|
|
191
|
+
if (Platform.OS === 'ios') {
|
|
192
|
+
// iOS-specific code
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Select value by platform
|
|
196
|
+
const behavior = Platform.select({
|
|
197
|
+
ios: 'padding',
|
|
198
|
+
android: 'height',
|
|
199
|
+
default: 'padding',
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
// Render platform-specific components
|
|
203
|
+
Platform.desktop(DesktopComponent) // Returns node on desktop, null on mobile
|
|
204
|
+
Platform.mobile(MobileComponent) // Returns node on mobile, null on desktop
|
|
205
|
+
Platform.any(AlwaysRender) // Always returns node
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## State Management
|
|
209
|
+
|
|
210
|
+
DeskForge uses reactive state with direct mutations:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { State, Computed, Watch, Effect } from 'deskforge/state'
|
|
214
|
+
|
|
215
|
+
// Create reactive state
|
|
216
|
+
const state = State({
|
|
217
|
+
count: 0,
|
|
218
|
+
name: '',
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
// Mutate directly - NO setState needed
|
|
222
|
+
state.count++
|
|
223
|
+
state.name = 'John'
|
|
224
|
+
|
|
225
|
+
// Computed values
|
|
226
|
+
const doubled = Computed(() => state.count * 2)
|
|
227
|
+
console.log(doubled.value) // Automatically updates
|
|
228
|
+
|
|
229
|
+
// Effects (like useEffect)
|
|
230
|
+
Effect(() => {
|
|
231
|
+
console.log('Count changed:', state.count)
|
|
232
|
+
// Optional cleanup function
|
|
233
|
+
return () => console.log('Cleanup')
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
// Watch specific values
|
|
237
|
+
Watch(
|
|
238
|
+
() => state.count,
|
|
239
|
+
(newValue, oldValue) => {
|
|
240
|
+
console.log(`Count: ${oldValue} -> ${newValue}`)
|
|
241
|
+
}
|
|
242
|
+
)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## StyleSheet - Style Objects
|
|
246
|
+
|
|
247
|
+
All styles use React Native property names via StyleObject:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
const styles = StyleSheet.create({
|
|
251
|
+
container: {
|
|
252
|
+
flex: 1,
|
|
253
|
+
backgroundColor: '#fff',
|
|
254
|
+
padding: 16,
|
|
255
|
+
justifyContent: 'center',
|
|
256
|
+
alignItems: 'center',
|
|
257
|
+
},
|
|
258
|
+
title: {
|
|
259
|
+
fontSize: 20,
|
|
260
|
+
fontWeight: 'bold',
|
|
261
|
+
color: '#111',
|
|
262
|
+
marginBottom: 8,
|
|
263
|
+
},
|
|
264
|
+
button: {
|
|
265
|
+
backgroundColor: '#0077ff',
|
|
266
|
+
borderRadius: 8,
|
|
267
|
+
paddingHorizontal: 16,
|
|
268
|
+
paddingVertical: 12,
|
|
269
|
+
elevation: 2,
|
|
270
|
+
shadowColor: '#000',
|
|
271
|
+
shadowOpacity: 0.1,
|
|
272
|
+
shadowRadius: 4,
|
|
273
|
+
},
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
// Use styles
|
|
277
|
+
View(styles.container, [...])
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Style Properties
|
|
281
|
+
|
|
282
|
+
### Layout
|
|
283
|
+
`flex`, `flexDirection`, `justifyContent`, `alignItems`, `alignSelf`, `flexWrap`, `gap`, `rowGap`, `columnGap`
|
|
284
|
+
|
|
285
|
+
### Spacing
|
|
286
|
+
`margin`, `marginTop`, `marginBottom`, `marginLeft`, `marginRight`, `marginHorizontal`, `marginVertical`
|
|
287
|
+
`padding`, `paddingTop`, `paddingBottom`, `paddingLeft`, `paddingRight`, `paddingHorizontal`, `paddingVertical`
|
|
288
|
+
|
|
289
|
+
### Dimensions
|
|
290
|
+
`width`, `height`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`
|
|
291
|
+
|
|
292
|
+
### Appearance
|
|
293
|
+
`backgroundColor`, `borderRadius`, `borderWidth`, `borderColor`, `opacity`, `overflow`, `zIndex`
|
|
294
|
+
`shadowColor`, `shadowOpacity`, `shadowRadius`, `shadowOffset`, `elevation`
|
|
295
|
+
|
|
296
|
+
### Typography
|
|
297
|
+
`fontSize`, `fontWeight`, `fontFamily`, `color`, `textAlign`, `lineHeight`, `letterSpacing`, `textDecorationLine`
|
|
298
|
+
|
|
299
|
+
### Position
|
|
300
|
+
`position`, `top`, `bottom`, `left`, `right`
|
|
301
|
+
|
|
302
|
+
## Key Patterns
|
|
303
|
+
|
|
304
|
+
### Conditional Rendering
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
View({ flex: 1 }, [
|
|
308
|
+
isLoading ? ActivityIndicator() : renderContent(),
|
|
309
|
+
state.error ? View({}, [Text(state.error, { color: 'red' })]) : null,
|
|
310
|
+
].filter(Boolean))
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### List Operations
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// Array filtering
|
|
317
|
+
state.items = state.items.filter(item => item.id !== toDelete)
|
|
318
|
+
|
|
319
|
+
// Array mutations
|
|
320
|
+
state.items.unshift(newItem)
|
|
321
|
+
state.items.push(newItem)
|
|
322
|
+
state.items.splice(index, 1)
|
|
323
|
+
|
|
324
|
+
// Array mapping with FlatList
|
|
325
|
+
FlatList(
|
|
326
|
+
state.items,
|
|
327
|
+
(item) => renderItem(item),
|
|
328
|
+
{ flex: 1 }
|
|
329
|
+
)
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Nested Components
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
const Header = (title: string) => View(
|
|
336
|
+
{ padding: 16, backgroundColor: '#fff' },
|
|
337
|
+
[Text(title, { fontSize: 20, fontWeight: 'bold' })]
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
const Container = (children: DFNode[]) => View(
|
|
341
|
+
{ flex: 1, backgroundColor: '#f8f9fc' },
|
|
342
|
+
children
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
// Usage
|
|
346
|
+
Container([
|
|
347
|
+
Header('My App'),
|
|
348
|
+
Content(),
|
|
349
|
+
])
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Error Handling
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
const state = State({ error: null as string | null })
|
|
356
|
+
|
|
357
|
+
Effect(() => {
|
|
358
|
+
DB.query('items')
|
|
359
|
+
.all()
|
|
360
|
+
.then(items => state.items = items)
|
|
361
|
+
.catch(err => state.error = err.message)
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
// In render
|
|
365
|
+
state.error
|
|
366
|
+
? View({ padding: 20 }, [Text(state.error, { color: '#e74c3c' })])
|
|
367
|
+
: renderContent()
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## Best Practices
|
|
371
|
+
|
|
372
|
+
1. **Use StyleSheet.create()** - Provides type safety and freezes styles
|
|
373
|
+
2. **Name your styles** - Use descriptive names like `buttonPrimary`, not `button1`
|
|
374
|
+
3. **Leverage State directly** - Mutate state, don't create new state objects
|
|
375
|
+
4. **Use Computed for derived values** - Don't manually track computed state
|
|
376
|
+
5. **Keep components small** - Extract common patterns into helper functions
|
|
377
|
+
6. **Type your props** - Use TypeScript interfaces for component arguments
|
|
378
|
+
7. **Document with JSDoc** - Add comments above functions
|
|
379
|
+
8. **Use FlatList for lists** - Not manual maps; FlatList virtualizes for performance
|
|
380
|
+
9. **Use Platform for OS detection** - Don't manually detect with userAgent
|
|
381
|
+
10. **Combine Pressable + styles** - Create semantic button styles, don't hard-code
|
|
382
|
+
|
|
383
|
+
## Complete Example
|
|
384
|
+
|
|
385
|
+
See `NotesApp.ts`, `TodoApp.ts`, `SettingsApp.ts`, `CounterApp.ts`, and `GalleryApp.ts` for full working examples of all patterns combined.
|