Add keyboard shortcut to create a new workspace
Cmd+N (Ctrl+N off mac) navigates to the new-workspace route through a global handler keyed on the active host. The sidebar header row surfaces the shortcut hint on hover, replacing the redundant "+" button that duplicated the action in the Workspaces section header.
This commit is contained in:
@@ -69,6 +69,7 @@ import {
|
||||
buildSettingsRoute,
|
||||
mapPathnameToServer,
|
||||
} from "@/utils/host-routes";
|
||||
import type { ShortcutKey } from "@/utils/format-shortcut";
|
||||
import { SidebarAgentListSkeleton } from "./sidebar-agent-list-skeleton";
|
||||
import { SidebarCalloutSlot } from "./sidebar-callout-slot";
|
||||
import { SidebarWorkspaceList } from "./sidebar-workspace-list";
|
||||
@@ -112,6 +113,7 @@ interface SidebarSharedProps {
|
||||
active: boolean;
|
||||
onPress: () => void;
|
||||
}) => ReactElement;
|
||||
newWorkspaceKeys: ShortcutKey[][] | null;
|
||||
}
|
||||
|
||||
interface SidebarLabels {
|
||||
@@ -285,6 +287,8 @@ export const LeftSidebar = memo(function LeftSidebar({
|
||||
[pathname],
|
||||
);
|
||||
|
||||
const newWorkspaceKeys = useShortcutKeys("new-workspace");
|
||||
|
||||
const labels = useMemo(
|
||||
(): SidebarLabels => ({
|
||||
addProject: t("sidebar.actions.addProject"),
|
||||
@@ -320,6 +324,7 @@ export const LeftSidebar = memo(function LeftSidebar({
|
||||
handleHostSelect,
|
||||
renderHostOption,
|
||||
labels,
|
||||
newWorkspaceKeys,
|
||||
};
|
||||
|
||||
if (isCompactLayout) {
|
||||
@@ -598,6 +603,7 @@ function MobileSidebar({
|
||||
handleRefresh,
|
||||
handleHostSelect,
|
||||
renderHostOption,
|
||||
newWorkspaceKeys,
|
||||
handleNewWorkspaceNavigate,
|
||||
handleOpenProject,
|
||||
handleHome,
|
||||
@@ -809,6 +815,7 @@ function MobileSidebar({
|
||||
onPress={handleNewWorkspace}
|
||||
testID="sidebar-global-new-workspace"
|
||||
variant="compact"
|
||||
shortcutKeys={newWorkspaceKeys}
|
||||
/>
|
||||
<SidebarHeaderRow
|
||||
icon={Clock}
|
||||
@@ -819,10 +826,7 @@ function MobileSidebar({
|
||||
variant="compact"
|
||||
/>
|
||||
</View>
|
||||
<WorkspacesSectionHeader
|
||||
serverId={activeServerId}
|
||||
onNewWorkspacePress={handleNewWorkspace}
|
||||
/>
|
||||
<WorkspacesSectionHeader serverId={activeServerId} />
|
||||
<Pressable
|
||||
style={styles.mobileCloseButton}
|
||||
onPress={closeSidebar}
|
||||
@@ -904,6 +908,7 @@ function DesktopSidebar({
|
||||
handleRefresh,
|
||||
handleHostSelect,
|
||||
renderHostOption,
|
||||
newWorkspaceKeys,
|
||||
handleNewWorkspaceNavigate,
|
||||
handleOpenProject,
|
||||
handleHome,
|
||||
@@ -990,6 +995,7 @@ function DesktopSidebar({
|
||||
onPress={handleNewWorkspaceNavigate}
|
||||
testID="sidebar-global-new-workspace"
|
||||
variant="compact"
|
||||
shortcutKeys={newWorkspaceKeys}
|
||||
/>
|
||||
<SidebarHeaderRow
|
||||
icon={Clock}
|
||||
@@ -1001,10 +1007,7 @@ function DesktopSidebar({
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<WorkspacesSectionHeader
|
||||
serverId={activeServerId}
|
||||
onNewWorkspacePress={handleNewWorkspaceNavigate}
|
||||
/>
|
||||
<WorkspacesSectionHeader serverId={activeServerId} />
|
||||
|
||||
{isInitialLoad ? (
|
||||
<SidebarAgentListSkeleton />
|
||||
@@ -1050,13 +1053,7 @@ function DesktopSidebar({
|
||||
);
|
||||
}
|
||||
|
||||
function WorkspacesSectionHeader({
|
||||
serverId,
|
||||
onNewWorkspacePress,
|
||||
}: {
|
||||
serverId: string | null;
|
||||
onNewWorkspacePress: () => void;
|
||||
}) {
|
||||
function WorkspacesSectionHeader({ serverId }: { serverId: string | null }) {
|
||||
const { theme } = useUnistyles();
|
||||
const setCommandCenterOpen = useKeyboardShortcutsStore((state) => state.setCommandCenterOpen);
|
||||
const commandCenterKeys = useShortcutKeys("toggle-command-center");
|
||||
@@ -1073,29 +1070,6 @@ function WorkspacesSectionHeader({
|
||||
<View style={styles.workspacesSectionHeader}>
|
||||
<Text style={styles.workspacesSectionTitle}>Workspaces</Text>
|
||||
<View style={styles.workspacesSectionActions}>
|
||||
<Tooltip delayDuration={300}>
|
||||
<TooltipTrigger asChild>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="New workspace"
|
||||
testID="sidebar-new-workspace"
|
||||
style={searchButtonStyle}
|
||||
onPress={onNewWorkspacePress}
|
||||
>
|
||||
{({ hovered, pressed }) => (
|
||||
<Plus
|
||||
size={14}
|
||||
color={
|
||||
hovered || pressed ? theme.colors.foreground : theme.colors.foregroundMuted
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" align="center" offset={8}>
|
||||
<HeaderIconTooltipContent label="New workspace" />
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip delayDuration={300}>
|
||||
<TooltipTrigger asChild>
|
||||
<Pressable
|
||||
|
||||
@@ -5,6 +5,8 @@ import type { LucideIcon } from "lucide-react-native";
|
||||
import { HEADER_INNER_HEIGHT, HEADER_INNER_HEIGHT_MOBILE } from "@/constants/layout";
|
||||
import { ICON_SIZE } from "@/styles/theme";
|
||||
import type { Theme } from "@/styles/theme";
|
||||
import { Shortcut } from "@/components/ui/shortcut";
|
||||
import type { ShortcutKey } from "@/utils/format-shortcut";
|
||||
|
||||
const foregroundColorMapping = (theme: Theme) => ({ color: theme.colors.foreground });
|
||||
const foregroundMutedColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted });
|
||||
@@ -26,6 +28,7 @@ interface SidebarHeaderRowProps {
|
||||
* sit in a header group whose wrapper owns the single divider.
|
||||
*/
|
||||
variant?: SidebarHeaderRowVariant;
|
||||
shortcutKeys?: ShortcutKey[][] | null;
|
||||
}
|
||||
|
||||
export function SidebarHeaderRow({
|
||||
@@ -37,6 +40,7 @@ export function SidebarHeaderRow({
|
||||
nativeID,
|
||||
accessibilityLabel,
|
||||
variant = "header",
|
||||
shortcutKeys = null,
|
||||
}: SidebarHeaderRowProps) {
|
||||
const ThemedIcon = useMemo(() => withUnistyles(Icon), [Icon]);
|
||||
|
||||
@@ -63,10 +67,13 @@ export function SidebarHeaderRow({
|
||||
uniProps={isHighlighted ? foregroundColorMapping : foregroundMutedColorMapping}
|
||||
/>
|
||||
<SidebarHeaderRowLabel label={label} isHighlighted={isHighlighted} />
|
||||
{shortcutKeys && Boolean(state.hovered) ? (
|
||||
<Shortcut chord={shortcutKeys} style={styles.shortcut} />
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
},
|
||||
[ThemedIcon, isActive, label],
|
||||
[ThemedIcon, isActive, label, shortcutKeys],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -139,4 +146,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
labelHighlighted: {
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
shortcut: {
|
||||
marginLeft: "auto",
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user