@vvedantb/native-ui 0.12.1 → 0.17.0
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/README.md +82 -19
- package/package.json +36 -3
- package/src/BottomSheetContainer.tsx +2 -2
- package/src/BulkActionBar.tsx +240 -105
- package/src/Button.tsx +11 -7
- package/src/ConfirmDialog.tsx +25 -35
- package/src/Dialog.tsx +47 -35
- package/src/DividedList.tsx +11 -7
- package/src/DropdownMenu.tsx +22 -8
- package/src/FloatingSurface.tsx +5 -2
- package/src/FloatingTabBar.tsx +13 -22
- package/src/HeaderIconButton.tsx +4 -3
- package/src/Input.tsx +2 -2
- package/src/LoadingState.tsx +132 -0
- package/src/PageTitle.tsx +9 -2
- package/src/Pager.tsx +62 -0
- package/src/PasswordInput.tsx +1 -1
- package/src/PillTabs.tsx +144 -34
- package/src/Progress.tsx +9 -7
- package/src/Select.tsx +4 -4
- package/src/Sheet.tsx +29 -22
- package/src/SheetRow.tsx +27 -9
- package/src/Skeleton.tsx +91 -9
- package/src/SurfaceRow.tsx +111 -0
- package/src/SwipeableRow.tsx +43 -0
- package/src/Switch.tsx +6 -4
- package/src/Tabs.tsx +68 -13
- package/src/Toast.tsx +107 -65
- package/src/ai/activity-step-detail.tsx +1 -3
- package/src/ai/activity-step-label.ts +981 -0
- package/src/ai/activity-step-types.ts +107 -0
- package/src/ai/activity-tasks-utils.ts +199 -0
- package/src/ai/activity-timeline.ts +45 -0
- package/src/ai/attachments.tsx +3 -1
- package/src/ai/card-button.tsx +49 -0
- package/src/ai/chat-text.ts +13 -7
- package/src/ai/chat-thread.tsx +34 -27
- package/src/ai/choice-question.tsx +154 -35
- package/src/ai/code-block.tsx +189 -49
- package/src/ai/collapsible.tsx +19 -13
- package/src/ai/conversation.tsx +390 -57
- package/src/ai/icon-button.tsx +41 -6
- package/src/ai/index.ts +52 -1
- package/src/ai/message-action-sheet.tsx +168 -0
- package/src/ai/message-quote.tsx +98 -0
- package/src/ai/message.tsx +226 -31
- package/src/ai/prompt-input-menu.tsx +197 -0
- package/src/ai/prompt-input.tsx +308 -73
- package/src/ai/reply-quote-chip.tsx +90 -0
- package/src/ai/shimmer.tsx +4 -6
- package/src/ai/subagent-cards.tsx +209 -0
- package/src/ai/tool-approval-card.tsx +162 -0
- package/src/ai/trace.tsx +54 -17
- package/src/ai/unified-diff.tsx +182 -0
- package/src/auth/GoogleSignInButton.tsx +6 -3
- package/src/auth/SignInScreen.tsx +11 -2
- package/src/brand/ProviderIcon.tsx +136 -0
- package/src/brand/index.ts +2 -0
- package/src/configureTheme.ts +39 -11
- package/src/controlStyles.ts +46 -30
- package/src/entries/gesture-handler.ts +4 -0
- package/src/entries/keyboard-controller.ts +4 -0
- package/src/entries/markdown.ts +1 -1
- package/src/entries/primitives.ts +24 -3
- package/src/entries/theme.ts +2 -1
- package/src/entries/video.ts +4 -0
- package/src/entries/webview.ts +6 -0
- package/src/floating.ts +25 -4
- package/src/haptics.ts +30 -4
- package/src/highlight.ts +302 -1
- package/src/index.ts +27 -5
- package/src/loadingGrid.ts +75 -0
- package/src/markdown/Markdown.tsx +234 -32
- package/src/markdown/MarkdownTable.tsx +188 -0
- package/src/markdown/tableText.ts +58 -0
- package/src/marketing/UpgradeToProCard.tsx +4 -3
- package/src/media/ImageGallery.tsx +224 -0
- package/src/media/MediaCard.tsx +100 -0
- package/src/media/imageGrid.ts +51 -0
- package/src/motion.ts +33 -0
- package/src/pagerSwipe.ts +102 -0
- package/src/palette.ts +8 -1
- package/src/primitives/button.tsx +4 -1
- package/src/primitives/keyboard-controller.tsx +106 -0
- package/src/primitives/keyboard-parts.tsx +104 -0
- package/src/primitives/overlay.tsx +133 -45
- package/src/primitives/pager-gesture.tsx +131 -0
- package/src/primitives/sheet-drag.tsx +305 -0
- package/src/primitives/sheet-gesture.tsx +181 -0
- package/src/primitives/swipe-row-gesture.tsx +290 -0
- package/src/primitives/toast-drag-gesture.tsx +92 -0
- package/src/primitives/web-click-guard.ts +50 -0
- package/src/relativeTime.ts +94 -0
- package/src/router/RouterStackPageHeader.tsx +2 -2
- package/src/settings/SettingsEmptyState.tsx +20 -10
- package/src/settings/SettingsField.tsx +11 -5
- package/src/settings/SettingsSection.tsx +8 -5
- package/src/settings/SettingsToggleRow.tsx +55 -22
- package/src/settings/relaxedSizeStyle.ts +11 -0
- package/src/sheetDrag.ts +67 -0
- package/src/swipeRow.ts +169 -0
- package/src/tabBarRoutes.ts +18 -0
- package/src/tabIndicator.ts +71 -0
- package/src/tailwind-preset.cjs +13 -6
- package/src/tailwind-preset.d.cts +5 -0
- package/src/theme.tsx +6 -2
- package/src/toastDrag.ts +58 -0
- package/src/typography.ts +20 -3
- package/src/useScheme.ts +10 -1
- package/src/video/InlineVideo.tsx +91 -0
- package/src/webview/HtmlView.tsx +91 -0
- package/src/webview/HtmlView.web.tsx +21 -0
- package/src/webview/htmlView.ts +45 -0
package/src/ai/icon-button.tsx
CHANGED
|
@@ -10,17 +10,44 @@ import { useScheme } from "../useScheme";
|
|
|
10
10
|
/** The web `icon-sm` button: 32 square with a 16 icon. */
|
|
11
11
|
export const CHAT_ICON_BUTTON_SIZE = 32;
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Side and icon per size: `sm` is the web `icon-sm` (32, 16 icon), `xs` the
|
|
15
|
+
* web `icon-xs` (28, 14 icon) and `md` the web `size-9` (36, 16 icon), Eva's
|
|
16
|
+
* composer send button. `touch` has no web twin: a 44 square with a 16 icon,
|
|
17
|
+
* for a row of ghost buttons a thumb taps (the message actions), where the
|
|
18
|
+
* web 28 squares 2 apart are a mouse's density.
|
|
19
|
+
*/
|
|
20
|
+
const CHAT_ICON_BUTTON = {
|
|
21
|
+
xs: { side: 28, icon: 14, className: "h-7 w-7" },
|
|
22
|
+
sm: { side: CHAT_ICON_BUTTON_SIZE, icon: 16, className: "h-8 w-8" },
|
|
23
|
+
md: { side: 36, icon: 16, className: "h-9 w-9" },
|
|
24
|
+
touch: { side: MIN_TOUCH_TARGET, icon: 16, className: "h-11 w-11" },
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
export type ChatIconButtonSize = keyof typeof CHAT_ICON_BUTTON;
|
|
28
|
+
|
|
13
29
|
export type ChatIconButtonProps = Omit<ButtonProps, "children" | "size" | "icon"> & {
|
|
14
30
|
icon: ComponentType<IconProps>;
|
|
15
31
|
/** Names the button for VoiceOver and TalkBack; it has no text. */
|
|
16
32
|
label: string;
|
|
17
33
|
/** Default: the variant's label colour. */
|
|
18
34
|
iconColor?: string;
|
|
35
|
+
/** Default `sm`. Larger text paints each 44 with a 20 icon. */
|
|
36
|
+
size?: ChatIconButtonSize;
|
|
37
|
+
/**
|
|
38
|
+
* `"square"` (default) slops to 44 on every side. `"vertical"` slops only
|
|
39
|
+
* the height to 44, for a dense row (the message actions sit 2 to 4 apart),
|
|
40
|
+
* where sideways slop would overlap the next button and steal its taps.
|
|
41
|
+
* `{ left, right }` is `"vertical"` plus that much on each side, for the
|
|
42
|
+
* outer side of a button at the end of such a row.
|
|
43
|
+
*/
|
|
44
|
+
slop?: "square" | "vertical" | { left: number; right: number };
|
|
19
45
|
};
|
|
20
46
|
|
|
21
47
|
/**
|
|
22
|
-
* A round `Button` at the web `icon-sm` size (32),
|
|
23
|
-
*
|
|
48
|
+
* A round `Button` at the web `icon-sm` size (32), or another
|
|
49
|
+
* `ChatIconButtonSize`, with the slop to 44. With larger text it is painted
|
|
50
|
+
* 44 with a 20 icon. The message
|
|
24
51
|
* actions, the composer tools and its send button, and the code copy button.
|
|
25
52
|
* Round from the `Button` pill default; `shape="rounded"` gives radius 16.
|
|
26
53
|
*/
|
|
@@ -29,23 +56,31 @@ export function ChatIconButton({
|
|
|
29
56
|
label,
|
|
30
57
|
iconColor,
|
|
31
58
|
variant = "ghost",
|
|
59
|
+
size = "sm",
|
|
60
|
+
slop = "square",
|
|
32
61
|
className,
|
|
33
62
|
...props
|
|
34
63
|
}: ChatIconButtonProps) {
|
|
35
64
|
const scheme = useScheme();
|
|
36
65
|
const largerText = useLargerText();
|
|
37
|
-
const
|
|
66
|
+
const metrics = largerText
|
|
67
|
+
? { side: MIN_TOUCH_TARGET, icon: 20, className: "h-11 w-11" }
|
|
68
|
+
: CHAT_ICON_BUTTON[size];
|
|
38
69
|
const color = iconColor ?? buttonColors(variant, scheme)?.color ?? tokens.color.foreground[scheme];
|
|
39
70
|
return (
|
|
40
71
|
<Button
|
|
41
72
|
variant={variant}
|
|
42
73
|
size="icon"
|
|
43
74
|
accessibilityLabel={label}
|
|
44
|
-
hitSlop={
|
|
45
|
-
|
|
75
|
+
hitSlop={
|
|
76
|
+
slop === "square"
|
|
77
|
+
? touchSlop(metrics.side)
|
|
78
|
+
: { ...touchSlop(MIN_TOUCH_TARGET, metrics.side), ...(slop === "vertical" ? null : slop) }
|
|
79
|
+
}
|
|
80
|
+
className={cn(metrics.className, className)}
|
|
46
81
|
{...props}
|
|
47
82
|
>
|
|
48
|
-
<Icon size={
|
|
83
|
+
<Icon size={metrics.icon} color={color} />
|
|
49
84
|
</Button>
|
|
50
85
|
);
|
|
51
86
|
}
|
package/src/ai/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// `@vvedantb/native-ui/ai` entry only, so apps without a chat do not bundle
|
|
3
3
|
// them. Generic: no model names and no app logic.
|
|
4
4
|
export { useStreamingDisclosure } from "./use-streaming-disclosure";
|
|
5
|
-
export { chatTextSize, formatElapsed, MONO_FONT } from "./chat-text";
|
|
5
|
+
export { chatTextSize, formatElapsed, META_TEXT_STYLE, MONO_FONT } from "./chat-text";
|
|
6
6
|
export type { ChatTextKind } from "./chat-text";
|
|
7
7
|
export { Shimmer, SHIMMER_LOW_OPACITY } from "./shimmer";
|
|
8
8
|
export type { ShimmerProps } from "./shimmer";
|
|
@@ -17,8 +17,10 @@ export type { CollapsibleProps, CollapsibleTriggerProps } from "./collapsible";
|
|
|
17
17
|
export {
|
|
18
18
|
Conversation,
|
|
19
19
|
ConversationContent,
|
|
20
|
+
ConversationList,
|
|
20
21
|
ConversationEmptyState,
|
|
21
22
|
ConversationScrollButton,
|
|
23
|
+
ConversationDock,
|
|
22
24
|
useConversation,
|
|
23
25
|
isScrolledToBottom,
|
|
24
26
|
AT_BOTTOM_THRESHOLD,
|
|
@@ -26,8 +28,11 @@ export {
|
|
|
26
28
|
export type {
|
|
27
29
|
ConversationProps,
|
|
28
30
|
ConversationContentProps,
|
|
31
|
+
ConversationListProps,
|
|
32
|
+
ConversationScroller,
|
|
29
33
|
ConversationEmptyStateProps,
|
|
30
34
|
ConversationScrollButtonProps,
|
|
35
|
+
ConversationDockProps,
|
|
31
36
|
} from "./conversation";
|
|
32
37
|
export {
|
|
33
38
|
Message,
|
|
@@ -35,7 +40,9 @@ export {
|
|
|
35
40
|
MessageActionsBar,
|
|
36
41
|
MessageAction,
|
|
37
42
|
MessageMeta,
|
|
43
|
+
MessageBotLabel,
|
|
38
44
|
MESSAGE_ACTIONS,
|
|
45
|
+
MESSAGE_HIGHLIGHT_MS,
|
|
39
46
|
userBubbleStyle,
|
|
40
47
|
tintBubbleStyle,
|
|
41
48
|
} from "./message";
|
|
@@ -46,9 +53,17 @@ export type {
|
|
|
46
53
|
MessageBubble,
|
|
47
54
|
MessageActionsBarProps,
|
|
48
55
|
MessageMetaProps,
|
|
56
|
+
MessageBot,
|
|
57
|
+
MessageBotLabelProps,
|
|
49
58
|
MessageActionKind,
|
|
50
59
|
MessageActionProps,
|
|
51
60
|
} from "./message";
|
|
61
|
+
export { MessageActionSheet } from "./message-action-sheet";
|
|
62
|
+
export type { MessageActionSheetProps, MessageSheetAction } from "./message-action-sheet";
|
|
63
|
+
export { ReplyQuoteChip } from "./reply-quote-chip";
|
|
64
|
+
export type { ReplyQuoteChipProps } from "./reply-quote-chip";
|
|
65
|
+
export { MessageQuote, QUOTE_MAX_LINES } from "./message-quote";
|
|
66
|
+
export type { MessageQuoteProps } from "./message-quote";
|
|
52
67
|
export {
|
|
53
68
|
Reasoning,
|
|
54
69
|
ReasoningTrigger,
|
|
@@ -92,18 +107,48 @@ export type {
|
|
|
92
107
|
} from "./chain-of-thought";
|
|
93
108
|
export { ActivityStep } from "./activity-step";
|
|
94
109
|
export type { ActivityStepProps, ActivityStepStatus } from "./activity-step";
|
|
110
|
+
// The web timeline's pure logic (byte-identical copies) and its folded rules.
|
|
111
|
+
// The step type is `ActivityTimelineStep` here: `ActivityStep` is the line above.
|
|
112
|
+
export { activityStepHasRichDetail } from "./activity-step-types";
|
|
113
|
+
export type {
|
|
114
|
+
ActivityStep as ActivityTimelineStep,
|
|
115
|
+
ActivityStepEdit,
|
|
116
|
+
ActivityStepOutput,
|
|
117
|
+
ActivityTodoItem,
|
|
118
|
+
} from "./activity-step-types";
|
|
119
|
+
export {
|
|
120
|
+
deriveActionGroupSummary,
|
|
121
|
+
deriveReadableCommandDisplay,
|
|
122
|
+
deriveStepRowPresentation,
|
|
123
|
+
resolveCommandVisualKind,
|
|
124
|
+
} from "./activity-step-label";
|
|
125
|
+
export type { CommandVisualKind, StepRowPresentation } from "./activity-step-label";
|
|
126
|
+
export { activityRowKey, activitySegmentKey, buildActivityRows, groupActivityRows } from "./activity-tasks-utils";
|
|
127
|
+
export type { ActivityRow, ActivitySegment } from "./activity-tasks-utils";
|
|
128
|
+
export {
|
|
129
|
+
ACTIVITY_MAX_VISIBLE_BLOCKS,
|
|
130
|
+
ACTIVITY_MAX_VISIBLE_CHILDREN,
|
|
131
|
+
activityChildRows,
|
|
132
|
+
activityStepSeconds,
|
|
133
|
+
foldedActivitySegments,
|
|
134
|
+
} from "./activity-timeline";
|
|
95
135
|
export {
|
|
96
136
|
PromptInput,
|
|
97
137
|
PromptInputButton,
|
|
138
|
+
PromptInputChip,
|
|
98
139
|
PromptInputSubmit,
|
|
140
|
+
PROMPT_INPUT_CHIP,
|
|
99
141
|
isGenerating,
|
|
100
142
|
} from "./prompt-input";
|
|
101
143
|
export type {
|
|
102
144
|
ChatStatus,
|
|
145
|
+
PromptInputChipProps,
|
|
103
146
|
PromptInputProps,
|
|
104
147
|
PromptInputButtonProps,
|
|
105
148
|
PromptInputSubmitProps,
|
|
106
149
|
} from "./prompt-input";
|
|
150
|
+
export { PromptInputMenu, PromptInputMenuItem, PROMPT_INPUT_MENU, promptInputMenuHeight } from "./prompt-input-menu";
|
|
151
|
+
export type { PromptInputMenuProps, PromptInputMenuItemProps } from "./prompt-input-menu";
|
|
107
152
|
export { Suggestions, Suggestion } from "./suggestion";
|
|
108
153
|
export type { SuggestionsProps, SuggestionProps } from "./suggestion";
|
|
109
154
|
export { Sources, SourcesTrigger, SourcesContent, Source, sourcesLabel } from "./sources";
|
|
@@ -136,3 +181,9 @@ export { ChoiceQuestion, choiceAnswers } from "./choice-question";
|
|
|
136
181
|
export type { ChoiceQuestionProps, ChoiceQuestionItem } from "./choice-question";
|
|
137
182
|
export { ActivityStepDetail } from "./activity-step-detail";
|
|
138
183
|
export type { ActivityStepDetailProps, ActivityStepDetailStep } from "./activity-step-detail";
|
|
184
|
+
export { UnifiedDiff, DiffStat, UNIFIED_DIFF } from "./unified-diff";
|
|
185
|
+
export type { UnifiedDiffProps, UnifiedDiffLine, DiffStatProps } from "./unified-diff";
|
|
186
|
+
export { ToolApprovalCard, ToolDraftCard, summarizeArgs, ARGS_MAX_CHARS } from "./tool-approval-card";
|
|
187
|
+
export type { ToolApprovalCardProps, ToolDraftCardProps, ToolDecision } from "./tool-approval-card";
|
|
188
|
+
export { SubagentRunCard, SubagentResultCard, subagentStatusColors } from "./subagent-cards";
|
|
189
|
+
export type { SubagentRunCardProps, SubagentResultCardProps, SubagentStatus } from "./subagent-cards";
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { useEffect, type ComponentType } from "react";
|
|
2
|
+
import { Modal, Pressable, StyleSheet, Text, View } from "react-native";
|
|
3
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
4
|
+
import type { IconProps } from "@tabler/icons-react-native";
|
|
5
|
+
import { cn } from "../cn";
|
|
6
|
+
import { MIN_TOUCH_TARGET, sizeStyle, textStyle, touchSlop, typeStyle } from "../controlStyles";
|
|
7
|
+
import { selectionHaptic } from "../haptics";
|
|
8
|
+
import { palette } from "../palette";
|
|
9
|
+
import { useLargerText } from "../theme";
|
|
10
|
+
import { tokens } from "../tokens";
|
|
11
|
+
import { useReduceMotion } from "../useReduceMotion";
|
|
12
|
+
import { useScheme } from "../useScheme";
|
|
13
|
+
import { chatTextSize } from "./chat-text";
|
|
14
|
+
|
|
15
|
+
/** One row of `MessageActionSheet`. */
|
|
16
|
+
export type MessageSheetAction = {
|
|
17
|
+
key: string;
|
|
18
|
+
label: string;
|
|
19
|
+
icon?: ComponentType<IconProps>;
|
|
20
|
+
/** Draws the row in the danger colour, such as Delete. */
|
|
21
|
+
destructive?: boolean;
|
|
22
|
+
onPress: () => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** The sheet's default title, also the message's `longpress` action label. */
|
|
26
|
+
export const MESSAGE_ACTIONS_TITLE = "Message actions";
|
|
27
|
+
|
|
28
|
+
export type MessageActionSheetProps = {
|
|
29
|
+
open: boolean;
|
|
30
|
+
/** Called on a backdrop tap, Cancel, Android back and after an action runs. */
|
|
31
|
+
onClose: () => void;
|
|
32
|
+
actions: MessageSheetAction[];
|
|
33
|
+
/** Shown above the rows, and names the sheet for VoiceOver and TalkBack. Default "Message actions". */
|
|
34
|
+
title?: string;
|
|
35
|
+
/** Default "Cancel". */
|
|
36
|
+
cancelLabel?: string;
|
|
37
|
+
testID?: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The sheet that opens on a long press of a chat message: a bottom sheet in
|
|
42
|
+
* an RN `Modal` (no `@gorhom/bottom-sheet`) with one pressable row per action
|
|
43
|
+
* and a Cancel row. It closes on the backdrop, Cancel, Android back and after
|
|
44
|
+
* an action. A `selectionHaptic()` fires when it opens (`expo-haptics` is an
|
|
45
|
+
* optional peer). Row testIDs are `{testID}-action-{key}`.
|
|
46
|
+
*/
|
|
47
|
+
export function MessageActionSheet({
|
|
48
|
+
open,
|
|
49
|
+
onClose,
|
|
50
|
+
actions,
|
|
51
|
+
title = MESSAGE_ACTIONS_TITLE,
|
|
52
|
+
cancelLabel = "Cancel",
|
|
53
|
+
testID = "message-action-sheet",
|
|
54
|
+
}: MessageActionSheetProps) {
|
|
55
|
+
const scheme = useScheme();
|
|
56
|
+
const largerText = useLargerText();
|
|
57
|
+
const insets = useSafeAreaInsets();
|
|
58
|
+
const reduceMotion = useReduceMotion();
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (open) selectionHaptic();
|
|
62
|
+
}, [open]);
|
|
63
|
+
|
|
64
|
+
const caption = chatTextSize("caption", largerText);
|
|
65
|
+
const body = chatTextSize("body", largerText);
|
|
66
|
+
const minHeight = largerText ? 56 : MIN_TOUCH_TARGET + 4;
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Modal
|
|
70
|
+
visible={open}
|
|
71
|
+
transparent
|
|
72
|
+
animationType={reduceMotion ? "none" : "fade"}
|
|
73
|
+
statusBarTranslucent
|
|
74
|
+
onRequestClose={onClose}
|
|
75
|
+
>
|
|
76
|
+
<Pressable
|
|
77
|
+
testID={`${testID}-backdrop`}
|
|
78
|
+
accessibilityRole="button"
|
|
79
|
+
accessibilityLabel="Close"
|
|
80
|
+
onPress={onClose}
|
|
81
|
+
hitSlop={touchSlop(MIN_TOUCH_TARGET)}
|
|
82
|
+
style={[StyleSheet.absoluteFill, { backgroundColor: palette.backdrop[scheme] }]}
|
|
83
|
+
/>
|
|
84
|
+
<View pointerEvents="box-none" style={styles.anchor}>
|
|
85
|
+
<View
|
|
86
|
+
testID={testID}
|
|
87
|
+
accessibilityViewIsModal
|
|
88
|
+
accessibilityLabel={title}
|
|
89
|
+
onAccessibilityEscape={onClose}
|
|
90
|
+
className="w-full self-center px-2"
|
|
91
|
+
style={{ maxWidth: 480, paddingBottom: insets.bottom + 8 }}
|
|
92
|
+
>
|
|
93
|
+
<View
|
|
94
|
+
className="overflow-hidden"
|
|
95
|
+
style={{ backgroundColor: tokens.color.surface[scheme], borderRadius: tokens.radius.card }}
|
|
96
|
+
>
|
|
97
|
+
<Text
|
|
98
|
+
accessibilityRole="header"
|
|
99
|
+
numberOfLines={1}
|
|
100
|
+
className={cn(caption, "px-4 pb-1 pt-3 font-instrument-sans-medium")}
|
|
101
|
+
style={[sizeStyle(caption), typeStyle("medium"), textStyle("muted", scheme)]}
|
|
102
|
+
>
|
|
103
|
+
{title}
|
|
104
|
+
</Text>
|
|
105
|
+
{actions.map((action, index) => {
|
|
106
|
+
const Icon = action.icon;
|
|
107
|
+
const color = action.destructive ? palette.danger[scheme] : tokens.color.foreground[scheme];
|
|
108
|
+
return (
|
|
109
|
+
<Pressable
|
|
110
|
+
key={action.key}
|
|
111
|
+
testID={`${testID}-action-${action.key}`}
|
|
112
|
+
accessibilityRole="button"
|
|
113
|
+
accessibilityLabel={action.label}
|
|
114
|
+
hitSlop={touchSlop(MIN_TOUCH_TARGET)}
|
|
115
|
+
onPress={() => {
|
|
116
|
+
onClose();
|
|
117
|
+
action.onPress();
|
|
118
|
+
}}
|
|
119
|
+
className="flex-row items-center gap-3 px-4"
|
|
120
|
+
style={[
|
|
121
|
+
{ minHeight },
|
|
122
|
+
index > 0 && {
|
|
123
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
124
|
+
borderTopColor: tokens.color.separator[scheme],
|
|
125
|
+
},
|
|
126
|
+
]}
|
|
127
|
+
>
|
|
128
|
+
{Icon ? <Icon size={largerText ? 22 : 20} color={color} /> : null}
|
|
129
|
+
<Text
|
|
130
|
+
numberOfLines={1}
|
|
131
|
+
className={cn(body, "min-w-0 flex-1 font-instrument-sans-medium")}
|
|
132
|
+
style={[sizeStyle(body), typeStyle("medium"), { color }]}
|
|
133
|
+
>
|
|
134
|
+
{action.label}
|
|
135
|
+
</Text>
|
|
136
|
+
</Pressable>
|
|
137
|
+
);
|
|
138
|
+
})}
|
|
139
|
+
</View>
|
|
140
|
+
<Pressable
|
|
141
|
+
testID={`${testID}-cancel`}
|
|
142
|
+
accessibilityRole="button"
|
|
143
|
+
accessibilityLabel={cancelLabel}
|
|
144
|
+
onPress={onClose}
|
|
145
|
+
hitSlop={touchSlop(MIN_TOUCH_TARGET)}
|
|
146
|
+
className="mt-2 items-center justify-center"
|
|
147
|
+
style={{
|
|
148
|
+
minHeight,
|
|
149
|
+
backgroundColor: tokens.color.surface[scheme],
|
|
150
|
+
borderRadius: tokens.radius.card,
|
|
151
|
+
}}
|
|
152
|
+
>
|
|
153
|
+
<Text
|
|
154
|
+
className={cn(body, "font-instrument-sans-semibold")}
|
|
155
|
+
style={[sizeStyle(body), typeStyle("semibold"), textStyle("foreground", scheme)]}
|
|
156
|
+
>
|
|
157
|
+
{cancelLabel}
|
|
158
|
+
</Text>
|
|
159
|
+
</Pressable>
|
|
160
|
+
</View>
|
|
161
|
+
</View>
|
|
162
|
+
</Modal>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const styles = StyleSheet.create({
|
|
167
|
+
anchor: { flex: 1, justifyContent: "flex-end" },
|
|
168
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { Pressable, Text, View } from "react-native";
|
|
3
|
+
import { cn } from "../cn";
|
|
4
|
+
import { MIN_TOUCH_TARGET, sizeStyle, textStyle, touchSlop, typeStyle } from "../controlStyles";
|
|
5
|
+
import { withAlpha } from "../palette";
|
|
6
|
+
import { useLargerText } from "../theme";
|
|
7
|
+
import { useScheme } from "../useScheme";
|
|
8
|
+
import { chatTextSize } from "./chat-text";
|
|
9
|
+
import { ChatTextColorContext } from "./response-text";
|
|
10
|
+
|
|
11
|
+
export type MessageQuoteProps = {
|
|
12
|
+
/** Who wrote the quoted message, such as "You" or the bot name. */
|
|
13
|
+
author: string;
|
|
14
|
+
/** The quoted text. Shown on up to two lines. */
|
|
15
|
+
text: string;
|
|
16
|
+
/** Makes the quote a button, so the app can scroll to and highlight the original. */
|
|
17
|
+
onPress?: () => void;
|
|
18
|
+
/** Names the button. Default "Go to quoted message from {author}". Used with `onPress` only. */
|
|
19
|
+
accessibilityLabel?: string;
|
|
20
|
+
className?: string;
|
|
21
|
+
testID?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Lines of the quoted text before it is cut with an ellipsis. */
|
|
25
|
+
export const QUOTE_MAX_LINES = 2;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A compact quote inside a message bubble, above the message text: a left
|
|
29
|
+
* border, the author in the caption size and the quoted text on up to two
|
|
30
|
+
* lines. It takes its colour from the enclosing `MessageContent`, so it reads
|
|
31
|
+
* on the primary user bubble as well as on the tint and assistant ones. With
|
|
32
|
+
* `onPress` it is a button (44 point minimum height).
|
|
33
|
+
*/
|
|
34
|
+
export function MessageQuote({
|
|
35
|
+
author,
|
|
36
|
+
text,
|
|
37
|
+
onPress,
|
|
38
|
+
accessibilityLabel,
|
|
39
|
+
className,
|
|
40
|
+
testID,
|
|
41
|
+
}: MessageQuoteProps) {
|
|
42
|
+
const scheme = useScheme();
|
|
43
|
+
const largerText = useLargerText();
|
|
44
|
+
const inherited = useContext(ChatTextColorContext);
|
|
45
|
+
const color = inherited ?? textStyle("foreground", scheme).color;
|
|
46
|
+
const caption = chatTextSize("caption", largerText);
|
|
47
|
+
const body = chatTextSize("small", largerText);
|
|
48
|
+
const content = (
|
|
49
|
+
<>
|
|
50
|
+
<Text
|
|
51
|
+
numberOfLines={1}
|
|
52
|
+
className={cn(caption, "font-instrument-sans-semibold")}
|
|
53
|
+
style={[sizeStyle(caption), typeStyle("semibold"), { color: withAlpha(color, 0.75) }]}
|
|
54
|
+
>
|
|
55
|
+
{author}
|
|
56
|
+
</Text>
|
|
57
|
+
<Text
|
|
58
|
+
numberOfLines={QUOTE_MAX_LINES}
|
|
59
|
+
className={cn(body, "font-instrument-sans")}
|
|
60
|
+
style={[sizeStyle(body), { color: withAlpha(color, 0.9) }]}
|
|
61
|
+
>
|
|
62
|
+
{text}
|
|
63
|
+
</Text>
|
|
64
|
+
</>
|
|
65
|
+
);
|
|
66
|
+
const style = {
|
|
67
|
+
borderLeftWidth: 3,
|
|
68
|
+
borderLeftColor: withAlpha(color, 0.45),
|
|
69
|
+
borderRadius: 4,
|
|
70
|
+
};
|
|
71
|
+
if (!onPress) {
|
|
72
|
+
return (
|
|
73
|
+
<View
|
|
74
|
+
testID={testID}
|
|
75
|
+
accessible
|
|
76
|
+
accessibilityLabel={`Quoted message from ${author}: ${text}`}
|
|
77
|
+
className={cn("min-w-0 gap-0.5 py-0.5 pl-2.5", className)}
|
|
78
|
+
style={style}
|
|
79
|
+
>
|
|
80
|
+
{content}
|
|
81
|
+
</View>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return (
|
|
85
|
+
<Pressable
|
|
86
|
+
testID={testID}
|
|
87
|
+
accessibilityRole="button"
|
|
88
|
+
accessibilityLabel={accessibilityLabel ?? `Go to quoted message from ${author}`}
|
|
89
|
+
accessibilityHint={text}
|
|
90
|
+
onPress={onPress}
|
|
91
|
+
hitSlop={touchSlop(MIN_TOUCH_TARGET)}
|
|
92
|
+
className={cn("min-h-11 min-w-0 justify-center gap-0.5 py-0.5 pl-2.5", className)}
|
|
93
|
+
style={style}
|
|
94
|
+
>
|
|
95
|
+
{content}
|
|
96
|
+
</Pressable>
|
|
97
|
+
);
|
|
98
|
+
}
|