vibefast-cli 0.1.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/FINAL-STATUS.md +144 -0
- package/HOW-IT-WORKS.md +559 -0
- package/PLAN.md +453 -0
- package/README.md +129 -0
- package/RECIPES-READY.md +172 -0
- package/STATUS.md +199 -0
- package/SUCCESS.md +259 -0
- package/TESTING-CHECKLIST.md +450 -0
- package/cloudflare-worker/.wrangler/state/v3/kv/64907821e2634080acce34618d2f3d4c/blobs/11f2769953c717e188062bc644da97c1fd1e4d6d0813a226ce7567dba759afab0000019a736fb8d4 +1 -0
- package/cloudflare-worker/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/0b03767237c0408301af51ca35d4b09470cbc479c7e5f23cc9de774749d23c59.sqlite +0 -0
- package/cloudflare-worker/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/0b03767237c0408301af51ca35d4b09470cbc479c7e5f23cc9de774749d23c59.sqlite-shm +0 -0
- package/cloudflare-worker/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/0b03767237c0408301af51ca35d4b09470cbc479c7e5f23cc9de774749d23c59.sqlite-wal +0 -0
- package/cloudflare-worker/.wrangler/state/v3/r2/miniflare-R2BucketObject/d1cc388a1a0ef44dd5669fd1a165d168b61362136c8b5fa50aefd96c72688e54.sqlite +0 -0
- package/cloudflare-worker/.wrangler/state/v3/r2/miniflare-R2BucketObject/d1cc388a1a0ef44dd5669fd1a165d168b61362136c8b5fa50aefd96c72688e54.sqlite-shm +0 -0
- package/cloudflare-worker/.wrangler/state/v3/r2/miniflare-R2BucketObject/d1cc388a1a0ef44dd5669fd1a165d168b61362136c8b5fa50aefd96c72688e54.sqlite-wal +0 -0
- package/cloudflare-worker/.wrangler/state/v3/r2/vibefast-recipes/blobs/620e8cf7c35d9806da25dee237e1d7e8b2432bd98f755b60e2c7f08a48d2c7b90000019a73736484 +0 -0
- package/cloudflare-worker/MIGRATION.md +160 -0
- package/cloudflare-worker/QUICKSTART.md +200 -0
- package/cloudflare-worker/README.md +242 -0
- package/cloudflare-worker/generate-token.js +32 -0
- package/cloudflare-worker/mini-native@latest.zip +0 -0
- package/cloudflare-worker/setup.sh +143 -0
- package/cloudflare-worker/test-recipe/apps/native/src/app/mini/index.tsx +15 -0
- package/cloudflare-worker/test-recipe/recipe.json +16 -0
- package/cloudflare-worker/worker.js +308 -0
- package/cloudflare-worker/wrangler.toml +13 -0
- package/dist/commands/add.d.ts +3 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +149 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/devices.d.ts +3 -0
- package/dist/commands/devices.d.ts.map +1 -0
- package/dist/commands/devices.js +35 -0
- package/dist/commands/devices.js.map +1 -0
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +67 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +40 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +23 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +3 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +16 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/remove.d.ts +3 -0
- package/dist/commands/remove.d.ts.map +1 -0
- package/dist/commands/remove.js +67 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/core/__tests__/journal.test.d.ts +2 -0
- package/dist/core/__tests__/journal.test.d.ts.map +1 -0
- package/dist/core/__tests__/journal.test.js +101 -0
- package/dist/core/__tests__/journal.test.js.map +1 -0
- package/dist/core/__tests__/validate.test.d.ts +2 -0
- package/dist/core/__tests__/validate.test.d.ts.map +1 -0
- package/dist/core/__tests__/validate.test.js +53 -0
- package/dist/core/__tests__/validate.test.js.map +1 -0
- package/dist/core/archive.d.ts +2 -0
- package/dist/core/archive.d.ts.map +1 -0
- package/dist/core/archive.js +59 -0
- package/dist/core/archive.js.map +1 -0
- package/dist/core/auth.d.ts +15 -0
- package/dist/core/auth.d.ts.map +1 -0
- package/dist/core/auth.js +76 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/codemod.d.ts +20 -0
- package/dist/core/codemod.d.ts.map +1 -0
- package/dist/core/codemod.js +150 -0
- package/dist/core/codemod.js.map +1 -0
- package/dist/core/fsx.d.ts +12 -0
- package/dist/core/fsx.d.ts.map +1 -0
- package/dist/core/fsx.js +70 -0
- package/dist/core/fsx.js.map +1 -0
- package/dist/core/http.d.ts +30 -0
- package/dist/core/http.d.ts.map +1 -0
- package/dist/core/http.js +95 -0
- package/dist/core/http.js.map +1 -0
- package/dist/core/journal.d.ts +18 -0
- package/dist/core/journal.d.ts.map +1 -0
- package/dist/core/journal.js +34 -0
- package/dist/core/journal.js.map +1 -0
- package/dist/core/log.d.ts +8 -0
- package/dist/core/log.d.ts.map +1 -0
- package/dist/core/log.js +9 -0
- package/dist/core/log.js.map +1 -0
- package/dist/core/pathGuard.d.ts +3 -0
- package/dist/core/pathGuard.d.ts.map +1 -0
- package/dist/core/pathGuard.js +18 -0
- package/dist/core/pathGuard.js.map +1 -0
- package/dist/core/paths.d.ts +11 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +22 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/validate.d.ts +8 -0
- package/dist/core/validate.d.ts.map +1 -0
- package/dist/core/validate.js +27 -0
- package/dist/core/validate.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/docs/decisions.md +55 -0
- package/package.json +39 -0
- package/recipes/audio-recorder/apps/native/src/app/audio-recorder/index.tsx +5 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/audio-player.tsx +301 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/audio-recorder.tsx +373 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/audio-waveform.tsx +270 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/index.ts +4 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/recording-list.tsx +89 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/audio-player-demo.tsx +66 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/audio-recorder-cloud.tsx +68 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/audio-recorder-interview.tsx +102 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/basic.tsx +27 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/index.ts +5 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/with-recording-list-demo.tsx +82 -0
- package/recipes/audio-recorder/recipe.json +22 -0
- package/recipes/audio-recorder@latest.zip +0 -0
- package/recipes/charts/apps/native/src/app/charts/index.tsx +3 -0
- package/recipes/charts/apps/native/src/features/charts/README.md +185 -0
- package/recipes/charts/apps/native/src/features/charts/app/preview.tsx +223 -0
- package/recipes/charts/apps/native/src/features/charts/components/area-chart.tsx +40 -0
- package/recipes/charts/apps/native/src/features/charts/components/bar-chart.tsx +143 -0
- package/recipes/charts/apps/native/src/features/charts/components/candlestick-chart.tsx +196 -0
- package/recipes/charts/apps/native/src/features/charts/components/chart-card.tsx +65 -0
- package/recipes/charts/apps/native/src/features/charts/components/column-chart.tsx +143 -0
- package/recipes/charts/apps/native/src/features/charts/components/doughnut-chart.tsx +246 -0
- package/recipes/charts/apps/native/src/features/charts/components/index.ts +10 -0
- package/recipes/charts/apps/native/src/features/charts/components/line-chart.tsx +308 -0
- package/recipes/charts/apps/native/src/features/charts/components/radar-chart.tsx +180 -0
- package/recipes/charts/apps/native/src/features/charts/components/radial-bar-chart.tsx +188 -0
- package/recipes/charts/apps/native/src/features/charts/components/stacked-area-chart.tsx +265 -0
- package/recipes/charts/apps/native/src/features/charts/components/stacked-bar-chart.tsx +322 -0
- package/recipes/charts/apps/native/src/features/charts/data/mock-data.ts +183 -0
- package/recipes/charts/apps/native/src/features/charts/types/index.ts +66 -0
- package/recipes/charts/recipe.json +22 -0
- package/recipes/charts@latest.zip +0 -0
- package/recipes/chatbot/apps/native/src/app/chatbot/index.tsx +1 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/app/index.tsx +302 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-header-buttons.tsx +59 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-input-bar.tsx +469 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-markdown.tsx +575 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-message-bubble.tsx +246 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-settings-modal.tsx +161 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/image-preview-list.tsx +115 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/code-block.tsx +165 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/index.ts +10 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/table-renderer.tsx +129 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/message-error-boundary.tsx +78 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/message-list.tsx +173 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/model-selector.tsx +283 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/report-content-modal.tsx +188 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/suggested-messages.tsx +67 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/constants/models.ts +20 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/constants/report-reasons.ts +9 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-attachment-cache.ts +143 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-chat-config.ts +664 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-chat-handlers.ts +359 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-chatbot-settings.ts +89 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-conversation.ts +79 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-image-picker.ts +122 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-keyboard-coordinator.ts +161 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-smart-scroll-manager.ts +207 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/models/index.ts +86 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/models/models.ts +162 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/models/providers.ts +62 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/models/types.ts +40 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/services/file-uploader.ts +238 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/services/message-handler-service.ts +180 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/types/index.ts +60 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/utils/chat-telemetry.ts +91 -0
- package/recipes/chatbot/recipe.json +22 -0
- package/recipes/chatbot@latest.zip +0 -0
- package/recipes/image-generator/apps/native/src/app/image-generator/gallery.tsx +3 -0
- package/recipes/image-generator/apps/native/src/app/image-generator/index.tsx +3 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/app/_layout.tsx +25 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/app/gallery.tsx +217 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/app/index.tsx +237 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/components/gallery-image.tsx +26 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/components/image-detail-modal.tsx +215 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/components/image-model-selector.tsx +210 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/components/image-placeholder.tsx +26 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-gallery.ts +71 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-generator-settings.ts +152 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-generator.ts +93 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/models/models.ts +66 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/services/image-gallery-service.ts +98 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/services/image-save-service.ts +121 -0
- package/recipes/image-generator/recipe.json +22 -0
- package/recipes/image-generator@latest.zip +0 -0
- package/recipes/quiz/apps/native/src/app/quiz/index.tsx +47 -0
- package/recipes/quiz/apps/native/src/features/quiz/components/question.tsx +67 -0
- package/recipes/quiz/apps/native/src/features/quiz/config.ts +11 -0
- package/recipes/quiz/apps/native/src/features/quiz/index.tsx +133 -0
- package/recipes/quiz/recipe.json +22 -0
- package/recipes/quiz@latest.zip +0 -0
- package/recipes/tracker-app/apps/native/src/app/tracker-app/index.tsx +1 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/app/index.tsx +108 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/animated-number.tsx +102 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/calorie-card.tsx +66 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/circular-progress.tsx +97 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/floating-add-button.tsx +27 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/macro-card.tsx +80 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/promo-banner.tsx +98 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/recently-logged.tsx +64 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/week-calendar.tsx +68 -0
- package/recipes/tracker-app/recipe.json +22 -0
- package/recipes/tracker-app@latest.zip +0 -0
- package/recipes/upload-all.sh +32 -0
- package/recipes/voice-bot/apps/native/src/app/voice-bot/index.tsx +27 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/README.md +185 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/conversation-status.tsx +76 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/index.ts +4 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/message-input.tsx +98 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/voice-bot-screen.tsx +173 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/voice-controls.tsx +73 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/index.ts +3 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/services/index.ts +1 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/services/use-voice-bot.ts +161 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/types.ts +29 -0
- package/recipes/voice-bot/recipe.json +22 -0
- package/recipes/voice-bot@latest.zip +0 -0
- package/scripts/create-recipes.mjs +189 -0
- package/src/commands/add.ts +183 -0
- package/src/commands/devices.ts +38 -0
- package/src/commands/doctor.ts +67 -0
- package/src/commands/list.ts +45 -0
- package/src/commands/login.ts +24 -0
- package/src/commands/logout.ts +15 -0
- package/src/commands/remove.ts +78 -0
- package/src/core/__tests__/journal.test.ts +119 -0
- package/src/core/__tests__/validate.test.ts +64 -0
- package/src/core/archive.ts +69 -0
- package/src/core/auth.ts +103 -0
- package/src/core/codemod.ts +211 -0
- package/src/core/fsx.ts +80 -0
- package/src/core/http.ts +136 -0
- package/src/core/journal.ts +64 -0
- package/src/core/log.ts +9 -0
- package/src/core/pathGuard.ts +22 -0
- package/src/core/paths.ts +33 -0
- package/src/core/validate.ts +44 -0
- package/src/index.ts +27 -0
- package/test-critical-cases.mjs +258 -0
- package/tsconfig.json +21 -0
- package/vitest.config.mts +12 -0
package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/recording-list.tsx
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Alert, View } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { Button, Icon, List, Text } from '@/components/ui';
|
|
4
|
+
|
|
5
|
+
import { AudioPlayer } from './audio-player';
|
|
6
|
+
|
|
7
|
+
export interface Recording {
|
|
8
|
+
uri: string;
|
|
9
|
+
name: string;
|
|
10
|
+
date: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface RecordingListProps {
|
|
14
|
+
recordings: Recording[];
|
|
15
|
+
onDeleteRecording: (uri: string) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function RecordingList({
|
|
19
|
+
recordings,
|
|
20
|
+
onDeleteRecording,
|
|
21
|
+
}: RecordingListProps) {
|
|
22
|
+
const handleDelete = (uri: string) => {
|
|
23
|
+
Alert.alert(
|
|
24
|
+
'Delete Recording',
|
|
25
|
+
'Are you sure you want to delete this recording?',
|
|
26
|
+
[
|
|
27
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
28
|
+
{
|
|
29
|
+
text: 'Delete',
|
|
30
|
+
style: 'destructive',
|
|
31
|
+
onPress: () => onDeleteRecording(uri),
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const renderItem = ({ item }: { item: Recording }) => (
|
|
38
|
+
<View className="my-1 rounded-lg border border-neutral-300 p-3 dark:border-neutral-800">
|
|
39
|
+
<View className="mb-2 flex-row items-center justify-between">
|
|
40
|
+
<View className="flex-1">
|
|
41
|
+
<Text className="pr-2 text-base font-semibold">{item.name}</Text>
|
|
42
|
+
<Text className="text-xs text-muted">{item.date}</Text>
|
|
43
|
+
</View>
|
|
44
|
+
<Button
|
|
45
|
+
variant="ghost"
|
|
46
|
+
size="icon"
|
|
47
|
+
onPress={() => handleDelete(item.uri)}
|
|
48
|
+
className="size-8"
|
|
49
|
+
>
|
|
50
|
+
<Icon name="trash" size={18} />
|
|
51
|
+
</Button>
|
|
52
|
+
</View>
|
|
53
|
+
<AudioPlayer
|
|
54
|
+
source={{ uri: item.uri }}
|
|
55
|
+
shrinked={true}
|
|
56
|
+
showWaveform={true}
|
|
57
|
+
showTimer={true}
|
|
58
|
+
showProgressBar={false}
|
|
59
|
+
showControls={true}
|
|
60
|
+
style={{ className: 'm-0 p-0 bg-transparent' }}
|
|
61
|
+
// Only shrink waveform, keep normal button sizes
|
|
62
|
+
waveformHeight={50}
|
|
63
|
+
waveformBarCount={45}
|
|
64
|
+
waveformBarWidth={2.5}
|
|
65
|
+
waveformBarGap={1}
|
|
66
|
+
controlButtonSize={20}
|
|
67
|
+
playButtonSize={24}
|
|
68
|
+
/>
|
|
69
|
+
</View>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<View className="flex-1">
|
|
74
|
+
<List
|
|
75
|
+
data={recordings}
|
|
76
|
+
renderItem={renderItem}
|
|
77
|
+
keyExtractor={(item) => item.uri}
|
|
78
|
+
ListEmptyComponent={
|
|
79
|
+
<View className="items-center p-4">
|
|
80
|
+
<Text>No recordings saved.</Text>
|
|
81
|
+
</View>
|
|
82
|
+
}
|
|
83
|
+
contentContainerStyle={{ paddingHorizontal: 8, flexGrow: 1 }}
|
|
84
|
+
showsVerticalScrollIndicator={false}
|
|
85
|
+
nestedScrollEnabled={true}
|
|
86
|
+
/>
|
|
87
|
+
</View>
|
|
88
|
+
);
|
|
89
|
+
}
|
package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/audio-player-demo.tsx
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Switch, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { Text } from '@/components/ui';
|
|
5
|
+
|
|
6
|
+
import { AudioPlayer } from '../components';
|
|
7
|
+
|
|
8
|
+
export function AudioPlayerDemo() {
|
|
9
|
+
const [showControls, setShowControls] = useState(true);
|
|
10
|
+
const [showWaveform, setShowWaveform] = useState(true);
|
|
11
|
+
const [showTimer, setShowTimer] = useState(true);
|
|
12
|
+
const [showProgressBar, setShowProgressBar] = useState(true);
|
|
13
|
+
|
|
14
|
+
const remoteAudioSource = {
|
|
15
|
+
uri: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const renderToggle = (
|
|
19
|
+
label: string,
|
|
20
|
+
value: boolean,
|
|
21
|
+
onValueChange: (value: boolean) => void,
|
|
22
|
+
) => (
|
|
23
|
+
<View
|
|
24
|
+
style={{
|
|
25
|
+
flexDirection: 'row',
|
|
26
|
+
alignItems: 'center',
|
|
27
|
+
justifyContent: 'space-between',
|
|
28
|
+
paddingVertical: 8,
|
|
29
|
+
}}
|
|
30
|
+
>
|
|
31
|
+
<Text>{label}</Text>
|
|
32
|
+
<Switch value={value} onValueChange={onValueChange} />
|
|
33
|
+
</View>
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<View style={{ width: '100%', gap: 16 }}>
|
|
38
|
+
<Text className="text-center text-lg font-bold">Audio Player Demo</Text>
|
|
39
|
+
<AudioPlayer
|
|
40
|
+
source={remoteAudioSource}
|
|
41
|
+
showControls={showControls}
|
|
42
|
+
showWaveform={showWaveform}
|
|
43
|
+
showTimer={showTimer}
|
|
44
|
+
showProgressBar={showProgressBar}
|
|
45
|
+
autoPlay={false}
|
|
46
|
+
onPlaybackStatusUpdate={(status) =>
|
|
47
|
+
console.log('Playback status:', status)
|
|
48
|
+
}
|
|
49
|
+
/>
|
|
50
|
+
<View
|
|
51
|
+
style={{
|
|
52
|
+
padding: 16,
|
|
53
|
+
marginTop: 16,
|
|
54
|
+
backgroundColor: '#f0f0f0',
|
|
55
|
+
borderRadius: 8,
|
|
56
|
+
}}
|
|
57
|
+
>
|
|
58
|
+
<Text className="text-md mb-2 font-bold">Player Options</Text>
|
|
59
|
+
{renderToggle('Show Controls', showControls, setShowControls)}
|
|
60
|
+
{renderToggle('Show Waveform', showWaveform, setShowWaveform)}
|
|
61
|
+
{renderToggle('Show Timer', showTimer, setShowTimer)}
|
|
62
|
+
{renderToggle('Show Progress Bar', showProgressBar, setShowProgressBar)}
|
|
63
|
+
</View>
|
|
64
|
+
</View>
|
|
65
|
+
);
|
|
66
|
+
}
|
package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/audio-recorder-cloud.tsx
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { ActivityIndicator, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { Text } from '@/components/ui';
|
|
5
|
+
|
|
6
|
+
import { AudioRecorder } from '../components/audio-recorder';
|
|
7
|
+
|
|
8
|
+
export function AudioRecorderCloud() {
|
|
9
|
+
const [uploading, setUploading] = useState(false);
|
|
10
|
+
const [uploadProgress, setUploadProgress] = useState(0);
|
|
11
|
+
|
|
12
|
+
const simulateCloudUpload = async (uri: string): Promise<string> => {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
setUploading(true);
|
|
15
|
+
setUploadProgress(0);
|
|
16
|
+
|
|
17
|
+
const interval = setInterval(() => {
|
|
18
|
+
setUploadProgress((prev) => {
|
|
19
|
+
if (prev >= 100) {
|
|
20
|
+
clearInterval(interval);
|
|
21
|
+
setUploading(false);
|
|
22
|
+
resolve(
|
|
23
|
+
`https://cloud-storage.example.com/audio/${Date.now()}.m4a`,
|
|
24
|
+
);
|
|
25
|
+
return 100;
|
|
26
|
+
}
|
|
27
|
+
return prev + 10;
|
|
28
|
+
});
|
|
29
|
+
}, 200);
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const handleRecordingComplete = async (uri: string) => {
|
|
34
|
+
try {
|
|
35
|
+
const cloudUrl = await simulateCloudUpload(uri);
|
|
36
|
+
|
|
37
|
+
console.log(`Recording uploaded to cloud storage!\n\nURL: ${cloudUrl}`);
|
|
38
|
+
|
|
39
|
+
setUploadProgress(0);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.log('Failed to upload recording to cloud storage:', error);
|
|
42
|
+
|
|
43
|
+
setUploading(false);
|
|
44
|
+
setUploadProgress(0);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<View style={{ width: '100%' }}>
|
|
50
|
+
<AudioRecorder
|
|
51
|
+
quality="high"
|
|
52
|
+
showWaveform={true}
|
|
53
|
+
showTimer={true}
|
|
54
|
+
maxDuration={600} // 10 minutes
|
|
55
|
+
onRecordingComplete={handleRecordingComplete}
|
|
56
|
+
/>
|
|
57
|
+
|
|
58
|
+
{uploading && (
|
|
59
|
+
<View style={{ marginTop: 16, alignItems: 'center' }}>
|
|
60
|
+
<ActivityIndicator size="small" />
|
|
61
|
+
<Text className="text-sm" style={{ marginTop: 8 }}>
|
|
62
|
+
Uploading... {uploadProgress}%
|
|
63
|
+
</Text>
|
|
64
|
+
</View>
|
|
65
|
+
)}
|
|
66
|
+
</View>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { RecordingPresets } from 'expo-audio';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { Alert, StyleSheet, View } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import { Text } from '@/components/ui';
|
|
6
|
+
|
|
7
|
+
import { AudioRecorder } from '../components/audio-recorder';
|
|
8
|
+
|
|
9
|
+
export function AudioRecorderInterview() {
|
|
10
|
+
const [interviewTitle, setInterviewTitle] = useState('');
|
|
11
|
+
const [isRecording, setIsRecording] = useState(false);
|
|
12
|
+
|
|
13
|
+
const handleRecordingStart = () => {
|
|
14
|
+
setIsRecording(true);
|
|
15
|
+
const title = `Interview ${new Date().toLocaleDateString()}`;
|
|
16
|
+
setInterviewTitle(title);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const handleRecordingStop = () => {
|
|
20
|
+
setIsRecording(false);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const handleRecordingComplete = (uri: string) => {
|
|
24
|
+
Alert.alert(
|
|
25
|
+
'🎤 Interview Complete',
|
|
26
|
+
`"${interviewTitle}" has been recorded and saved.\n\nDuration: Available in file metadata\nQuality: High (48kHz, Stereo)`,
|
|
27
|
+
[{ text: 'Save & Exit' }],
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<View style={{ width: '100%' }}>
|
|
33
|
+
<View style={styles.interviewHeader}>
|
|
34
|
+
<Text
|
|
35
|
+
className="mt-4 text-xl font-medium"
|
|
36
|
+
style={styles.interviewTitle}
|
|
37
|
+
>
|
|
38
|
+
{interviewTitle || 'Ready for Interview'}
|
|
39
|
+
</Text>
|
|
40
|
+
|
|
41
|
+
<View
|
|
42
|
+
style={[styles.statusBadge, isRecording && styles.recordingBadge]}
|
|
43
|
+
>
|
|
44
|
+
<Text
|
|
45
|
+
style={[styles.statusText, isRecording && styles.recordingText]}
|
|
46
|
+
>
|
|
47
|
+
{isRecording ? '🔴 LIVE' : '⏸️ READY'}
|
|
48
|
+
</Text>
|
|
49
|
+
</View>
|
|
50
|
+
</View>
|
|
51
|
+
|
|
52
|
+
<AudioRecorder
|
|
53
|
+
quality="high"
|
|
54
|
+
showWaveform={true}
|
|
55
|
+
showTimer={true}
|
|
56
|
+
maxDuration={7200} // 2 hours for long interviews
|
|
57
|
+
customRecordingOptions={{
|
|
58
|
+
...RecordingPresets.HIGH_QUALITY,
|
|
59
|
+
sampleRate: 48000,
|
|
60
|
+
bitRate: 128000,
|
|
61
|
+
numberOfChannels: 2,
|
|
62
|
+
isMeteringEnabled: true,
|
|
63
|
+
}}
|
|
64
|
+
onRecordingStart={handleRecordingStart}
|
|
65
|
+
onRecordingStop={handleRecordingStop}
|
|
66
|
+
onRecordingComplete={handleRecordingComplete}
|
|
67
|
+
/>
|
|
68
|
+
|
|
69
|
+
<Text className="text-sm" style={{ marginTop: 12, textAlign: 'center' }}>
|
|
70
|
+
Maximum duration: 2 hours • High quality stereo recording
|
|
71
|
+
</Text>
|
|
72
|
+
</View>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const styles = StyleSheet.create({
|
|
77
|
+
interviewHeader: {
|
|
78
|
+
marginBottom: 16,
|
|
79
|
+
alignItems: 'center',
|
|
80
|
+
},
|
|
81
|
+
interviewTitle: {
|
|
82
|
+
marginBottom: 8,
|
|
83
|
+
textAlign: 'center',
|
|
84
|
+
},
|
|
85
|
+
statusBadge: {
|
|
86
|
+
paddingHorizontal: 12,
|
|
87
|
+
paddingVertical: 4,
|
|
88
|
+
borderRadius: 12,
|
|
89
|
+
backgroundColor: '#f3f4f6',
|
|
90
|
+
},
|
|
91
|
+
recordingBadge: {
|
|
92
|
+
backgroundColor: '#fef2f2',
|
|
93
|
+
},
|
|
94
|
+
statusText: {
|
|
95
|
+
fontSize: 12,
|
|
96
|
+
fontWeight: '600',
|
|
97
|
+
color: '#6b7280',
|
|
98
|
+
},
|
|
99
|
+
recordingText: {
|
|
100
|
+
color: '#dc2626',
|
|
101
|
+
},
|
|
102
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AudioRecorder } from '../components/audio-recorder';
|
|
2
|
+
|
|
3
|
+
export function AudioRecorderDemo() {
|
|
4
|
+
const handleRecordingComplete = (uri: string) => {
|
|
5
|
+
console.log('Recording saved to:', uri);
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const handleRecordingStart = () => {
|
|
9
|
+
console.log('Recording started');
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const handleRecordingStop = () => {
|
|
13
|
+
console.log('Recording stopped');
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<AudioRecorder
|
|
18
|
+
quality="high"
|
|
19
|
+
showWaveform={true}
|
|
20
|
+
showTimer={true}
|
|
21
|
+
maxDuration={300} // 5 minutes
|
|
22
|
+
onRecordingComplete={handleRecordingComplete}
|
|
23
|
+
onRecordingStart={handleRecordingStart}
|
|
24
|
+
onRecordingStop={handleRecordingStop}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { ActivityIndicator, View } from 'react-native';
|
|
3
|
+
import { MMKV } from 'react-native-mmkv';
|
|
4
|
+
|
|
5
|
+
import { AudioRecorder, type Recording, RecordingList } from '../components';
|
|
6
|
+
|
|
7
|
+
const RECORDINGS_STORAGE_KEY = 'audio_recordings';
|
|
8
|
+
const storage = new MMKV();
|
|
9
|
+
|
|
10
|
+
export function RecordingListDemo() {
|
|
11
|
+
const [recordings, setRecordings] = useState<Recording[]>([]);
|
|
12
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
13
|
+
|
|
14
|
+
// Load recordings from storage on component mount
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
loadRecordings();
|
|
17
|
+
}, []);
|
|
18
|
+
|
|
19
|
+
const loadRecordings = () => {
|
|
20
|
+
try {
|
|
21
|
+
const storedRecordings = storage.getString(RECORDINGS_STORAGE_KEY);
|
|
22
|
+
if (storedRecordings) {
|
|
23
|
+
const parsedRecordings = JSON.parse(storedRecordings);
|
|
24
|
+
setRecordings(parsedRecordings);
|
|
25
|
+
}
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('Failed to load recordings:', error);
|
|
28
|
+
} finally {
|
|
29
|
+
setIsLoading(false);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const saveRecordings = (newRecordings: Recording[]) => {
|
|
34
|
+
try {
|
|
35
|
+
storage.set(RECORDINGS_STORAGE_KEY, JSON.stringify(newRecordings));
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('Failed to save recordings:', error);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const handleRecordingComplete = (uri: string) => {
|
|
42
|
+
const newRecording: Recording = {
|
|
43
|
+
uri,
|
|
44
|
+
name: `Recording ${recordings.length + 1}`,
|
|
45
|
+
date: new Date().toLocaleString(),
|
|
46
|
+
};
|
|
47
|
+
const updatedRecordings = [...recordings, newRecording];
|
|
48
|
+
setRecordings(updatedRecordings);
|
|
49
|
+
saveRecordings(updatedRecordings);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const handleDeleteRecording = (uri: string) => {
|
|
53
|
+
const updatedRecordings = recordings.filter((rec) => rec.uri !== uri);
|
|
54
|
+
setRecordings(updatedRecordings);
|
|
55
|
+
saveRecordings(updatedRecordings);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
if (isLoading) {
|
|
59
|
+
return (
|
|
60
|
+
<View
|
|
61
|
+
style={{
|
|
62
|
+
flex: 1,
|
|
63
|
+
width: '100%',
|
|
64
|
+
justifyContent: 'center',
|
|
65
|
+
alignItems: 'center',
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
<ActivityIndicator size="large" />
|
|
69
|
+
</View>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<View style={{ flex: 1, width: '100%', gap: 16 }}>
|
|
75
|
+
<AudioRecorder onRecordingComplete={handleRecordingComplete} />
|
|
76
|
+
<RecordingList
|
|
77
|
+
recordings={recordings}
|
|
78
|
+
onDeleteRecording={handleDeleteRecording}
|
|
79
|
+
/>
|
|
80
|
+
</View>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "audio-recorder",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Record and manage voice notes",
|
|
5
|
+
"copy": [
|
|
6
|
+
{
|
|
7
|
+
"from": "apps/native/src/app/audio-recorder",
|
|
8
|
+
"to": "apps/native/src/app/(root)/(protected)/audio-recorder"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"from": "apps/native/src/features/audio-recorder",
|
|
12
|
+
"to": "apps/native/src/features/audio-recorder"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"nav": {
|
|
16
|
+
"href": "/(root)/(protected)/audio-recorder",
|
|
17
|
+
"label": "Voice Notes",
|
|
18
|
+
"icon": "🎤",
|
|
19
|
+
"color": "#EF4444"
|
|
20
|
+
},
|
|
21
|
+
"target": "native"
|
|
22
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Chart Components
|
|
2
|
+
|
|
3
|
+
A collection of beautiful, animated chart components built with React Native SVG, d3-shape, and React Native Reanimated.
|
|
4
|
+
|
|
5
|
+
## 🚀 Features
|
|
6
|
+
|
|
7
|
+
- **Fully Animated**: Smooth entrance animations with staggered effects
|
|
8
|
+
- **Theme-Aware**: Automatically adapts to light/dark themes using `useThemeConfig`
|
|
9
|
+
- **Responsive**: Horizontal scrolling support for charts that exceed screen width
|
|
10
|
+
- **Accessible**: Proper contrast ratios and accessibility labels
|
|
11
|
+
- **TypeScript**: Fully typed with comprehensive prop interfaces
|
|
12
|
+
- **Customizable**: Extensive styling and behavior options
|
|
13
|
+
|
|
14
|
+
## 📊 Available Charts
|
|
15
|
+
|
|
16
|
+
### BarChart
|
|
17
|
+
|
|
18
|
+
Animated bar charts with horizontal scrolling support.
|
|
19
|
+
|
|
20
|
+
**Features:**
|
|
21
|
+
|
|
22
|
+
- Staggered bar growth animations
|
|
23
|
+
- Grid lines with proper contrast
|
|
24
|
+
- Value labels above bars
|
|
25
|
+
- Category labels below bars
|
|
26
|
+
- Horizontal scrolling for wide datasets
|
|
27
|
+
- Haptic feedback on press
|
|
28
|
+
|
|
29
|
+
**Props:**
|
|
30
|
+
|
|
31
|
+
- `data`: Array of chart data objects with `id`, `label`, `value`, and optional `color`
|
|
32
|
+
- `height`: Chart height (default: 250)
|
|
33
|
+
- `showValues`: Display values above bars (default: true)
|
|
34
|
+
- `showLabels`: Display labels below bars (default: true)
|
|
35
|
+
- `showGrid`: Show grid lines (default: true)
|
|
36
|
+
- `valueFormatter`: Function to format displayed values
|
|
37
|
+
- `barRadius`: Border radius of bars (default: 8)
|
|
38
|
+
- `gap`: Space between bars (default: 12)
|
|
39
|
+
|
|
40
|
+
### LineChart
|
|
41
|
+
|
|
42
|
+
Smooth animated line charts with area fills and data points.
|
|
43
|
+
|
|
44
|
+
**Features:**
|
|
45
|
+
|
|
46
|
+
- Animated path drawing with stroke-dash effects
|
|
47
|
+
- Curved or linear line styles
|
|
48
|
+
- Optional area fills
|
|
49
|
+
- Animated data points
|
|
50
|
+
- Grid overlay support
|
|
51
|
+
|
|
52
|
+
**Props:**
|
|
53
|
+
|
|
54
|
+
- `data`: Array of numeric values
|
|
55
|
+
- `labels`: Array of string labels for x-axis
|
|
56
|
+
- `height`: Chart height (default: 200)
|
|
57
|
+
- `curved`: Use curved lines (default: true)
|
|
58
|
+
- `showArea`: Show area fill (default: true)
|
|
59
|
+
- `showDots`: Show data points (default: true)
|
|
60
|
+
- `valueFormatter`: Function to format displayed values
|
|
61
|
+
|
|
62
|
+
### PieChart
|
|
63
|
+
|
|
64
|
+
Animated pie charts with detailed legends.
|
|
65
|
+
|
|
66
|
+
**Features:**
|
|
67
|
+
|
|
68
|
+
- Slice-by-slice animation with staggered timing
|
|
69
|
+
- Interactive legend with slide-in effects
|
|
70
|
+
- Support for custom colors
|
|
71
|
+
- Touch interactions with haptic feedback
|
|
72
|
+
|
|
73
|
+
**Props:**
|
|
74
|
+
|
|
75
|
+
- `data`: Array of chart data objects
|
|
76
|
+
- `height`: Chart height (default: 300)
|
|
77
|
+
- `radius`: Chart radius (auto-calculated if not provided)
|
|
78
|
+
- `showLegend`: Display legend (default: true)
|
|
79
|
+
- `valueFormatter`: Function to format displayed values
|
|
80
|
+
|
|
81
|
+
### DonutChart
|
|
82
|
+
|
|
83
|
+
Specialized donut charts with center labels.
|
|
84
|
+
|
|
85
|
+
**Features:**
|
|
86
|
+
|
|
87
|
+
- All PieChart features
|
|
88
|
+
- Customizable inner radius ratio
|
|
89
|
+
- Center total display for donut charts
|
|
90
|
+
- Enhanced visual hierarchy
|
|
91
|
+
|
|
92
|
+
**Props:**
|
|
93
|
+
|
|
94
|
+
- All PieChart props except `innerRadius`
|
|
95
|
+
- `innerRadiusRatio`: Inner radius as percentage of outer radius (default: 0.6)
|
|
96
|
+
|
|
97
|
+
## 🎨 Theme Integration
|
|
98
|
+
|
|
99
|
+
All charts use the `useThemeConfig` hook for consistent theming:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// Text colors
|
|
103
|
+
theme.colors.foreground; // Primary text
|
|
104
|
+
theme.colors.mutedForeground; // Secondary text
|
|
105
|
+
|
|
106
|
+
// Background colors
|
|
107
|
+
theme.colors.background; // Chart background
|
|
108
|
+
theme.colors.card; // Card backgrounds
|
|
109
|
+
theme.colors.muted; // Legend item backgrounds
|
|
110
|
+
|
|
111
|
+
// Border colors
|
|
112
|
+
theme.colors.outline; // Grid lines and borders
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## 🔧 Usage Examples
|
|
116
|
+
|
|
117
|
+
### Bar Chart with Horizontal Scrolling
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
<BarChart
|
|
121
|
+
data={salesData}
|
|
122
|
+
height={350}
|
|
123
|
+
showValues={true}
|
|
124
|
+
showGrid={true}
|
|
125
|
+
showLabels={true}
|
|
126
|
+
valueFormatter={(value) => `$${value.toLocaleString()}`}
|
|
127
|
+
/>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Animated Line Chart
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
<LineChart
|
|
134
|
+
data={monthlyActiveUsers}
|
|
135
|
+
labels={monthLabels}
|
|
136
|
+
height={250}
|
|
137
|
+
curved={true}
|
|
138
|
+
showArea={true}
|
|
139
|
+
showDots={true}
|
|
140
|
+
valueFormatter={(value) => value.toLocaleString()}
|
|
141
|
+
/>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Donut Chart with Custom Styling
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
<DonutChart
|
|
148
|
+
data={expensesData}
|
|
149
|
+
height={450}
|
|
150
|
+
radius={130}
|
|
151
|
+
innerRadiusRatio={0.65}
|
|
152
|
+
showLegend={true}
|
|
153
|
+
valueFormatter={(value) => `$${value}k`}
|
|
154
|
+
/>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 🎭 Animation Details
|
|
158
|
+
|
|
159
|
+
- **Bar Charts**: Staggered bar growth with 100ms delays between bars
|
|
160
|
+
- **Line Charts**: Stroke-dash animation followed by area fill and dot appearance
|
|
161
|
+
- **Pie Charts**: Radial growth animation with 150ms delays between slices
|
|
162
|
+
- **Legends**: Slide-in animations with opacity transitions
|
|
163
|
+
|
|
164
|
+
## 📱 Responsive Design
|
|
165
|
+
|
|
166
|
+
Charts automatically adapt to different screen sizes:
|
|
167
|
+
|
|
168
|
+
- Bar charts enable horizontal scrolling when content exceeds screen width
|
|
169
|
+
- Minimum bar widths ensure readability on all devices
|
|
170
|
+
- Dynamic font sizing based on chart dimensions
|
|
171
|
+
- Proper padding and margins for touch interactions
|
|
172
|
+
|
|
173
|
+
## 🔄 Performance Optimizations
|
|
174
|
+
|
|
175
|
+
- React Native Reanimated for smooth 60fps animations
|
|
176
|
+
- Efficient re-renders using `useCallback` and `useMemo`
|
|
177
|
+
- SVG path caching for complex shapes
|
|
178
|
+
- Minimal re-animations on data updates
|
|
179
|
+
|
|
180
|
+
## 🎯 Accessibility
|
|
181
|
+
|
|
182
|
+
- High contrast color schemes for both light and dark themes
|
|
183
|
+
- Proper accessibility labels and roles
|
|
184
|
+
- Touch target sizes meet accessibility guidelines
|
|
185
|
+
- Screen reader friendly value announcements
|