@comergehq/studio 0.1.2 → 0.1.4
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/dist/index.d.mts +2 -10
- package/dist/index.d.ts +2 -10
- package/dist/index.js +293 -264
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +251 -222
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -5
- package/src/components/chat/ChatComposer.tsx +277 -0
- package/src/components/chat/ChatHeader.tsx +31 -0
- package/src/components/chat/ChatMessageBubble.tsx +69 -0
- package/src/components/chat/ChatMessageList.tsx +137 -0
- package/src/components/chat/ChatPage.tsx +69 -0
- package/src/components/chat/ForkNoticeBanner.tsx +66 -0
- package/src/components/chat/MultilineTextInput.tsx +46 -0
- package/src/components/chat/ScrollToBottomButton.tsx +78 -0
- package/src/components/chat/TypingIndicator.tsx +54 -0
- package/src/components/chat/index.ts +28 -0
- package/src/components/comments/AppCommentsSheet.tsx +213 -0
- package/src/components/comments/CommentRow.tsx +63 -0
- package/src/components/comments/formatTimeAgo.ts +3 -0
- package/src/components/comments/index.ts +3 -0
- package/src/components/comments/useAppComments.ts +74 -0
- package/src/components/comments/useAppDetails.ts +35 -0
- package/src/components/comments/useIosKeyboardSnapFix.ts +24 -0
- package/src/components/dialogs/ConfirmMergeRequestDialog.tsx +156 -0
- package/src/components/dialogs/index.ts +4 -0
- package/src/components/draw/DrawColorPicker.tsx +77 -0
- package/src/components/draw/DrawModeOverlay.tsx +144 -0
- package/src/components/draw/DrawSurface.tsx +127 -0
- package/src/components/draw/DrawToolbar.tsx +253 -0
- package/src/components/draw/index.ts +15 -0
- package/src/components/draw/optionalHaptics.ts +15 -0
- package/src/components/draw/strokes.ts +21 -0
- package/src/components/draw/types.ts +9 -0
- package/src/components/floating-draggable-button/FloatingDraggableButton.tsx +323 -0
- package/src/components/floating-draggable-button/constants.ts +17 -0
- package/src/components/floating-draggable-button/index.ts +4 -0
- package/src/components/floating-draggable-button/types.ts +63 -0
- package/src/components/icons/MergeIcon.tsx +14 -0
- package/src/components/icons/StudioIcons.tsx +66 -0
- package/src/components/index.ts +17 -0
- package/src/components/merge-requests/MergeRequestStatusCard.tsx +179 -0
- package/src/components/merge-requests/ReviewMergeRequestActionButton.tsx +62 -0
- package/src/components/merge-requests/ReviewMergeRequestCard.tsx +192 -0
- package/src/components/merge-requests/ReviewMergeRequestCarousel.tsx +132 -0
- package/src/components/merge-requests/index.ts +7 -0
- package/src/components/merge-requests/mergeRequestStatusDisplay.ts +23 -0
- package/src/components/merge-requests/toIsoString.ts +9 -0
- package/src/components/merge-requests/useControlledExpansion.ts +16 -0
- package/src/components/models/index.ts +9 -0
- package/src/components/models/types.ts +43 -0
- package/src/components/overlays/EdgeGlowFrame.tsx +105 -0
- package/src/components/overlays/index.ts +4 -0
- package/src/components/preview/PreviewHeroCard.tsx +58 -0
- package/src/components/preview/PreviewImage.tsx +22 -0
- package/src/components/preview/PreviewMetaRow.tsx +70 -0
- package/src/components/preview/PreviewPage.tsx +36 -0
- package/src/components/preview/PreviewPlaceholder.tsx +72 -0
- package/src/components/preview/PreviewStatusBadge.tsx +63 -0
- package/src/components/preview/StatsBar.tsx +109 -0
- package/src/components/preview/index.ts +22 -0
- package/src/components/primitives/Avatar.tsx +68 -0
- package/src/components/primitives/Button.tsx +102 -0
- package/src/components/primitives/Card.tsx +30 -0
- package/src/components/primitives/Divider.tsx +17 -0
- package/src/components/primitives/Icon.tsx +40 -0
- package/src/components/primitives/MarkdownText.tsx +72 -0
- package/src/components/primitives/Modal.tsx +53 -0
- package/src/components/primitives/Surface.tsx +42 -0
- package/src/components/primitives/Text.tsx +83 -0
- package/src/components/primitives/index.ts +35 -0
- package/src/components/primitives/types.ts +30 -0
- package/src/components/studio-sheet/StudioBottomSheet.tsx +114 -0
- package/src/components/studio-sheet/StudioSheetBackground.tsx +63 -0
- package/src/components/studio-sheet/StudioSheetHeader.tsx +35 -0
- package/src/components/studio-sheet/StudioSheetHeaderIconButton.tsx +109 -0
- package/src/components/studio-sheet/StudioSheetPager.tsx +66 -0
- package/src/components/studio-sheet/index.ts +18 -0
- package/src/components/studio-sheet/types.ts +5 -0
- package/src/components/utils/color.ts +25 -0
- package/src/components/utils/formatTimeAgo.ts +19 -0
- package/src/core/logger.ts +42 -0
- package/src/core/services/http/baseUrl.ts +3 -0
- package/src/core/services/http/index.ts +128 -0
- package/src/core/services/http/public.ts +33 -0
- package/src/core/services/supabase/auth.ts +41 -0
- package/src/core/services/supabase/client.ts +43 -0
- package/src/core/services/supabase/index.ts +7 -0
- package/src/data/agent/remote.ts +30 -0
- package/src/data/agent/repository.ts +34 -0
- package/src/data/agent/types.ts +28 -0
- package/src/data/apps/bundles/remote.ts +47 -0
- package/src/data/apps/bundles/repository.ts +35 -0
- package/src/data/apps/bundles/types.ts +27 -0
- package/src/data/apps/images/remote.ts +61 -0
- package/src/data/apps/images/repository.ts +47 -0
- package/src/data/apps/remote.ts +97 -0
- package/src/data/apps/repository.ts +185 -0
- package/src/data/apps/types.ts +206 -0
- package/src/data/attachment/remote.ts +32 -0
- package/src/data/attachment/repository.ts +40 -0
- package/src/data/attachment/types.ts +42 -0
- package/src/data/base-remote.ts +3 -0
- package/src/data/base-repository.ts +11 -0
- package/src/data/comments/likes/remote.ts +87 -0
- package/src/data/comments/likes/repository.ts +61 -0
- package/src/data/comments/likes/types.ts +47 -0
- package/src/data/comments/remote.ts +71 -0
- package/src/data/comments/repository.ts +53 -0
- package/src/data/comments/types.ts +60 -0
- package/src/data/github/remote.ts +23 -0
- package/src/data/github/repository.ts +35 -0
- package/src/data/github/types.ts +23 -0
- package/src/data/home/remote.ts +24 -0
- package/src/data/home/repository.ts +28 -0
- package/src/data/home/types.ts +70 -0
- package/src/data/index.ts +3 -0
- package/src/data/likes/remote.ts +57 -0
- package/src/data/likes/repository.ts +47 -0
- package/src/data/likes/types.ts +46 -0
- package/src/data/me/remote.ts +28 -0
- package/src/data/me/repository.ts +30 -0
- package/src/data/me/types.ts +14 -0
- package/src/data/merge-requests/remote.ts +76 -0
- package/src/data/merge-requests/repository.ts +66 -0
- package/src/data/merge-requests/types.ts +33 -0
- package/src/data/messages/remote.ts +21 -0
- package/src/data/messages/repository.ts +104 -0
- package/src/data/messages/types.ts +20 -0
- package/src/data/public/studio-config/remote.ts +19 -0
- package/src/data/public/studio-config/repository.ts +23 -0
- package/src/data/public/studio-config/types.ts +6 -0
- package/src/data/ratings/remote.ts +76 -0
- package/src/data/ratings/repository.ts +63 -0
- package/src/data/ratings/types.ts +57 -0
- package/src/data/threads/remote.ts +40 -0
- package/src/data/threads/repository.ts +41 -0
- package/src/data/threads/types.ts +25 -0
- package/src/data/types.ts +8 -0
- package/src/data/users/remote.ts +31 -0
- package/src/data/users/repository.ts +45 -0
- package/src/data/users/types.ts +15 -0
- package/src/index.ts +6 -0
- package/src/studio/ComergeStudio.tsx +239 -0
- package/src/studio/bootstrap/StudioBootstrap.tsx +45 -0
- package/src/studio/bootstrap/useStudioBootstrap.ts +55 -0
- package/src/studio/hooks/useApp.ts +83 -0
- package/src/studio/hooks/useAppStats.ts +111 -0
- package/src/studio/hooks/useAttachmentUpload.ts +59 -0
- package/src/studio/hooks/useBundleManager.ts +389 -0
- package/src/studio/hooks/useMergeRequests.ts +173 -0
- package/src/studio/hooks/useStudioActions.ts +96 -0
- package/src/studio/hooks/useThreadMessages.ts +85 -0
- package/src/studio/lib/chat.ts +34 -0
- package/src/studio/ui/ChatPanel.tsx +154 -0
- package/src/studio/ui/ConfirmMergeFlow.tsx +55 -0
- package/src/studio/ui/PreviewPanel.tsx +131 -0
- package/src/studio/ui/RuntimeRenderer.tsx +40 -0
- package/src/studio/ui/StudioOverlay.tsx +257 -0
- package/src/studio/ui/preview-panel/PressableCardRow.tsx +49 -0
- package/src/studio/ui/preview-panel/PreviewCollaborateSection.tsx +174 -0
- package/src/studio/ui/preview-panel/PreviewCustomizeSection.tsx +160 -0
- package/src/studio/ui/preview-panel/PreviewHeroSection.tsx +56 -0
- package/src/studio/ui/preview-panel/PreviewMetaSection.tsx +67 -0
- package/src/studio/ui/preview-panel/PreviewPanelHeader.tsx +48 -0
- package/src/studio/ui/preview-panel/SectionTitle.tsx +31 -0
- package/src/studio/ui/preview-panel/usePreviewPanelData.ts +132 -0
- package/src/studio/ui/preview-panel/utils.ts +29 -0
- package/src/theme/index.ts +5 -0
- package/src/theme/tokens.ts +118 -0
- package/src/theme/types.ts +90 -0
- package/src/theme/useTheme.ts +11 -0
- package/dist/assets/images/merge.svg +0 -3
- package/dist/merge-72UG27QV.svg +0 -3
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Pressable, View, type ViewStyle } from 'react-native';
|
|
3
|
+
import { LiquidGlassView, isLiquidGlassSupported } from '@callstack/liquid-glass';
|
|
4
|
+
import { Heart, MessageCircle } from 'lucide-react-native';
|
|
5
|
+
|
|
6
|
+
import { useTheme } from '../../theme';
|
|
7
|
+
import { Text } from '../primitives/Text';
|
|
8
|
+
import { MergeIcon } from '../icons/MergeIcon';
|
|
9
|
+
|
|
10
|
+
export type StatsBarProps = {
|
|
11
|
+
likeCount: number;
|
|
12
|
+
commentCount: number;
|
|
13
|
+
forkCount: number;
|
|
14
|
+
isLiked?: boolean;
|
|
15
|
+
onPressLike?: () => void;
|
|
16
|
+
onPressComments?: () => void;
|
|
17
|
+
style?: ViewStyle;
|
|
18
|
+
centered?: boolean;
|
|
19
|
+
fixedWidth?: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function StatsBar({
|
|
23
|
+
likeCount,
|
|
24
|
+
commentCount,
|
|
25
|
+
forkCount,
|
|
26
|
+
isLiked = false,
|
|
27
|
+
onPressLike,
|
|
28
|
+
onPressComments,
|
|
29
|
+
style,
|
|
30
|
+
centered = false,
|
|
31
|
+
fixedWidth,
|
|
32
|
+
}: StatsBarProps) {
|
|
33
|
+
const theme = useTheme();
|
|
34
|
+
const statsBgColor = theme.scheme === 'dark' ? 'rgba(24, 24, 27, 0.5)' : 'rgba(255, 255, 255, 0.5)';
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<View
|
|
38
|
+
style={[
|
|
39
|
+
{ position: 'absolute', bottom: 12, width: '100%', paddingHorizontal: 12 },
|
|
40
|
+
centered && { alignItems: 'center' },
|
|
41
|
+
style,
|
|
42
|
+
]}
|
|
43
|
+
>
|
|
44
|
+
<LiquidGlassView
|
|
45
|
+
style={[
|
|
46
|
+
{ borderRadius: 100, overflow: 'hidden' },
|
|
47
|
+
fixedWidth ? { width: fixedWidth } : undefined,
|
|
48
|
+
!isLiquidGlassSupported && { backgroundColor: statsBgColor },
|
|
49
|
+
]}
|
|
50
|
+
effect="clear"
|
|
51
|
+
>
|
|
52
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 16 }}>
|
|
53
|
+
<Pressable
|
|
54
|
+
disabled={!onPressLike}
|
|
55
|
+
onPress={onPressLike}
|
|
56
|
+
hitSlop={8}
|
|
57
|
+
style={{ paddingVertical: 8 }}
|
|
58
|
+
>
|
|
59
|
+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
60
|
+
<Heart
|
|
61
|
+
size={16}
|
|
62
|
+
strokeWidth={2.5}
|
|
63
|
+
color={isLiked ? theme.colors.danger : '#FFFFFF'}
|
|
64
|
+
fill={isLiked ? theme.colors.danger : 'transparent'}
|
|
65
|
+
/>
|
|
66
|
+
<View style={{ width: 4 }} />
|
|
67
|
+
<Text
|
|
68
|
+
variant="caption"
|
|
69
|
+
style={{
|
|
70
|
+
color: isLiked ? theme.colors.danger : '#FFFFFF',
|
|
71
|
+
fontWeight: theme.typography.fontWeight.bold,
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
{likeCount}
|
|
75
|
+
</Text>
|
|
76
|
+
</View>
|
|
77
|
+
</Pressable>
|
|
78
|
+
|
|
79
|
+
<Pressable
|
|
80
|
+
disabled={!onPressComments}
|
|
81
|
+
onPress={onPressComments}
|
|
82
|
+
hitSlop={8}
|
|
83
|
+
style={{ paddingVertical: 8 }}
|
|
84
|
+
>
|
|
85
|
+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
86
|
+
<MessageCircle size={16} strokeWidth={2.5} color="#FFFFFF" />
|
|
87
|
+
<View style={{ width: 4 }} />
|
|
88
|
+
<Text variant="caption" style={{ color: '#FFFFFF', fontWeight: theme.typography.fontWeight.bold }}>
|
|
89
|
+
{commentCount}
|
|
90
|
+
</Text>
|
|
91
|
+
</View>
|
|
92
|
+
</Pressable>
|
|
93
|
+
|
|
94
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', paddingVertical: 8 }}>
|
|
95
|
+
<View style={{ transform: [{ scaleY: -1 }] }}>
|
|
96
|
+
<MergeIcon width={14} height={14} color="#FFFFFF" />
|
|
97
|
+
</View>
|
|
98
|
+
<View style={{ width: 4 }} />
|
|
99
|
+
<Text variant="caption" style={{ color: '#FFFFFF', fontWeight: theme.typography.fontWeight.bold }}>
|
|
100
|
+
{forkCount}
|
|
101
|
+
</Text>
|
|
102
|
+
</View>
|
|
103
|
+
</View>
|
|
104
|
+
</LiquidGlassView>
|
|
105
|
+
</View>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export { PreviewPage } from './PreviewPage';
|
|
2
|
+
export type { PreviewPageProps } from './PreviewPage';
|
|
3
|
+
|
|
4
|
+
export { PreviewHeroCard } from './PreviewHeroCard';
|
|
5
|
+
export type { PreviewHeroCardProps } from './PreviewHeroCard';
|
|
6
|
+
|
|
7
|
+
export { PreviewImage } from './PreviewImage';
|
|
8
|
+
export type { PreviewImageProps } from './PreviewImage';
|
|
9
|
+
|
|
10
|
+
export { PreviewPlaceholder } from './PreviewPlaceholder';
|
|
11
|
+
export type { PreviewPlaceholderProps } from './PreviewPlaceholder';
|
|
12
|
+
|
|
13
|
+
export { StatsBar } from './StatsBar';
|
|
14
|
+
export type { StatsBarProps } from './StatsBar';
|
|
15
|
+
|
|
16
|
+
export { PreviewMetaRow } from './PreviewMetaRow';
|
|
17
|
+
export type { PreviewMetaRowProps } from './PreviewMetaRow';
|
|
18
|
+
|
|
19
|
+
export { PreviewStatusBadge } from './PreviewStatusBadge';
|
|
20
|
+
export type { PreviewStatusBadgeProps } from './PreviewStatusBadge';
|
|
21
|
+
|
|
22
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Image, View, type ImageStyle, type ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useTheme } from '../../theme';
|
|
5
|
+
import { Text } from './Text';
|
|
6
|
+
import type { WithStyle } from './types';
|
|
7
|
+
|
|
8
|
+
export type AvatarProps = WithStyle<ViewStyle> & {
|
|
9
|
+
size?: number;
|
|
10
|
+
uri?: string | null;
|
|
11
|
+
name?: string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Optional override for the fallback background.
|
|
14
|
+
* Prefer leaving this undefined so it uses theme.
|
|
15
|
+
*/
|
|
16
|
+
fallbackBackgroundColor?: string;
|
|
17
|
+
imageStyle?: WithStyle<ImageStyle>['style'];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function initialsFrom(name?: string | null): string {
|
|
21
|
+
const trimmed = (name ?? '').trim();
|
|
22
|
+
if (!trimmed) return '?';
|
|
23
|
+
return trimmed[0]?.toUpperCase?.() ?? '?';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function Avatar({
|
|
27
|
+
size = 32,
|
|
28
|
+
uri,
|
|
29
|
+
name,
|
|
30
|
+
fallbackBackgroundColor,
|
|
31
|
+
style,
|
|
32
|
+
imageStyle,
|
|
33
|
+
}: AvatarProps) {
|
|
34
|
+
const theme = useTheme();
|
|
35
|
+
const radius = size / 2;
|
|
36
|
+
const fallbackBg = fallbackBackgroundColor ?? theme.colors.neutral;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<View
|
|
40
|
+
style={[
|
|
41
|
+
{
|
|
42
|
+
width: size,
|
|
43
|
+
height: size,
|
|
44
|
+
borderRadius: radius,
|
|
45
|
+
overflow: 'hidden',
|
|
46
|
+
backgroundColor: fallbackBg,
|
|
47
|
+
alignItems: 'center',
|
|
48
|
+
justifyContent: 'center',
|
|
49
|
+
},
|
|
50
|
+
style,
|
|
51
|
+
]}
|
|
52
|
+
>
|
|
53
|
+
{uri ? (
|
|
54
|
+
<Image
|
|
55
|
+
source={{ uri }}
|
|
56
|
+
style={[{ width: size, height: size }, imageStyle]}
|
|
57
|
+
resizeMode="cover"
|
|
58
|
+
/>
|
|
59
|
+
) : (
|
|
60
|
+
<Text variant="caption" style={{ color: theme.colors.onNeutral }}>
|
|
61
|
+
{initialsFrom(name)}
|
|
62
|
+
</Text>
|
|
63
|
+
)}
|
|
64
|
+
</View>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Pressable,
|
|
4
|
+
type PressableProps,
|
|
5
|
+
type ViewStyle,
|
|
6
|
+
} from 'react-native';
|
|
7
|
+
|
|
8
|
+
import { useTheme } from '../../theme';
|
|
9
|
+
import type { ButtonSize, ButtonVariant, PressStateStyle, WithStyle } from './types';
|
|
10
|
+
|
|
11
|
+
export type ButtonProps = Omit<PressableProps, 'style'> &
|
|
12
|
+
WithStyle<ViewStyle> &
|
|
13
|
+
PressStateStyle<ViewStyle> & {
|
|
14
|
+
variant?: ButtonVariant;
|
|
15
|
+
size?: ButtonSize;
|
|
16
|
+
children?: React.ReactNode;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function backgroundFor(
|
|
20
|
+
variant: ButtonVariant,
|
|
21
|
+
theme: ReturnType<typeof useTheme>,
|
|
22
|
+
pressed: boolean,
|
|
23
|
+
disabled?: boolean
|
|
24
|
+
): string {
|
|
25
|
+
const { colors } = theme;
|
|
26
|
+
if (variant === 'ghost') return 'transparent';
|
|
27
|
+
|
|
28
|
+
if (disabled) {
|
|
29
|
+
return colors.neutral;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const base =
|
|
33
|
+
variant === 'primary'
|
|
34
|
+
? colors.primary
|
|
35
|
+
: variant === 'danger'
|
|
36
|
+
? colors.danger
|
|
37
|
+
: colors.neutral;
|
|
38
|
+
|
|
39
|
+
// pressed state is a subtle overlay using borderStrong as tint source
|
|
40
|
+
if (!pressed) return base;
|
|
41
|
+
return base;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function borderFor(variant: ButtonVariant, theme: ReturnType<typeof useTheme>): { borderWidth?: number; borderColor?: string } {
|
|
45
|
+
if (variant !== 'ghost') return {};
|
|
46
|
+
return { borderWidth: 1, borderColor: theme.colors.border };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function paddingFor(size: ButtonSize, theme: ReturnType<typeof useTheme>): { paddingHorizontal: number; paddingVertical: number; minHeight: number; minWidth?: number } {
|
|
50
|
+
switch (size) {
|
|
51
|
+
case 'sm':
|
|
52
|
+
return { paddingHorizontal: theme.spacing.md, paddingVertical: theme.spacing.sm, minHeight: 36 };
|
|
53
|
+
case 'icon':
|
|
54
|
+
return { paddingHorizontal: 0, paddingVertical: 0, minHeight: 44, minWidth: 44 };
|
|
55
|
+
case 'md':
|
|
56
|
+
default:
|
|
57
|
+
return { paddingHorizontal: theme.spacing.lg, paddingVertical: theme.spacing.md, minHeight: 44 };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function Button({
|
|
62
|
+
variant = 'neutral',
|
|
63
|
+
size = 'md',
|
|
64
|
+
disabled,
|
|
65
|
+
style,
|
|
66
|
+
children,
|
|
67
|
+
...props
|
|
68
|
+
}: ButtonProps) {
|
|
69
|
+
const theme = useTheme();
|
|
70
|
+
const isDisabled = disabled ?? undefined;
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Pressable
|
|
74
|
+
{...props}
|
|
75
|
+
disabled={isDisabled}
|
|
76
|
+
style={(state) => {
|
|
77
|
+
const pressed = state.pressed;
|
|
78
|
+
const base: ViewStyle = {
|
|
79
|
+
alignItems: 'center',
|
|
80
|
+
justifyContent: 'center',
|
|
81
|
+
flexDirection: 'row',
|
|
82
|
+
borderRadius: size === 'icon' ? theme.radii.pill : theme.radii.pill,
|
|
83
|
+
backgroundColor: backgroundFor(variant, theme, pressed, isDisabled),
|
|
84
|
+
opacity: pressed && !isDisabled ? 0.92 : 1,
|
|
85
|
+
...paddingFor(size, theme),
|
|
86
|
+
...(borderFor(variant, theme) as ViewStyle),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const resolved =
|
|
90
|
+
typeof style === 'function'
|
|
91
|
+
? style({ pressed, disabled: isDisabled })
|
|
92
|
+
: style;
|
|
93
|
+
|
|
94
|
+
return [base, resolved] as any;
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
{children}
|
|
98
|
+
</Pressable>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type ViewProps, type ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useTheme } from '../../theme';
|
|
5
|
+
import { Surface } from './Surface';
|
|
6
|
+
import type { CardVariant, WithStyle } from './types';
|
|
7
|
+
|
|
8
|
+
export type CardProps = ViewProps &
|
|
9
|
+
WithStyle<ViewStyle> & {
|
|
10
|
+
variant?: CardVariant;
|
|
11
|
+
padded?: boolean;
|
|
12
|
+
border?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function Card({ variant = 'surface', padded = true, border = true, style, ...props }: CardProps) {
|
|
16
|
+
const theme = useTheme();
|
|
17
|
+
const radius = theme.radii.lg;
|
|
18
|
+
const padding = padded ? theme.spacing.lg : 0;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Surface
|
|
22
|
+
{...props}
|
|
23
|
+
variant={variant === 'surfaceRaised' ? 'surfaceRaised' : 'surface'}
|
|
24
|
+
border={border}
|
|
25
|
+
style={[{ borderRadius: radius, padding }, style]}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { View, type ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useTheme } from '../../theme';
|
|
5
|
+
import type { DividerVariant, WithStyle } from './types';
|
|
6
|
+
|
|
7
|
+
export type DividerProps = WithStyle<ViewStyle> & {
|
|
8
|
+
variant?: DividerVariant;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function Divider({ variant = 'default', style }: DividerProps) {
|
|
12
|
+
const theme = useTheme();
|
|
13
|
+
const color = variant === 'subtle' ? theme.colors.border : theme.colors.borderStrong;
|
|
14
|
+
return <View style={[{ height: 1, backgroundColor: color }, style]} />;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { useTheme } from '../../theme';
|
|
4
|
+
import type { IconColorRole } from './types';
|
|
5
|
+
|
|
6
|
+
export type IconProps = {
|
|
7
|
+
/**
|
|
8
|
+
* Any icon component that supports `color` and `size` props (e.g. lucide-react-native).
|
|
9
|
+
*/
|
|
10
|
+
as: React.ComponentType<{ color?: string; size?: number }>;
|
|
11
|
+
size?: number;
|
|
12
|
+
role?: IconColorRole;
|
|
13
|
+
color?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function colorFor(role: IconColorRole, theme: ReturnType<typeof useTheme>): string {
|
|
17
|
+
const { colors } = theme;
|
|
18
|
+
switch (role) {
|
|
19
|
+
case 'muted':
|
|
20
|
+
return colors.textMuted;
|
|
21
|
+
case 'primary':
|
|
22
|
+
return colors.primary;
|
|
23
|
+
case 'danger':
|
|
24
|
+
return colors.danger;
|
|
25
|
+
case 'success':
|
|
26
|
+
return colors.success;
|
|
27
|
+
case 'warning':
|
|
28
|
+
return colors.warning;
|
|
29
|
+
case 'default':
|
|
30
|
+
default:
|
|
31
|
+
return colors.text;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function Icon({ as: Comp, size = 18, role = 'default', color }: IconProps) {
|
|
36
|
+
const theme = useTheme();
|
|
37
|
+
return <Comp size={size} color={color ?? colorFor(role, theme)} />;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Platform, View, type ViewStyle } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import Markdown from 'react-native-markdown-display';
|
|
4
|
+
|
|
5
|
+
import { useTheme } from '../../theme';
|
|
6
|
+
|
|
7
|
+
export type MarkdownTextVariant = 'chat' | 'mergeRequest';
|
|
8
|
+
|
|
9
|
+
export type MarkdownTextProps = {
|
|
10
|
+
markdown: string;
|
|
11
|
+
variant?: MarkdownTextVariant;
|
|
12
|
+
/**
|
|
13
|
+
* Optional override for the base text color (e.g. success/error outcomes in chat).
|
|
14
|
+
*/
|
|
15
|
+
bodyColor?: string;
|
|
16
|
+
style?: ViewStyle;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function MarkdownText({ markdown, variant = 'chat', bodyColor, style }: MarkdownTextProps) {
|
|
20
|
+
const theme = useTheme();
|
|
21
|
+
const isDark = theme.scheme === 'dark';
|
|
22
|
+
|
|
23
|
+
const baseBodyColor = variant === 'mergeRequest' ? theme.colors.textMuted : theme.colors.text;
|
|
24
|
+
const linkColor =
|
|
25
|
+
variant === 'mergeRequest' ? (isDark ? theme.colors.primary : '#3700B3') : theme.colors.link;
|
|
26
|
+
const linkWeight = variant === 'mergeRequest' ? theme.typography.fontWeight.semibold : undefined;
|
|
27
|
+
|
|
28
|
+
const codeBgColor = isDark ? '#27272A' : '#E4E4E7';
|
|
29
|
+
const codeTextColor = isDark ? '#FFFFFF' : theme.colors.text;
|
|
30
|
+
|
|
31
|
+
const paragraphBottom = variant === 'mergeRequest' ? 8 : 6;
|
|
32
|
+
const baseLineHeight = variant === 'mergeRequest' ? 22 : 20;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<View style={style}>
|
|
36
|
+
<Markdown
|
|
37
|
+
style={{
|
|
38
|
+
body: { color: bodyColor ?? baseBodyColor, fontSize: 14, lineHeight: baseLineHeight },
|
|
39
|
+
paragraph: { marginTop: 0, marginBottom: paragraphBottom },
|
|
40
|
+
link: { color: linkColor, fontWeight: linkWeight },
|
|
41
|
+
code_inline: {
|
|
42
|
+
backgroundColor: codeBgColor,
|
|
43
|
+
color: codeTextColor,
|
|
44
|
+
paddingHorizontal: variant === 'mergeRequest' ? 6 : 4,
|
|
45
|
+
paddingVertical: variant === 'mergeRequest' ? 2 : 0,
|
|
46
|
+
borderRadius: variant === 'mergeRequest' ? 6 : 4,
|
|
47
|
+
fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
|
|
48
|
+
fontSize: 13,
|
|
49
|
+
},
|
|
50
|
+
code_block: {
|
|
51
|
+
backgroundColor: codeBgColor,
|
|
52
|
+
color: codeTextColor,
|
|
53
|
+
padding: variant === 'mergeRequest' ? 12 : 8,
|
|
54
|
+
borderRadius: variant === 'mergeRequest' ? 8 : 6,
|
|
55
|
+
marginVertical: variant === 'mergeRequest' ? 8 : 0,
|
|
56
|
+
},
|
|
57
|
+
fence: {
|
|
58
|
+
backgroundColor: codeBgColor,
|
|
59
|
+
color: codeTextColor,
|
|
60
|
+
padding: variant === 'mergeRequest' ? 12 : 8,
|
|
61
|
+
borderRadius: variant === 'mergeRequest' ? 8 : 6,
|
|
62
|
+
marginVertical: variant === 'mergeRequest' ? 8 : 0,
|
|
63
|
+
},
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
{markdown}
|
|
67
|
+
</Markdown>
|
|
68
|
+
</View>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Modal as RNModal,
|
|
4
|
+
Pressable,
|
|
5
|
+
View,
|
|
6
|
+
type ViewStyle,
|
|
7
|
+
} from 'react-native';
|
|
8
|
+
|
|
9
|
+
import { useTheme } from '../../theme';
|
|
10
|
+
import { Card } from './Card';
|
|
11
|
+
|
|
12
|
+
export type ModalProps = {
|
|
13
|
+
visible: boolean;
|
|
14
|
+
onRequestClose: () => void;
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
/**
|
|
17
|
+
* When true, tapping the backdrop closes the modal.
|
|
18
|
+
*/
|
|
19
|
+
dismissOnBackdropPress?: boolean;
|
|
20
|
+
contentStyle?: ViewStyle;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function Modal({
|
|
24
|
+
visible,
|
|
25
|
+
onRequestClose,
|
|
26
|
+
dismissOnBackdropPress = true,
|
|
27
|
+
children,
|
|
28
|
+
contentStyle,
|
|
29
|
+
}: ModalProps) {
|
|
30
|
+
const theme = useTheme();
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<RNModal
|
|
34
|
+
visible={visible}
|
|
35
|
+
transparent
|
|
36
|
+
animationType="fade"
|
|
37
|
+
onRequestClose={onRequestClose}
|
|
38
|
+
>
|
|
39
|
+
<View style={{ flex: 1, backgroundColor: theme.colors.backdrop, justifyContent: 'center', padding: theme.spacing.lg }}>
|
|
40
|
+
<Pressable
|
|
41
|
+
accessibilityRole="button"
|
|
42
|
+
onPress={dismissOnBackdropPress ? onRequestClose : undefined}
|
|
43
|
+
style={{ position: 'absolute', inset: 0 }}
|
|
44
|
+
/>
|
|
45
|
+
<Card variant="surfaceRaised" padded style={[{ borderRadius: theme.radii.xl }, contentStyle]}>
|
|
46
|
+
{children}
|
|
47
|
+
</Card>
|
|
48
|
+
</View>
|
|
49
|
+
</RNModal>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { View, type ViewProps, type ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useTheme } from '../../theme';
|
|
5
|
+
import type { SurfaceVariant, WithStyle } from './types';
|
|
6
|
+
|
|
7
|
+
export type SurfaceProps = ViewProps &
|
|
8
|
+
WithStyle<ViewStyle> & {
|
|
9
|
+
variant?: SurfaceVariant;
|
|
10
|
+
border?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function backgroundFor(variant: SurfaceVariant, theme: ReturnType<typeof useTheme>): string {
|
|
14
|
+
const { colors } = theme;
|
|
15
|
+
switch (variant) {
|
|
16
|
+
case 'background':
|
|
17
|
+
return colors.background;
|
|
18
|
+
case 'surfaceRaised':
|
|
19
|
+
return colors.surfaceRaised;
|
|
20
|
+
case 'floating':
|
|
21
|
+
return colors.floatingSurface;
|
|
22
|
+
case 'surface':
|
|
23
|
+
default:
|
|
24
|
+
return colors.surface;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function Surface({ variant = 'surface', border = false, style, ...props }: SurfaceProps) {
|
|
29
|
+
const theme = useTheme();
|
|
30
|
+
return (
|
|
31
|
+
<View
|
|
32
|
+
{...props}
|
|
33
|
+
style={[
|
|
34
|
+
{ backgroundColor: backgroundFor(variant, theme) },
|
|
35
|
+
border ? { borderWidth: 1, borderColor: theme.colors.border } : null,
|
|
36
|
+
style,
|
|
37
|
+
]}
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Text as RNText, type TextProps as RNTextProps, type TextStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useTheme } from '../../theme';
|
|
5
|
+
import type { TextAlign, TextVariant, WithStyle } from './types';
|
|
6
|
+
|
|
7
|
+
export type TextProps = RNTextProps &
|
|
8
|
+
WithStyle<TextStyle> & {
|
|
9
|
+
variant?: TextVariant;
|
|
10
|
+
align?: TextAlign;
|
|
11
|
+
/**
|
|
12
|
+
* Optional explicit color override. Prefer `variant` + theme.
|
|
13
|
+
* Use sparingly for semantic emphasis.
|
|
14
|
+
*/
|
|
15
|
+
color?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function getVariantStyle(variant: TextVariant, theme: ReturnType<typeof useTheme>): TextStyle {
|
|
19
|
+
const { colors, typography } = theme;
|
|
20
|
+
switch (variant) {
|
|
21
|
+
case 'title':
|
|
22
|
+
return {
|
|
23
|
+
color: colors.text,
|
|
24
|
+
fontSize: typography.fontSize.xl,
|
|
25
|
+
lineHeight: typography.lineHeight.xl,
|
|
26
|
+
fontWeight: typography.fontWeight.semibold,
|
|
27
|
+
};
|
|
28
|
+
case 'caption':
|
|
29
|
+
return {
|
|
30
|
+
color: colors.text,
|
|
31
|
+
fontSize: typography.fontSize.xs,
|
|
32
|
+
lineHeight: typography.lineHeight.xs,
|
|
33
|
+
fontWeight: typography.fontWeight.medium,
|
|
34
|
+
};
|
|
35
|
+
case 'captionMuted':
|
|
36
|
+
return {
|
|
37
|
+
color: colors.textSubtle,
|
|
38
|
+
fontSize: typography.fontSize.xs,
|
|
39
|
+
lineHeight: typography.lineHeight.xs,
|
|
40
|
+
fontWeight: typography.fontWeight.medium,
|
|
41
|
+
};
|
|
42
|
+
case 'bodyMuted':
|
|
43
|
+
return {
|
|
44
|
+
color: colors.textMuted,
|
|
45
|
+
fontSize: typography.fontSize.md,
|
|
46
|
+
lineHeight: typography.lineHeight.md,
|
|
47
|
+
fontWeight: typography.fontWeight.regular,
|
|
48
|
+
};
|
|
49
|
+
case 'body':
|
|
50
|
+
default:
|
|
51
|
+
return {
|
|
52
|
+
color: colors.text,
|
|
53
|
+
fontSize: typography.fontSize.md,
|
|
54
|
+
lineHeight: typography.lineHeight.md,
|
|
55
|
+
fontWeight: typography.fontWeight.regular,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function Text({
|
|
61
|
+
variant = 'body',
|
|
62
|
+
align,
|
|
63
|
+
color,
|
|
64
|
+
style,
|
|
65
|
+
...props
|
|
66
|
+
}: TextProps) {
|
|
67
|
+
const theme = useTheme();
|
|
68
|
+
const base = getVariantStyle(variant, theme);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<RNText
|
|
72
|
+
{...props}
|
|
73
|
+
style={[
|
|
74
|
+
base,
|
|
75
|
+
align ? { textAlign: align } : null,
|
|
76
|
+
color ? { color } : null,
|
|
77
|
+
style,
|
|
78
|
+
]}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export { Text } from './Text';
|
|
2
|
+
export type { TextProps } from './Text';
|
|
3
|
+
|
|
4
|
+
export { Button } from './Button';
|
|
5
|
+
export type { ButtonProps } from './Button';
|
|
6
|
+
|
|
7
|
+
export { Surface } from './Surface';
|
|
8
|
+
export type { SurfaceProps } from './Surface';
|
|
9
|
+
|
|
10
|
+
export { Card } from './Card';
|
|
11
|
+
export type { CardProps } from './Card';
|
|
12
|
+
|
|
13
|
+
export { Avatar } from './Avatar';
|
|
14
|
+
export type { AvatarProps } from './Avatar';
|
|
15
|
+
|
|
16
|
+
export { Divider } from './Divider';
|
|
17
|
+
export type { DividerProps } from './Divider';
|
|
18
|
+
|
|
19
|
+
export { Icon } from './Icon';
|
|
20
|
+
export type { IconProps } from './Icon';
|
|
21
|
+
|
|
22
|
+
export { Modal } from './Modal';
|
|
23
|
+
export type { ModalProps } from './Modal';
|
|
24
|
+
|
|
25
|
+
export type {
|
|
26
|
+
ButtonSize,
|
|
27
|
+
ButtonVariant,
|
|
28
|
+
CardVariant,
|
|
29
|
+
DividerVariant,
|
|
30
|
+
IconColorRole,
|
|
31
|
+
SurfaceVariant,
|
|
32
|
+
TextVariant,
|
|
33
|
+
} from './types';
|
|
34
|
+
|
|
35
|
+
|