Improve review comment UI
This commit is contained in:
@@ -43,6 +43,7 @@ const {
|
||||
fontSize: { xs: 11, sm: 13, base: 15, lg: 18 },
|
||||
fontWeight: { normal: "400", medium: "500" },
|
||||
lineHeight: { diff: 18 },
|
||||
opacity: { 50: 0.5 },
|
||||
shadow: { md: {} },
|
||||
colors: {
|
||||
surface0: "#000",
|
||||
@@ -226,6 +227,7 @@ vi.mock("lucide-react-native", () => {
|
||||
AudioLines: createIcon("AudioLines"),
|
||||
CircleDot: createIcon("CircleDot"),
|
||||
GitPullRequest: createIcon("GitPullRequest"),
|
||||
MessageSquareCode: createIcon("MessageSquareCode"),
|
||||
X: createIcon("X"),
|
||||
Mic: createIcon("Mic"),
|
||||
MicOff: createIcon("MicOff"),
|
||||
|
||||
@@ -853,9 +853,7 @@ function ComposerCancelButton({
|
||||
) : (
|
||||
<Square size={buttonIconSize} color="white" fill="white" />
|
||||
);
|
||||
const shortcutNode = agentInterruptKeys ? (
|
||||
<Shortcut chord={agentInterruptKeys} style={styles.tooltipShortcut} />
|
||||
) : null;
|
||||
const shortcutNode = agentInterruptKeys ? <Shortcut chord={agentInterruptKeys} /> : null;
|
||||
return (
|
||||
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
|
||||
<TooltipTrigger
|
||||
@@ -941,9 +939,7 @@ function ComposerVoiceModeButton({
|
||||
realtimeVoiceButtonStyle,
|
||||
voiceToggleKeys,
|
||||
}: ComposerVoiceModeButtonProps) {
|
||||
const shortcutNode = voiceToggleKeys ? (
|
||||
<Shortcut chord={voiceToggleKeys} style={styles.tooltipShortcut} />
|
||||
) : null;
|
||||
const shortcutNode = voiceToggleKeys ? <Shortcut chord={voiceToggleKeys} /> : null;
|
||||
const renderTriggerContent = useCallback(
|
||||
({ hovered }: PressableStateCallbackType & { hovered?: boolean }) => {
|
||||
if (isVoiceSwitching) {
|
||||
@@ -1874,10 +1870,6 @@ const styles = StyleSheet.create((theme: Theme) => ({
|
||||
fontSize: theme.fontSize.sm,
|
||||
color: theme.colors.popoverForeground,
|
||||
},
|
||||
tooltipShortcut: {
|
||||
backgroundColor: theme.colors.surface3,
|
||||
borderColor: theme.colors.borderAccent,
|
||||
},
|
||||
buttonDisabled: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
|
||||
@@ -138,7 +138,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
tooltipTitle: {
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontWeight: theme.fontWeight.semibold,
|
||||
},
|
||||
tooltipText: {
|
||||
color: theme.colors.foreground,
|
||||
|
||||
@@ -231,35 +231,59 @@ const EMPTY_COMMENTS: readonly ReviewDraftComment[] = [];
|
||||
|
||||
function noopStartComment(): void {}
|
||||
|
||||
const LONG_PRESS_COMMENT_DELAY_MS = 350;
|
||||
const DIFF_LINE_HOVER_STYLE = isWeb ? ({ cursor: "auto" } as const) : null;
|
||||
|
||||
function LongPressableLine({
|
||||
reviewTarget,
|
||||
reviewActions,
|
||||
onHoverChange,
|
||||
hoverTargetKey,
|
||||
onHoverTargetChange,
|
||||
style,
|
||||
children,
|
||||
}: {
|
||||
reviewTarget: ReviewableDiffTarget | null | undefined;
|
||||
reviewActions: InlineReviewActions | undefined;
|
||||
onHoverChange?: (hovered: boolean) => void;
|
||||
hoverTargetKey?: string | null;
|
||||
onHoverTargetChange?: (key: string | null) => void;
|
||||
style: StyleProp<ViewStyle>;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const onStartComment = reviewActions?.onStartComment;
|
||||
const handleLongPress = useCallback(() => {
|
||||
const handlePress = useCallback(() => {
|
||||
if (reviewTarget && onStartComment) {
|
||||
onStartComment(reviewTarget);
|
||||
}
|
||||
}, [reviewTarget, onStartComment]);
|
||||
|
||||
const handleHoverIn = useCallback(() => {
|
||||
onHoverChange?.(true);
|
||||
if (hoverTargetKey) {
|
||||
onHoverTargetChange?.(hoverTargetKey);
|
||||
}
|
||||
}, [hoverTargetKey, onHoverChange, onHoverTargetChange]);
|
||||
const handleHoverOut = useCallback(() => {
|
||||
onHoverChange?.(false);
|
||||
if (hoverTargetKey) {
|
||||
onHoverTargetChange?.(null);
|
||||
}
|
||||
}, [hoverTargetKey, onHoverChange, onHoverTargetChange]);
|
||||
const hoverStyle = useMemo(() => [style, DIFF_LINE_HOVER_STYLE], [style]);
|
||||
|
||||
if (isWeb && (onHoverChange || onHoverTargetChange)) {
|
||||
return (
|
||||
<Pressable onHoverIn={handleHoverIn} onHoverOut={handleHoverOut} style={hoverStyle}>
|
||||
{children}
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isNative || !reviewTarget || !onStartComment) {
|
||||
return <View style={style}>{children}</View>;
|
||||
}
|
||||
return (
|
||||
<Pressable
|
||||
onLongPress={handleLongPress}
|
||||
delayLongPress={LONG_PRESS_COMMENT_DELAY_MS}
|
||||
style={style}
|
||||
>
|
||||
<Pressable onPress={handlePress} style={style}>
|
||||
{children}
|
||||
</Pressable>
|
||||
);
|
||||
@@ -279,6 +303,7 @@ function DiffGutterCell({
|
||||
gutterWidth,
|
||||
reviewTarget,
|
||||
reviewActions,
|
||||
isLineHovered,
|
||||
style,
|
||||
}: {
|
||||
lineNumber: number | null;
|
||||
@@ -286,6 +311,7 @@ function DiffGutterCell({
|
||||
gutterWidth: number;
|
||||
reviewTarget?: ReviewableDiffTarget | null;
|
||||
reviewActions?: InlineReviewActions;
|
||||
isLineHovered?: boolean;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
}) {
|
||||
const containerStyle = useMemo(
|
||||
@@ -315,6 +341,7 @@ function DiffGutterCell({
|
||||
reviewTarget={reviewTarget}
|
||||
comments={comments}
|
||||
isEditorOpen={isEditorOpen}
|
||||
isLineHovered={isLineHovered}
|
||||
onStartComment={onStartComment}
|
||||
style={containerStyle}
|
||||
>
|
||||
@@ -330,11 +357,17 @@ function DiffTextLine({
|
||||
wrapLines,
|
||||
reviewTarget,
|
||||
reviewActions,
|
||||
onHoverChange,
|
||||
hoverTargetKey,
|
||||
onHoverTargetChange,
|
||||
}: {
|
||||
line: DiffLine;
|
||||
wrapLines: boolean;
|
||||
reviewTarget?: ReviewableDiffTarget | null;
|
||||
reviewActions?: InlineReviewActions;
|
||||
onHoverChange?: (hovered: boolean) => void;
|
||||
hoverTargetKey?: string | null;
|
||||
onHoverTargetChange?: (key: string | null) => void;
|
||||
}) {
|
||||
const visibleTokens = hasVisibleDiffTokens(line.tokens) ? line.tokens : null;
|
||||
|
||||
@@ -358,6 +391,9 @@ function DiffTextLine({
|
||||
<LongPressableLine
|
||||
reviewTarget={reviewTarget}
|
||||
reviewActions={reviewActions}
|
||||
onHoverChange={onHoverChange}
|
||||
hoverTargetKey={hoverTargetKey}
|
||||
onHoverTargetChange={onHoverTargetChange}
|
||||
style={containerStyle}
|
||||
>
|
||||
{line.type !== "header" && visibleTokens ? (
|
||||
@@ -373,10 +409,16 @@ function SplitTextLine({
|
||||
line,
|
||||
wrapLines,
|
||||
reviewActions,
|
||||
onHoverChange,
|
||||
hoverTargetKey,
|
||||
onHoverTargetChange,
|
||||
}: {
|
||||
line: SplitDiffDisplayLine | null;
|
||||
wrapLines: boolean;
|
||||
reviewActions?: InlineReviewActions;
|
||||
onHoverChange?: (hovered: boolean) => void;
|
||||
hoverTargetKey?: string | null;
|
||||
onHoverTargetChange?: (key: string | null) => void;
|
||||
}) {
|
||||
const visibleTokens = line && hasVisibleDiffTokens(line.tokens) ? line.tokens : null;
|
||||
|
||||
@@ -400,6 +442,9 @@ function SplitTextLine({
|
||||
<LongPressableLine
|
||||
reviewTarget={line?.reviewTarget}
|
||||
reviewActions={reviewActions}
|
||||
onHoverChange={onHoverChange}
|
||||
hoverTargetKey={hoverTargetKey}
|
||||
onHoverTargetChange={onHoverTargetChange}
|
||||
style={containerStyle}
|
||||
>
|
||||
{visibleTokens ? (
|
||||
@@ -426,6 +471,7 @@ function DiffLineView({
|
||||
reviewTarget?: ReviewableDiffTarget | null;
|
||||
reviewActions?: InlineReviewActions;
|
||||
}) {
|
||||
const [isLineHovered, setIsLineHovered] = useState(false);
|
||||
const visibleTokens = hasVisibleDiffTokens(line.tokens) ? line.tokens : null;
|
||||
|
||||
const containerStyle = useMemo(
|
||||
@@ -448,6 +494,7 @@ function DiffLineView({
|
||||
<LongPressableLine
|
||||
reviewTarget={reviewTarget}
|
||||
reviewActions={reviewActions}
|
||||
onHoverChange={setIsLineHovered}
|
||||
style={containerStyle}
|
||||
>
|
||||
<DiffGutterCell
|
||||
@@ -456,6 +503,7 @@ function DiffLineView({
|
||||
gutterWidth={gutterWidth}
|
||||
reviewTarget={reviewTarget}
|
||||
reviewActions={reviewActions}
|
||||
isLineHovered={isLineHovered}
|
||||
style={styles.lineNumberGutter}
|
||||
/>
|
||||
{line.type !== "header" && visibleTokens ? (
|
||||
@@ -478,6 +526,7 @@ function SplitDiffLine({
|
||||
wrapLines: boolean;
|
||||
reviewActions?: InlineReviewActions;
|
||||
}) {
|
||||
const [isLineHovered, setIsLineHovered] = useState(false);
|
||||
const visibleTokens = line && hasVisibleDiffTokens(line.tokens) ? line.tokens : null;
|
||||
|
||||
const containerStyle = useMemo(
|
||||
@@ -500,6 +549,7 @@ function SplitDiffLine({
|
||||
<LongPressableLine
|
||||
reviewTarget={line?.reviewTarget}
|
||||
reviewActions={reviewActions}
|
||||
onHoverChange={setIsLineHovered}
|
||||
style={containerStyle}
|
||||
>
|
||||
<DiffGutterCell
|
||||
@@ -508,6 +558,7 @@ function SplitDiffLine({
|
||||
gutterWidth={gutterWidth}
|
||||
reviewTarget={line?.reviewTarget}
|
||||
reviewActions={reviewActions}
|
||||
isLineHovered={isLineHovered}
|
||||
style={styles.lineNumberGutter}
|
||||
/>
|
||||
{visibleTokens ? (
|
||||
@@ -635,6 +686,7 @@ function SplitDiffColumn({
|
||||
showDivider?: boolean;
|
||||
}) {
|
||||
const [scrollWidth, setScrollWidth] = useState(0);
|
||||
const [hoveredReviewTargetKey, setHoveredReviewTargetKey] = useState<string | null>(null);
|
||||
|
||||
const wrapCellStyle = useMemo(
|
||||
() => [styles.splitCell, showDivider && styles.splitCellWithDivider],
|
||||
@@ -701,6 +753,7 @@ function SplitDiffColumn({
|
||||
);
|
||||
}
|
||||
const line = side === "left" ? row.left : row.right;
|
||||
const reviewTargetKey = line?.reviewTarget?.key ?? null;
|
||||
const reviewRowState = getSplitInlineReviewThreadState({
|
||||
left: row.left?.reviewTarget,
|
||||
right: row.right?.reviewTarget,
|
||||
@@ -714,6 +767,9 @@ function SplitDiffColumn({
|
||||
gutterWidth={gutterWidth}
|
||||
reviewTarget={line?.reviewTarget}
|
||||
reviewActions={reviewActions}
|
||||
isLineHovered={
|
||||
reviewTargetKey !== null && hoveredReviewTargetKey === reviewTargetKey
|
||||
}
|
||||
/>
|
||||
<InlineReviewGutterSpacer
|
||||
reviewTarget={line?.reviewTarget}
|
||||
@@ -741,6 +797,7 @@ function SplitDiffColumn({
|
||||
);
|
||||
}
|
||||
const line = side === "left" ? row.left : row.right;
|
||||
const reviewTargetKey = line?.reviewTarget?.key ?? null;
|
||||
const reviewRowState = getSplitInlineReviewThreadState({
|
||||
left: row.left?.reviewTarget,
|
||||
right: row.right?.reviewTarget,
|
||||
@@ -748,7 +805,13 @@ function SplitDiffColumn({
|
||||
});
|
||||
return (
|
||||
<View key={key}>
|
||||
<SplitTextLine line={line} wrapLines={false} reviewActions={reviewActions} />
|
||||
<SplitTextLine
|
||||
line={line}
|
||||
wrapLines={false}
|
||||
reviewActions={reviewActions}
|
||||
hoverTargetKey={reviewTargetKey}
|
||||
onHoverTargetChange={setHoveredReviewTargetKey}
|
||||
/>
|
||||
<InlineReviewThreadContent
|
||||
reviewTarget={line?.reviewTarget}
|
||||
reviewActions={reviewActions}
|
||||
@@ -872,6 +935,7 @@ function DiffFileBody({
|
||||
}) {
|
||||
const [scrollViewWidth, setScrollViewWidth] = useState(0);
|
||||
const [bodyWidth, setBodyWidth] = useState(0);
|
||||
const [hoveredReviewTargetKey, setHoveredReviewTargetKey] = useState<string | null>(null);
|
||||
const { theme } = useUnistyles();
|
||||
|
||||
const handleLayout = useCallback(
|
||||
@@ -975,6 +1039,9 @@ function DiffFileBody({
|
||||
gutterWidth={gutterWidth}
|
||||
reviewTarget={reviewTarget}
|
||||
reviewActions={reviewActions}
|
||||
isLineHovered={
|
||||
reviewTarget?.key !== undefined && hoveredReviewTargetKey === reviewTarget.key
|
||||
}
|
||||
/>
|
||||
<InlineReviewGutterSpacer
|
||||
reviewTarget={reviewTarget}
|
||||
@@ -998,6 +1065,8 @@ function DiffFileBody({
|
||||
wrapLines={false}
|
||||
reviewTarget={reviewTarget}
|
||||
reviewActions={reviewActions}
|
||||
hoverTargetKey={reviewTarget?.key ?? null}
|
||||
onHoverTargetChange={setHoveredReviewTargetKey}
|
||||
/>
|
||||
<InlineReviewThreadContent
|
||||
reviewTarget={reviewTarget}
|
||||
@@ -2998,11 +3067,17 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
gutterColumn: {
|
||||
backgroundColor: theme.colors.surface1,
|
||||
zIndex: 4,
|
||||
elevation: 4,
|
||||
overflow: "visible",
|
||||
},
|
||||
gutterCell: {
|
||||
borderRightWidth: theme.borderWidth[1],
|
||||
borderRightColor: theme.colors.border,
|
||||
justifyContent: "flex-start",
|
||||
zIndex: 4,
|
||||
elevation: 4,
|
||||
overflow: "visible",
|
||||
},
|
||||
inlineReviewRow: {
|
||||
flexDirection: "row",
|
||||
@@ -3050,6 +3125,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
diffLineContainer: {
|
||||
flexDirection: "row",
|
||||
alignItems: "stretch",
|
||||
overflow: "visible",
|
||||
},
|
||||
lineNumberGutter: {
|
||||
borderRightWidth: theme.borderWidth[1],
|
||||
@@ -3057,8 +3133,12 @@ const styles = StyleSheet.create((theme) => ({
|
||||
marginRight: theme.spacing[2],
|
||||
alignSelf: "stretch",
|
||||
justifyContent: "flex-start",
|
||||
zIndex: 4,
|
||||
elevation: 4,
|
||||
overflow: "visible",
|
||||
},
|
||||
lineNumberText: {
|
||||
width: "100%",
|
||||
textAlign: "right",
|
||||
paddingRight: theme.spacing[2],
|
||||
fontSize: theme.fontSize.xs,
|
||||
|
||||
@@ -88,8 +88,5 @@ const styles = StyleSheet.create((theme) => ({
|
||||
fontSize: theme.fontSize.sm,
|
||||
color: theme.colors.popoverForeground,
|
||||
},
|
||||
shortcut: {
|
||||
backgroundColor: theme.colors.surface3,
|
||||
borderColor: theme.colors.borderAccent,
|
||||
},
|
||||
shortcut: {},
|
||||
}));
|
||||
|
||||
@@ -278,7 +278,7 @@ function VoiceTooltipBody({
|
||||
return (
|
||||
<View style={styles.tooltipRow}>
|
||||
<Text style={styles.tooltipText}>{voiceTooltipText}</Text>
|
||||
{shortcut ? <Shortcut chord={shortcut} style={styles.tooltipShortcut} /> : null}
|
||||
{shortcut ? <Shortcut chord={shortcut} /> : null}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -293,7 +293,7 @@ function SendTooltipBody({
|
||||
return (
|
||||
<View style={styles.tooltipRow}>
|
||||
<Text style={styles.tooltipText}>{label}</Text>
|
||||
{sendKeys ? <Shortcut chord={sendKeys} style={styles.tooltipShortcut} /> : null}
|
||||
{sendKeys ? <Shortcut chord={sendKeys} /> : null}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -1925,10 +1925,6 @@ const styles = StyleSheet.create((theme: Theme) => ({
|
||||
fontSize: theme.fontSize.sm,
|
||||
color: theme.colors.popoverForeground,
|
||||
},
|
||||
tooltipShortcut: {
|
||||
backgroundColor: theme.colors.surface3,
|
||||
borderColor: theme.colors.borderAccent,
|
||||
},
|
||||
buttonDisabled: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
|
||||
@@ -2724,10 +2724,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
projectActionTooltipShortcut: {
|
||||
backgroundColor: theme.colors.surface3,
|
||||
borderColor: theme.colors.borderAccent,
|
||||
},
|
||||
projectActionTooltipShortcut: {},
|
||||
workspaceRow: {
|
||||
minHeight: 36,
|
||||
marginBottom: theme.spacing[1],
|
||||
|
||||
@@ -126,7 +126,7 @@ interface SplitPaneDropData {
|
||||
interface SplitNodeViewProps extends Omit<SplitContainerProps, "layout" | "onMoveTabToPane"> {
|
||||
node: SplitNode;
|
||||
uiTabs: WorkspaceTab[];
|
||||
focusedPaneId: string;
|
||||
focusedPaneId: string | null;
|
||||
activeDragTabId: string | null;
|
||||
showDropZones: boolean;
|
||||
dropPreview: SplitDropZoneHover | null;
|
||||
@@ -370,7 +370,7 @@ export function SplitContainer({
|
||||
if (!focusModeEnabled) {
|
||||
return layout.root;
|
||||
}
|
||||
const focusedPane = panesById.get(layout.focusedPaneId);
|
||||
const focusedPane = layout.focusedPaneId ? panesById.get(layout.focusedPaneId) : null;
|
||||
if (!focusedPane) {
|
||||
return layout.root;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import {
|
||||
default as React,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
type ComponentType,
|
||||
type PropsWithChildren,
|
||||
type ReactElement,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { ActivityIndicator, Pressable, Text, View } from "react-native";
|
||||
import type {
|
||||
@@ -17,7 +19,7 @@ import type {
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
|
||||
type ButtonVariant = "default" | "secondary" | "outline" | "ghost" | "destructive";
|
||||
type ButtonSize = "sm" | "md" | "lg";
|
||||
type ButtonSize = "xs" | "sm" | "md" | "lg";
|
||||
|
||||
type LeftIcon =
|
||||
| ReactElement
|
||||
@@ -25,7 +27,7 @@ type LeftIcon =
|
||||
| ((color: string) => ReactElement)
|
||||
| null;
|
||||
|
||||
const ICON_SIZE: Record<ButtonSize, number> = { sm: 14, md: 16, lg: 20 };
|
||||
const ICON_SIZE: Record<ButtonSize, number> = { xs: 12, sm: 14, md: 16, lg: 20 };
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
base: {
|
||||
@@ -41,6 +43,12 @@ const styles = StyleSheet.create((theme) => ({
|
||||
paddingVertical: theme.spacing[3],
|
||||
paddingHorizontal: theme.spacing[4],
|
||||
},
|
||||
xs: {
|
||||
minHeight: 28,
|
||||
paddingVertical: theme.spacing[1],
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
borderRadius: theme.borderRadius.md,
|
||||
},
|
||||
sm: {
|
||||
paddingVertical: theme.spacing[2],
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
@@ -82,6 +90,9 @@ const styles = StyleSheet.create((theme) => ({
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
},
|
||||
textXs: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
},
|
||||
textDefault: {
|
||||
color: theme.colors.palette.white,
|
||||
},
|
||||
@@ -101,6 +112,7 @@ export function Button({
|
||||
variant = "secondary",
|
||||
size = "md",
|
||||
leftIcon,
|
||||
trailing,
|
||||
style,
|
||||
textStyle,
|
||||
disabled,
|
||||
@@ -112,6 +124,7 @@ export function Button({
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
leftIcon?: LeftIcon;
|
||||
trailing?: ReactNode;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
textStyle?: StyleProp<TextStyle>;
|
||||
loading?: boolean;
|
||||
@@ -135,7 +148,9 @@ export function Button({
|
||||
}
|
||||
|
||||
let sizeStyle: ViewStyle;
|
||||
if (size === "sm") {
|
||||
if (size === "xs") {
|
||||
sizeStyle = styles.xs;
|
||||
} else if (size === "sm") {
|
||||
sizeStyle = styles.sm;
|
||||
} else if (size === "lg") {
|
||||
sizeStyle = styles.lg;
|
||||
@@ -162,13 +177,14 @@ export function Button({
|
||||
const resolvedTextStyle = useMemo(
|
||||
() => [
|
||||
styles.text,
|
||||
size === "xs" ? styles.textXs : null,
|
||||
variant === "default" ? styles.textDefault : null,
|
||||
variant === "destructive" ? styles.textDestructive : null,
|
||||
variant === "ghost" ? styles.textGhost : null,
|
||||
textStyle,
|
||||
isGhostHovered ? styles.textGhostHovered : null,
|
||||
],
|
||||
[variant, textStyle, isGhostHovered],
|
||||
[size, variant, textStyle, isGhostHovered],
|
||||
);
|
||||
|
||||
const accessibilityState = useMemo(
|
||||
@@ -235,6 +251,7 @@ export function Button({
|
||||
>
|
||||
{renderIcon()}
|
||||
{children != null ? <Text style={resolvedTextStyle}>{children}</Text> : null}
|
||||
{trailing}
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
import { useMemo, type ReactElement } from "react";
|
||||
import React, { useMemo, type ReactElement } from "react";
|
||||
import { Text, View, type StyleProp, type TextStyle, type ViewStyle } from "react-native";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
import { formatShortcut, type ShortcutKey } from "@/utils/format-shortcut";
|
||||
import { getShortcutOs } from "@/utils/shortcut-platform";
|
||||
|
||||
function colorWithOpacity(color: string, opacity: number): string {
|
||||
const hex = color.trim();
|
||||
if (/^#[0-9a-fA-F]{3}$/.test(hex)) {
|
||||
const r = Number.parseInt(hex[1] + hex[1], 16);
|
||||
const g = Number.parseInt(hex[2] + hex[2], 16);
|
||||
const b = Number.parseInt(hex[3] + hex[3], 16);
|
||||
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
||||
}
|
||||
if (/^#[0-9a-fA-F]{6}$/.test(hex)) {
|
||||
const r = Number.parseInt(hex.slice(1, 3), 16);
|
||||
const g = Number.parseInt(hex.slice(3, 5), 16);
|
||||
const b = Number.parseInt(hex.slice(5, 7), 16);
|
||||
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
||||
}
|
||||
return `rgba(255, 255, 255, ${opacity})`;
|
||||
}
|
||||
|
||||
export function Shortcut({
|
||||
keys,
|
||||
chord,
|
||||
@@ -53,9 +70,8 @@ const styles = StyleSheet.create((theme) => ({
|
||||
paddingHorizontal: theme.spacing[1],
|
||||
paddingVertical: 2,
|
||||
borderRadius: theme.borderRadius.md,
|
||||
backgroundColor: theme.colors.surface2,
|
||||
borderWidth: theme.borderWidth[1],
|
||||
borderColor: theme.colors.borderAccent,
|
||||
backgroundColor: colorWithOpacity(theme.colors.foreground, 0.05),
|
||||
borderWidth: 0,
|
||||
},
|
||||
sequence: {
|
||||
flexDirection: "row",
|
||||
@@ -66,6 +82,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
text: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
color: theme.colors.foregroundMuted,
|
||||
color: colorWithOpacity(theme.colors.foreground, 0.6),
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user