mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-26 19:22:51 +00:00
Show press feedback on sidebar rows, footer buttons, and settings items (#7150)
* fix(app): show press feedback on sidebar rows, footer icons, and settings tiles Touch has no hover, so these controls gave no acknowledgement when pressed. Rows take the workspace row's pressed fill; the sidebar header row paints it on the row so the trailing slot lights up with the button. Footer icon buttons take the round header-control highlight. * style(app): format sidebar header row * fix(app): keep footer icon corners tappable on web Web hit-testing follows border-radius, so rounding the footer buttons turned their 44pt square targets into circles and taps near the corners fell through. Paint the round highlight as a layer inside the square button instead.
This commit is contained in:
@@ -32,6 +32,7 @@ import { HostPicker } from "@/components/hosts/host-picker";
|
||||
import { SidebarDisplayPreferencesMenu } from "@/components/sidebar/display-preferences/menu";
|
||||
import { SidebarSeparator } from "@/components/sidebar/sidebar-separator";
|
||||
import { SidebarNavRows } from "@/components/sidebar/sidebar-nav-rows";
|
||||
import { SidebarFooterIconHighlight } from "@/components/sidebar/sidebar-footer-icon-highlight";
|
||||
import { SidebarHelpMenu } from "@/components/sidebar/sidebar-help-menu";
|
||||
import { SidebarResizeHandle } from "@/components/sidebar-resize-handle";
|
||||
import { DensityIcon } from "@/components/ui/density-icon";
|
||||
@@ -317,13 +318,16 @@ function FooterIconButton({
|
||||
accessibilityRole="button"
|
||||
onPress={onPress}
|
||||
>
|
||||
{({ hovered }) => (
|
||||
<DensityIcon
|
||||
icon={icon}
|
||||
size="md"
|
||||
sizeAdjustment={iconSizeAdjustment}
|
||||
color={hovered ? "foreground" : "foregroundMuted"}
|
||||
/>
|
||||
{({ hovered, pressed }) => (
|
||||
<>
|
||||
<SidebarFooterIconHighlight visible={pressed} />
|
||||
<DensityIcon
|
||||
icon={icon}
|
||||
size="md"
|
||||
sizeAdjustment={iconSizeAdjustment}
|
||||
color={hovered ? "foreground" : "foregroundMuted"}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Pressable>
|
||||
</TooltipTrigger>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { View } from "react-native";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
|
||||
/**
|
||||
* The round highlight behind a sidebar footer icon. It is a layer inside the button rather than a
|
||||
* radius on it: web hit-testing follows `border-radius`, so a round button would stop taking taps
|
||||
* near the corners of its square finger-sized target.
|
||||
*/
|
||||
export function SidebarFooterIconHighlight({ visible }: { visible: boolean }) {
|
||||
return <View pointerEvents="none" style={[styles.highlight, visible && styles.visible]} />;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
highlight: {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
borderRadius: theme.borderRadius.full,
|
||||
},
|
||||
visible: {
|
||||
backgroundColor: theme.colors.interactionHighlight,
|
||||
},
|
||||
}));
|
||||
@@ -54,6 +54,10 @@ export function SidebarHeaderRow({
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const handlePointerEnter = useCallback(() => setIsHovered(true), []);
|
||||
const handlePointerLeave = useCallback(() => setIsHovered(false), []);
|
||||
// The button and its trailing slot are siblings, so the row owns the pressed fill for both.
|
||||
const [isPressed, setIsPressed] = useState(false);
|
||||
const handlePressIn = useCallback(() => setIsPressed(true), []);
|
||||
const handlePressOut = useCallback(() => setIsPressed(false), []);
|
||||
const isHighlighted = isHovered || isActive;
|
||||
const iconSize = variant === "header" ? "md" : "sm";
|
||||
|
||||
@@ -65,12 +69,14 @@ export function SidebarHeaderRow({
|
||||
return (
|
||||
<View ref={rowRef} collapsable={false} style={getContainerStyle(variant)}>
|
||||
<View
|
||||
style={[styles.row, isHighlighted && styles.rowHighlighted]}
|
||||
style={[styles.row, isHighlighted && styles.rowHighlighted, isPressed && styles.rowPressed]}
|
||||
onPointerEnter={handlePointerEnter}
|
||||
onPointerLeave={handlePointerLeave}
|
||||
>
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
onPressIn={handlePressIn}
|
||||
onPressOut={handlePressOut}
|
||||
testID={testID}
|
||||
nativeID={nativeID}
|
||||
accessible
|
||||
@@ -92,7 +98,14 @@ export function SidebarHeaderRow({
|
||||
<Text style={[styles.label, isHighlighted && styles.labelHighlighted]}>{label}</Text>
|
||||
</Pressable>
|
||||
{right === null ? null : (
|
||||
<Pressable onPress={onPress} accessible={false} focusable={false} style={styles.trailing}>
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
onPressIn={handlePressIn}
|
||||
onPressOut={handlePressOut}
|
||||
accessible={false}
|
||||
focusable={false}
|
||||
style={styles.trailing}
|
||||
>
|
||||
{right}
|
||||
</Pressable>
|
||||
)}
|
||||
@@ -136,6 +149,10 @@ const styles = StyleSheet.create((theme) => ({
|
||||
rowHighlighted: {
|
||||
backgroundColor: theme.colors.surfaceSidebarHover,
|
||||
},
|
||||
// The workspace rows' pressed fill.
|
||||
rowPressed: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
button: {
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { DensityIcon } from "@/components/ui/density-icon";
|
||||
import { SidebarFooterIconHighlight } from "./sidebar-footer-icon-highlight";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { useAppDiagnosticStore } from "@/diagnostics/store";
|
||||
import { useKeyboardShortcutsAvailable } from "@/keyboard/availability";
|
||||
@@ -108,12 +109,15 @@ export function SidebarHelpMenu() {
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t("sidebar.help.trigger")}
|
||||
>
|
||||
{({ hovered }) => (
|
||||
<DensityIcon
|
||||
icon={CircleHelp}
|
||||
size="md"
|
||||
color={hovered ? "foreground" : "foregroundMuted"}
|
||||
/>
|
||||
{({ hovered, pressed, open: menuOpen }) => (
|
||||
<>
|
||||
<SidebarFooterIconHighlight visible={pressed || menuOpen} />
|
||||
<DensityIcon
|
||||
icon={CircleHelp}
|
||||
size="md"
|
||||
color={hovered ? "foreground" : "foregroundMuted"}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuTrigger>
|
||||
</View>
|
||||
|
||||
@@ -275,15 +275,26 @@ function renderHostSettingsContent(
|
||||
// Trigger + sidebar style helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function sidebarItemStyle({ hovered }: PressableStateCallbackType & { hovered?: boolean }) {
|
||||
return [sidebarStyles.item, Boolean(hovered) && sidebarStyles.itemHovered];
|
||||
function sidebarItemStyle({
|
||||
hovered,
|
||||
pressed,
|
||||
}: PressableStateCallbackType & { hovered?: boolean }) {
|
||||
return [
|
||||
sidebarStyles.item,
|
||||
Boolean(hovered) && sidebarStyles.itemHovered,
|
||||
pressed && sidebarStyles.itemPressed,
|
||||
];
|
||||
}
|
||||
|
||||
function selectedSidebarItemStyle({ hovered }: PressableStateCallbackType & { hovered?: boolean }) {
|
||||
function selectedSidebarItemStyle({
|
||||
hovered,
|
||||
pressed,
|
||||
}: PressableStateCallbackType & { hovered?: boolean }) {
|
||||
return [
|
||||
sidebarStyles.item,
|
||||
Boolean(hovered) && sidebarStyles.itemHovered,
|
||||
sidebarStyles.itemSelected,
|
||||
pressed && sidebarStyles.itemPressed,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -865,9 +876,10 @@ function HostPicker({
|
||||
[],
|
||||
);
|
||||
const triggerStyle = useCallback(
|
||||
({ hovered = false }: PressableStateCallbackType & { hovered?: boolean }) => [
|
||||
({ hovered = false, pressed }: PressableStateCallbackType & { hovered?: boolean }) => [
|
||||
sidebarStyles.pickerTrigger,
|
||||
hovered && sidebarStyles.pickerTriggerHovered,
|
||||
pressed && sidebarStyles.itemPressed,
|
||||
],
|
||||
[],
|
||||
);
|
||||
@@ -1615,6 +1627,10 @@ const sidebarStyles = StyleSheet.create((theme) => ({
|
||||
itemSelected: {
|
||||
backgroundColor: theme.colors.surfaceSidebarHover,
|
||||
},
|
||||
// The workspace rows' pressed fill.
|
||||
itemPressed: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
label: {
|
||||
fontSize: { xs: theme.fontSize.lg, md: theme.fontSize.base },
|
||||
color: theme.colors.foregroundMuted,
|
||||
|
||||
Reference in New Issue
Block a user