@comergehq/studio 0.1.1 → 0.1.3

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 (172) hide show
  1. package/dist/index.js +255 -245
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +213 -203
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +9 -6
  6. package/src/components/chat/ChatComposer.tsx +277 -0
  7. package/src/components/chat/ChatHeader.tsx +31 -0
  8. package/src/components/chat/ChatMessageBubble.tsx +69 -0
  9. package/src/components/chat/ChatMessageList.tsx +137 -0
  10. package/src/components/chat/ChatPage.tsx +69 -0
  11. package/src/components/chat/ForkNoticeBanner.tsx +66 -0
  12. package/src/components/chat/MultilineTextInput.tsx +46 -0
  13. package/src/components/chat/ScrollToBottomButton.tsx +78 -0
  14. package/src/components/chat/TypingIndicator.tsx +54 -0
  15. package/src/components/chat/index.ts +28 -0
  16. package/src/components/comments/AppCommentsSheet.tsx +213 -0
  17. package/src/components/comments/CommentRow.tsx +63 -0
  18. package/src/components/comments/formatTimeAgo.ts +3 -0
  19. package/src/components/comments/index.ts +3 -0
  20. package/src/components/comments/useAppComments.ts +74 -0
  21. package/src/components/comments/useAppDetails.ts +35 -0
  22. package/src/components/comments/useIosKeyboardSnapFix.ts +24 -0
  23. package/src/components/dialogs/ConfirmMergeRequestDialog.tsx +156 -0
  24. package/src/components/dialogs/index.ts +4 -0
  25. package/src/components/draw/DrawColorPicker.tsx +77 -0
  26. package/src/components/draw/DrawModeOverlay.tsx +144 -0
  27. package/src/components/draw/DrawSurface.tsx +127 -0
  28. package/src/components/draw/DrawToolbar.tsx +253 -0
  29. package/src/components/draw/index.ts +15 -0
  30. package/src/components/draw/optionalHaptics.ts +15 -0
  31. package/src/components/draw/strokes.ts +21 -0
  32. package/src/components/draw/types.ts +9 -0
  33. package/src/components/floating-draggable-button/FloatingDraggableButton.tsx +323 -0
  34. package/src/components/floating-draggable-button/constants.ts +17 -0
  35. package/src/components/floating-draggable-button/index.ts +4 -0
  36. package/src/components/floating-draggable-button/types.ts +63 -0
  37. package/src/components/icons/MergeIcon.tsx +14 -0
  38. package/src/components/icons/StudioIcons.tsx +66 -0
  39. package/src/components/index.ts +17 -0
  40. package/src/components/merge-requests/MergeRequestStatusCard.tsx +179 -0
  41. package/src/components/merge-requests/ReviewMergeRequestActionButton.tsx +62 -0
  42. package/src/components/merge-requests/ReviewMergeRequestCard.tsx +192 -0
  43. package/src/components/merge-requests/ReviewMergeRequestCarousel.tsx +132 -0
  44. package/src/components/merge-requests/index.ts +7 -0
  45. package/src/components/merge-requests/mergeRequestStatusDisplay.ts +23 -0
  46. package/src/components/merge-requests/toIsoString.ts +9 -0
  47. package/src/components/merge-requests/useControlledExpansion.ts +16 -0
  48. package/src/components/models/index.ts +9 -0
  49. package/src/components/models/types.ts +43 -0
  50. package/src/components/overlays/EdgeGlowFrame.tsx +105 -0
  51. package/src/components/overlays/index.ts +4 -0
  52. package/src/components/preview/PreviewHeroCard.tsx +58 -0
  53. package/src/components/preview/PreviewImage.tsx +22 -0
  54. package/src/components/preview/PreviewMetaRow.tsx +70 -0
  55. package/src/components/preview/PreviewPage.tsx +36 -0
  56. package/src/components/preview/PreviewPlaceholder.tsx +72 -0
  57. package/src/components/preview/PreviewStatusBadge.tsx +63 -0
  58. package/src/components/preview/StatsBar.tsx +109 -0
  59. package/src/components/preview/index.ts +22 -0
  60. package/src/components/primitives/Avatar.tsx +68 -0
  61. package/src/components/primitives/Button.tsx +102 -0
  62. package/src/components/primitives/Card.tsx +30 -0
  63. package/src/components/primitives/Divider.tsx +17 -0
  64. package/src/components/primitives/Icon.tsx +40 -0
  65. package/src/components/primitives/MarkdownText.tsx +72 -0
  66. package/src/components/primitives/Modal.tsx +53 -0
  67. package/src/components/primitives/Surface.tsx +42 -0
  68. package/src/components/primitives/Text.tsx +83 -0
  69. package/src/components/primitives/index.ts +35 -0
  70. package/src/components/primitives/types.ts +30 -0
  71. package/src/components/studio-sheet/StudioBottomSheet.tsx +114 -0
  72. package/src/components/studio-sheet/StudioSheetBackground.tsx +63 -0
  73. package/src/components/studio-sheet/StudioSheetHeader.tsx +35 -0
  74. package/src/components/studio-sheet/StudioSheetHeaderIconButton.tsx +109 -0
  75. package/src/components/studio-sheet/StudioSheetPager.tsx +66 -0
  76. package/src/components/studio-sheet/index.ts +18 -0
  77. package/src/components/studio-sheet/types.ts +5 -0
  78. package/src/components/utils/color.ts +25 -0
  79. package/src/components/utils/formatTimeAgo.ts +19 -0
  80. package/src/core/logger.ts +42 -0
  81. package/src/core/services/http/baseUrl.ts +3 -0
  82. package/src/core/services/http/index.ts +128 -0
  83. package/src/core/services/http/public.ts +14 -0
  84. package/src/core/services/supabase/auth.ts +41 -0
  85. package/src/core/services/supabase/client.ts +43 -0
  86. package/src/core/services/supabase/index.ts +7 -0
  87. package/src/data/agent/remote.ts +30 -0
  88. package/src/data/agent/repository.ts +34 -0
  89. package/src/data/agent/types.ts +28 -0
  90. package/src/data/apps/bundles/remote.ts +47 -0
  91. package/src/data/apps/bundles/repository.ts +35 -0
  92. package/src/data/apps/bundles/types.ts +27 -0
  93. package/src/data/apps/images/remote.ts +61 -0
  94. package/src/data/apps/images/repository.ts +47 -0
  95. package/src/data/apps/remote.ts +97 -0
  96. package/src/data/apps/repository.ts +185 -0
  97. package/src/data/apps/types.ts +206 -0
  98. package/src/data/attachment/remote.ts +32 -0
  99. package/src/data/attachment/repository.ts +40 -0
  100. package/src/data/attachment/types.ts +42 -0
  101. package/src/data/base-remote.ts +3 -0
  102. package/src/data/base-repository.ts +11 -0
  103. package/src/data/comments/likes/remote.ts +87 -0
  104. package/src/data/comments/likes/repository.ts +61 -0
  105. package/src/data/comments/likes/types.ts +47 -0
  106. package/src/data/comments/remote.ts +71 -0
  107. package/src/data/comments/repository.ts +53 -0
  108. package/src/data/comments/types.ts +60 -0
  109. package/src/data/github/remote.ts +23 -0
  110. package/src/data/github/repository.ts +35 -0
  111. package/src/data/github/types.ts +23 -0
  112. package/src/data/home/remote.ts +24 -0
  113. package/src/data/home/repository.ts +28 -0
  114. package/src/data/home/types.ts +70 -0
  115. package/src/data/index.ts +3 -0
  116. package/src/data/likes/remote.ts +57 -0
  117. package/src/data/likes/repository.ts +47 -0
  118. package/src/data/likes/types.ts +46 -0
  119. package/src/data/me/remote.ts +28 -0
  120. package/src/data/me/repository.ts +30 -0
  121. package/src/data/me/types.ts +14 -0
  122. package/src/data/merge-requests/remote.ts +76 -0
  123. package/src/data/merge-requests/repository.ts +66 -0
  124. package/src/data/merge-requests/types.ts +33 -0
  125. package/src/data/messages/remote.ts +21 -0
  126. package/src/data/messages/repository.ts +104 -0
  127. package/src/data/messages/types.ts +20 -0
  128. package/src/data/public/studio-config/remote.ts +19 -0
  129. package/src/data/public/studio-config/repository.ts +23 -0
  130. package/src/data/public/studio-config/types.ts +6 -0
  131. package/src/data/ratings/remote.ts +76 -0
  132. package/src/data/ratings/repository.ts +63 -0
  133. package/src/data/ratings/types.ts +57 -0
  134. package/src/data/threads/remote.ts +40 -0
  135. package/src/data/threads/repository.ts +41 -0
  136. package/src/data/threads/types.ts +25 -0
  137. package/src/data/types.ts +8 -0
  138. package/src/data/users/remote.ts +31 -0
  139. package/src/data/users/repository.ts +45 -0
  140. package/src/data/users/types.ts +15 -0
  141. package/src/index.ts +6 -0
  142. package/src/studio/ComergeStudio.tsx +246 -0
  143. package/src/studio/bootstrap/StudioBootstrap.tsx +45 -0
  144. package/src/studio/bootstrap/useStudioBootstrap.ts +51 -0
  145. package/src/studio/hooks/useApp.ts +83 -0
  146. package/src/studio/hooks/useAppStats.ts +111 -0
  147. package/src/studio/hooks/useAttachmentUpload.ts +59 -0
  148. package/src/studio/hooks/useBundleManager.ts +389 -0
  149. package/src/studio/hooks/useMergeRequests.ts +173 -0
  150. package/src/studio/hooks/useStudioActions.ts +96 -0
  151. package/src/studio/hooks/useThreadMessages.ts +85 -0
  152. package/src/studio/lib/chat.ts +34 -0
  153. package/src/studio/ui/ChatPanel.tsx +154 -0
  154. package/src/studio/ui/ConfirmMergeFlow.tsx +55 -0
  155. package/src/studio/ui/PreviewPanel.tsx +131 -0
  156. package/src/studio/ui/RuntimeRenderer.tsx +40 -0
  157. package/src/studio/ui/StudioOverlay.tsx +257 -0
  158. package/src/studio/ui/preview-panel/PressableCardRow.tsx +49 -0
  159. package/src/studio/ui/preview-panel/PreviewCollaborateSection.tsx +174 -0
  160. package/src/studio/ui/preview-panel/PreviewCustomizeSection.tsx +160 -0
  161. package/src/studio/ui/preview-panel/PreviewHeroSection.tsx +56 -0
  162. package/src/studio/ui/preview-panel/PreviewMetaSection.tsx +67 -0
  163. package/src/studio/ui/preview-panel/PreviewPanelHeader.tsx +48 -0
  164. package/src/studio/ui/preview-panel/SectionTitle.tsx +31 -0
  165. package/src/studio/ui/preview-panel/usePreviewPanelData.ts +132 -0
  166. package/src/studio/ui/preview-panel/utils.ts +29 -0
  167. package/src/theme/index.ts +5 -0
  168. package/src/theme/tokens.ts +118 -0
  169. package/src/theme/types.ts +90 -0
  170. package/src/theme/useTheme.ts +11 -0
  171. package/dist/assets/images/merge.svg +0 -3
  172. package/dist/merge-72UG27QV.svg +0 -3
@@ -0,0 +1,67 @@
1
+ import * as React from 'react';
2
+ import { View } from 'react-native';
3
+
4
+ import type { App } from '../../../data/apps/types';
5
+ import { PreviewMetaRow } from '../../../components/preview/PreviewMetaRow';
6
+ import { Text } from '../../../components/primitives/Text';
7
+ import { IconPlay } from '../../../components/icons/StudioIcons';
8
+ import { withAlpha } from '../../../components/utils/color';
9
+ import { useTheme } from '../../../theme';
10
+ import { formatCount } from './utils';
11
+
12
+ export type PreviewMetaSectionProps = {
13
+ app: App;
14
+ isOwner: boolean;
15
+ creator: { name: string | null; avatar: string | null } | null;
16
+ downloadsCount?: number;
17
+ };
18
+
19
+ export function PreviewMetaSection({ app, isOwner, creator, downloadsCount }: PreviewMetaSectionProps) {
20
+ const theme = useTheme();
21
+
22
+ return (
23
+ <PreviewMetaRow
24
+ title={app.name}
25
+ subtitle={app.description}
26
+ avatarUri={creator?.avatar ?? null}
27
+ creatorName={creator?.name ?? null}
28
+ tag={
29
+ isOwner || app.forkedFromAppId ? (
30
+ <View style={{ paddingHorizontal: 8, paddingVertical: 2, borderRadius: 999, backgroundColor: '#3700B3' }}>
31
+ <Text variant="caption" style={{ color: '#fff', fontWeight: theme.typography.fontWeight.semibold }}>
32
+ {app.forkedFromAppId ? 'Remix' : 'Owner'}
33
+ </Text>
34
+ </View>
35
+ ) : null
36
+ }
37
+ rightMetric={
38
+ <View
39
+ style={{
40
+ flexDirection: 'row',
41
+ alignItems: 'center',
42
+ paddingHorizontal: 6,
43
+ paddingVertical: 2,
44
+ borderRadius: 6,
45
+ backgroundColor: withAlpha(theme.colors.neutral, 0.3),
46
+ }}
47
+ >
48
+ <Text
49
+ style={{
50
+ marginRight: 2,
51
+ color: theme.colors.textMuted,
52
+ fontSize: 14,
53
+ lineHeight: 18,
54
+ fontWeight: theme.typography.fontWeight.bold,
55
+ }}
56
+ >
57
+ {formatCount(downloadsCount ?? app.insights?.totalDownloads ?? 0)}
58
+ </Text>
59
+ <IconPlay size={14} colorToken="textMuted" fill={theme.colors.textMuted} />
60
+ </View>
61
+ }
62
+ style={{ marginBottom: 16 }}
63
+ />
64
+ );
65
+ }
66
+
67
+
@@ -0,0 +1,48 @@
1
+ import * as React from 'react';
2
+ import { View } from 'react-native';
3
+
4
+ import { StudioSheetHeader } from '../../../components/studio-sheet/StudioSheetHeader';
5
+ import { StudioSheetHeaderIconButton } from '../../../components/studio-sheet/StudioSheetHeaderIconButton';
6
+ import { IconChat, IconClose, IconHome } from '../../../components/icons/StudioIcons';
7
+
8
+ export type PreviewPanelHeaderProps = {
9
+ isOwner: boolean;
10
+ onClose: () => void;
11
+ onNavigateHome?: () => void;
12
+ onGoToChat: () => void;
13
+ };
14
+
15
+ export function PreviewPanelHeader({ isOwner, onClose, onNavigateHome, onGoToChat }: PreviewPanelHeaderProps) {
16
+ return (
17
+ <StudioSheetHeader
18
+ left={
19
+ onNavigateHome ? (
20
+ <StudioSheetHeaderIconButton onPress={onNavigateHome} accessibilityLabel="Home" appearance="glass" intent="primary">
21
+ <IconHome size={20} colorToken="onPrimary" />
22
+ </StudioSheetHeaderIconButton>
23
+ ) : null
24
+ }
25
+ center={null}
26
+ right={
27
+ <View style={{ flexDirection: 'row', alignItems: 'center' }}>
28
+ {isOwner ? (
29
+ <StudioSheetHeaderIconButton
30
+ onPress={onGoToChat}
31
+ accessibilityLabel="Chat"
32
+ intent="primary"
33
+ appearance="glass"
34
+ style={{ marginRight: 8 }}
35
+ >
36
+ <IconChat size={20} colorToken="onPrimary" />
37
+ </StudioSheetHeaderIconButton>
38
+ ) : null}
39
+ <StudioSheetHeaderIconButton onPress={onClose} accessibilityLabel="Close" appearance="glass" intent="primary">
40
+ <IconClose size={20} colorToken="onPrimary" />
41
+ </StudioSheetHeaderIconButton>
42
+ </View>
43
+ }
44
+ />
45
+ );
46
+ }
47
+
48
+
@@ -0,0 +1,31 @@
1
+ import * as React from 'react';
2
+
3
+ import { Text } from '../../../components/primitives/Text';
4
+ import { useTheme } from '../../../theme';
5
+
6
+ export type SectionTitleProps = {
7
+ children: string;
8
+ marginTop?: number;
9
+ };
10
+
11
+ export function SectionTitle({ children, marginTop }: SectionTitleProps) {
12
+ const theme = useTheme();
13
+ return (
14
+ <Text
15
+ style={{
16
+ color: theme.colors.textMuted,
17
+ fontSize: 12,
18
+ lineHeight: 16,
19
+ textTransform: 'uppercase',
20
+ letterSpacing: 0.8,
21
+ marginTop: marginTop ?? theme.spacing.sm,
22
+ marginBottom: theme.spacing.sm,
23
+ fontWeight: theme.typography.fontWeight.bold,
24
+ }}
25
+ >
26
+ {children}
27
+ </Text>
28
+ );
29
+ }
30
+
31
+
@@ -0,0 +1,132 @@
1
+ import * as React from 'react';
2
+
3
+ import type { App } from '../../../data/apps/types';
4
+ import type { MergeRequest } from '../../../data/merge-requests/types';
5
+ import { appImagesRepository } from '../../../data/apps/images/repository';
6
+ import { appsRepository } from '../../../data/apps/repository';
7
+ import { usersRepository } from '../../../data/users/repository';
8
+ import { log } from '../../../core/logger';
9
+ import { useAppStats } from '../../hooks/useAppStats';
10
+
11
+ type InsightsSummary = { likes: number; comments: number; forks: number; downloads: number };
12
+
13
+ const LIKE_DEBUG_PREFIX = '[COMERGE_LIKE_DEBUG]';
14
+
15
+ export function usePreviewPanelData(params: {
16
+ app: App | null;
17
+ isOwner: boolean;
18
+ outgoingMergeRequests: MergeRequest[];
19
+ onOpenComments?: () => void;
20
+ commentCountOverride?: number;
21
+ }) {
22
+ const { app, isOwner, outgoingMergeRequests, onOpenComments, commentCountOverride } = params;
23
+
24
+ const [imageUrl, setImageUrl] = React.useState<string | null>(null);
25
+ const [imageLoaded, setImageLoaded] = React.useState(false);
26
+ const [insights, setInsights] = React.useState<InsightsSummary>({ likes: 0, comments: 0, forks: 0, downloads: 0 });
27
+ const [creator, setCreator] = React.useState<{ name: string | null; avatar: string | null } | null>(null);
28
+
29
+ React.useEffect(() => {
30
+ if (!app?.id) return;
31
+ let cancelled = false;
32
+ (async () => {
33
+ try {
34
+ const res = await appImagesRepository.getSignedUrl(app.id);
35
+ if (!cancelled) setImageUrl(res.url);
36
+ } catch {
37
+ if (!cancelled) setImageUrl(null);
38
+ }
39
+ })();
40
+ return () => {
41
+ cancelled = true;
42
+ };
43
+ }, [app?.id]);
44
+
45
+ React.useEffect(() => {
46
+ if (!app?.createdBy) return;
47
+ let cancelled = false;
48
+ (async () => {
49
+ try {
50
+ const stats = await usersRepository.getStats(app.createdBy);
51
+ if (cancelled) return;
52
+ setCreator({ name: stats.name, avatar: stats.avatar });
53
+ } catch {
54
+ if (!cancelled) setCreator(null);
55
+ }
56
+ })();
57
+ return () => {
58
+ cancelled = true;
59
+ };
60
+ }, [app?.createdBy]);
61
+
62
+ React.useEffect(() => {
63
+ setImageLoaded(false);
64
+ }, [app?.id]);
65
+
66
+ React.useEffect(() => {
67
+ if (!app?.id) return;
68
+ let cancelled = false;
69
+ (async () => {
70
+ try {
71
+ const full = await appsRepository.getInsights(app.id);
72
+ if (cancelled) return;
73
+ log.debug(
74
+ `${LIKE_DEBUG_PREFIX} usePreviewPanelData.getInsights appId=${app.id} insights.likes.total=${full.likes.total} app.isLiked=${String(
75
+ app.isLiked
76
+ )}`
77
+ );
78
+ setInsights({
79
+ likes: full.likes.total,
80
+ comments: full.comments.total,
81
+ forks: full.forks.total,
82
+ downloads: full.downloads.total,
83
+ });
84
+ } catch {
85
+ // Leave zeros
86
+ }
87
+ })();
88
+ return () => {
89
+ cancelled = true;
90
+ };
91
+ }, [app?.id]);
92
+
93
+ React.useEffect(() => {
94
+ if (!app?.id) return;
95
+ log.debug(
96
+ `${LIKE_DEBUG_PREFIX} usePreviewPanelData.appChanged appId=${app.id} app.isLiked=${String(app.isLiked)}`
97
+ );
98
+ }, [app?.id, app?.isLiked]);
99
+
100
+ const stats = useAppStats({
101
+ appId: app?.id ?? '',
102
+ initialLikes: insights.likes,
103
+ initialForks: insights.forks,
104
+ initialComments: commentCountOverride ?? insights.comments,
105
+ initialIsLiked: Boolean(app?.isLiked),
106
+ onOpenComments,
107
+ });
108
+
109
+ const canSubmitMergeRequest = React.useMemo(() => {
110
+ if (!isOwner) return false;
111
+ if (!app) return false;
112
+ if (!app.forkedFromAppId) return false;
113
+ if (outgoingMergeRequests.some((mr) => mr.status === 'open')) return false;
114
+ if (app.headCommitId && app.forkedFromCommitId && app.headCommitId !== app.forkedFromCommitId) return true;
115
+ return false;
116
+ }, [app, isOwner, outgoingMergeRequests]);
117
+
118
+ const showProcessing = app ? app.status !== 'ready' : false;
119
+
120
+ return {
121
+ imageUrl,
122
+ imageLoaded,
123
+ setImageLoaded,
124
+ creator,
125
+ insights,
126
+ stats,
127
+ showProcessing,
128
+ canSubmitMergeRequest,
129
+ };
130
+ }
131
+
132
+
@@ -0,0 +1,29 @@
1
+ import type { App } from '../../../data/apps/types';
2
+
3
+ export function formatCount(n: number): string {
4
+ if (n < 10_000) return n.toLocaleString();
5
+ if (n < 1_000_000) return `${Math.floor(n / 1_000)}k`;
6
+ return `${Math.floor(n / 1_000_000)}m`;
7
+ }
8
+
9
+ export function statusDescription(status: App['status'], statusError: string | null): string {
10
+ switch (status) {
11
+ case 'editing':
12
+ return 'Changes are being applied';
13
+ case 'creating':
14
+ return 'Your app is being created';
15
+ case 'forking':
16
+ return 'Creating your copy';
17
+ case 'merging':
18
+ return 'Merging changes from contributor';
19
+ case 'archived':
20
+ return 'This app has been archived';
21
+ case 'error':
22
+ return statusError ?? 'Something went wrong';
23
+ case 'ready':
24
+ default:
25
+ return '';
26
+ }
27
+ }
28
+
29
+
@@ -0,0 +1,5 @@
1
+ export { useTheme } from './useTheme';
2
+ export { themes, lightTheme, darkTheme } from './tokens';
3
+ export type { Theme, ThemeColors, ThemeScheme } from './types';
4
+
5
+
@@ -0,0 +1,118 @@
1
+ import type { Theme } from './types';
2
+
3
+ export const lightTheme = {
4
+ scheme: 'light',
5
+ colors: {
6
+ text: '#09090B',
7
+ textMuted: '#898994',
8
+ textSubtle: 'rgba(137, 137, 148, 0.70)',
9
+
10
+ background: '#FFFFFF',
11
+ surface: '#F6F6F6',
12
+ surfaceRaised: '#FFFFFF',
13
+
14
+ border: '#E4E4E7',
15
+ borderStrong: '#D4D4D8',
16
+
17
+ primary: '#6200EE',
18
+ onPrimary: '#FFFFFF',
19
+
20
+ neutral: '#ECECEE',
21
+ onNeutral: '#09090B',
22
+
23
+ danger: '#F43F5E',
24
+ onDanger: '#FFFFFF',
25
+ dangerSubtle: 'rgba(244, 63, 94, 0.12)',
26
+
27
+ success: '#10B981',
28
+ onSuccess: '#FFFFFF',
29
+ successSubtle: 'rgba(16, 185, 129, 0.12)',
30
+
31
+ warning: '#FACC15',
32
+ onWarning: '#09090B',
33
+ warningSubtle: 'rgba(250, 204, 21, 0.14)',
34
+
35
+ link: '#6200EE',
36
+
37
+ backdrop: 'rgba(0, 0, 0, 0.35)',
38
+
39
+ handleIndicator: '#71717A',
40
+
41
+ floatingSurface: 'rgba(255, 255, 255, 0.6)',
42
+ floatingContent: '#000000',
43
+
44
+ accentRingFrom: 'rgba(98, 0, 238, 0.20)',
45
+ accentRingTo: 'rgba(98, 0, 238, 0.90)',
46
+ dangerRingFrom: 'rgba(244, 63, 94, 0.35)',
47
+ dangerRingTo: 'rgba(244, 63, 94, 1.0)',
48
+ },
49
+ spacing: { xs: 4, sm: 8, md: 12, lg: 16, xl: 24 },
50
+ radii: { sm: 8, md: 12, lg: 16, xl: 24, pill: 999 },
51
+ typography: {
52
+ fontSize: { xs: 12, sm: 13, md: 15, lg: 17, xl: 20 },
53
+ lineHeight: { xs: 16, sm: 18, md: 20, lg: 22, xl: 26 },
54
+ fontWeight: { regular: '400', medium: '500', semibold: '600', bold: '700' },
55
+ },
56
+ } as const satisfies Theme;
57
+
58
+ export const darkTheme = {
59
+ scheme: 'dark',
60
+ colors: {
61
+ text: '#FFFFFF',
62
+ textMuted: '#A1A1AA',
63
+ textSubtle: 'rgba(161, 161, 170, 0.70)',
64
+
65
+ background: '#0B080F',
66
+ surface: '#18181B',
67
+ surfaceRaised: '#0B080F',
68
+
69
+ border: '#404049',
70
+ borderStrong: '#52525B',
71
+
72
+ primary: '#6200EE',
73
+ onPrimary: '#FFFFFF',
74
+
75
+ neutral: '#0B080F',
76
+ onNeutral: '#FFFFFF',
77
+
78
+ danger: '#F43F5E',
79
+ onDanger: '#FFFFFF',
80
+ dangerSubtle: 'rgba(244, 63, 94, 0.18)',
81
+
82
+ success: '#10B981',
83
+ onSuccess: '#0B080F',
84
+ successSubtle: 'rgba(16, 185, 129, 0.16)',
85
+
86
+ warning: '#FBBF24',
87
+ onWarning: '#0B080F',
88
+ warningSubtle: 'rgba(251, 191, 36, 0.18)',
89
+
90
+ link: '#6200EE',
91
+
92
+ backdrop: 'rgba(0, 0, 0, 0.55)',
93
+
94
+ handleIndicator: '#A1A1AA',
95
+
96
+ floatingSurface: 'rgba(0, 0, 0, 0.6)',
97
+ floatingContent: '#FFFFFF',
98
+
99
+ accentRingFrom: 'rgba(98, 0, 238, 0.20)',
100
+ accentRingTo: 'rgba(98, 0, 238, 0.90)',
101
+ dangerRingFrom: 'rgba(244, 63, 94, 0.35)',
102
+ dangerRingTo: 'rgba(244, 63, 94, 1.0)',
103
+ },
104
+ spacing: { xs: 4, sm: 8, md: 12, lg: 16, xl: 24 },
105
+ radii: { sm: 8, md: 12, lg: 16, xl: 24, pill: 999 },
106
+ typography: {
107
+ fontSize: { xs: 12, sm: 13, md: 15, lg: 17, xl: 20 },
108
+ lineHeight: { xs: 16, sm: 18, md: 20, lg: 22, xl: 26 },
109
+ fontWeight: { regular: '400', medium: '500', semibold: '600', bold: '700' },
110
+ },
111
+ } as const satisfies Theme;
112
+
113
+ export const themes = {
114
+ light: lightTheme,
115
+ dark: darkTheme,
116
+ } as const;
117
+
118
+
@@ -0,0 +1,90 @@
1
+ export type ThemeScheme = 'light' | 'dark';
2
+
3
+
4
+ export type ThemeColors = {
5
+ text: string;
6
+ textMuted: string;
7
+ textSubtle: string;
8
+
9
+ background: string;
10
+ surface: string;
11
+ surfaceRaised: string;
12
+
13
+ border: string;
14
+ borderStrong: string;
15
+
16
+ primary: string;
17
+ onPrimary: string;
18
+
19
+ neutral: string;
20
+ onNeutral: string;
21
+
22
+ danger: string;
23
+ onDanger: string;
24
+ dangerSubtle: string;
25
+
26
+ success: string;
27
+ onSuccess: string;
28
+ successSubtle: string;
29
+
30
+ warning: string;
31
+ onWarning: string;
32
+ warningSubtle: string;
33
+
34
+ link: string;
35
+
36
+ backdrop: string;
37
+
38
+ handleIndicator: string;
39
+
40
+ floatingSurface: string;
41
+ floatingContent: string;
42
+
43
+ accentRingFrom: string;
44
+ accentRingTo: string;
45
+ dangerRingFrom: string;
46
+ dangerRingTo: string;
47
+ };
48
+
49
+ export type Theme = {
50
+ scheme: ThemeScheme;
51
+ colors: ThemeColors;
52
+ spacing: {
53
+ xs: number;
54
+ sm: number;
55
+ md: number;
56
+ lg: number;
57
+ xl: number;
58
+ };
59
+ radii: {
60
+ sm: number;
61
+ md: number;
62
+ lg: number;
63
+ xl: number;
64
+ pill: number;
65
+ };
66
+ typography: {
67
+ fontSize: {
68
+ xs: number;
69
+ sm: number;
70
+ md: number;
71
+ lg: number;
72
+ xl: number;
73
+ };
74
+ lineHeight: {
75
+ xs: number;
76
+ sm: number;
77
+ md: number;
78
+ lg: number;
79
+ xl: number;
80
+ };
81
+ fontWeight: {
82
+ regular: '400' | '500';
83
+ medium: '500' | '600';
84
+ semibold: '600' | '700';
85
+ bold: '700';
86
+ };
87
+ };
88
+ };
89
+
90
+
@@ -0,0 +1,11 @@
1
+ import { useColorScheme } from 'react-native';
2
+
3
+ import { themes } from './tokens';
4
+ import type { Theme, ThemeScheme } from './types';
5
+
6
+ export function useTheme(): Theme {
7
+ const scheme = (useColorScheme() ?? 'light') as ThemeScheme;
8
+ return themes[scheme] ?? themes.light;
9
+ }
10
+
11
+
@@ -1,3 +0,0 @@
1
- <svg width="486" height="486" viewBox="0 0 486 486" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <path d="M237.025 0H243.664C254.876 95.0361 275.236 175.597 304.743 241.684C334.249 307.478 367.002 357.774 403 392.572L389.722 486C361.691 458.22 338.233 429.417 319.349 399.59C300.464 369.764 284.531 335.843 271.548 297.829C258.565 259.522 246.615 214.343 235.697 162.292L237.91 161.415C228.468 214.928 217.993 261.569 206.485 301.338C194.978 341.107 179.634 375.904 160.455 405.731C141.571 435.265 115.752 462.022 83 486L96.278 392.572C124.014 369.179 147.62 336.72 167.094 295.197C186.864 253.381 202.65 206.886 214.452 155.713C226.255 104.247 233.779 52.343 237.025 0Z" fill="currentColor"/>
3
- </svg>
@@ -1,3 +0,0 @@
1
- <svg width="486" height="486" viewBox="0 0 486 486" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <path d="M237.025 0H243.664C254.876 95.0361 275.236 175.597 304.743 241.684C334.249 307.478 367.002 357.774 403 392.572L389.722 486C361.691 458.22 338.233 429.417 319.349 399.59C300.464 369.764 284.531 335.843 271.548 297.829C258.565 259.522 246.615 214.343 235.697 162.292L237.91 161.415C228.468 214.928 217.993 261.569 206.485 301.338C194.978 341.107 179.634 375.904 160.455 405.731C141.571 435.265 115.752 462.022 83 486L96.278 392.572C124.014 369.179 147.62 336.72 167.094 295.197C186.864 253.381 202.65 206.886 214.452 155.713C226.255 104.247 233.779 52.343 237.025 0Z" fill="currentColor"/>
3
- </svg>