Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
b210bf5
Initial hide/show the side menu with toggle switch
samejr Jan 20, 2026
a13952b
Move Waitpoint tokens into the main list
samejr Jan 20, 2026
ae6cde0
Nicer styling for the toggle button
samejr Jan 20, 2026
b4735c2
Animate collapsing the side menu
samejr Jan 20, 2026
db17007
Improvements to the section dividers
samejr Jan 20, 2026
66fdf23
Use framer motion instead to animate the svg
samejr Jan 21, 2026
999c8d1
Animate the opacity too
samejr Jan 21, 2026
2376c52
Improves the menu item widths in the collapsed state
samejr Jan 21, 2026
aa6f192
Adds shortcut to toggle side menu
samejr Jan 21, 2026
ae6abd7
Fixes width issue with environment selector
samejr Jan 21, 2026
e086fe1
Smooth transition the env selector
samejr Jan 21, 2026
dcc5d0f
Adds trailingIcon props
samejr Jan 21, 2026
2280bab
Improvements to the help and feedback button transition
samejr Jan 21, 2026
8872d47
Adds disableHoverableContent
samejr Jan 21, 2026
575a815
Make shortcut work when inputs are focused
samejr Jan 21, 2026
03e808b
FIxes help and feedback button transition
samejr Jan 21, 2026
c06255e
Adds a tooltip to the help and feedback button
samejr Jan 21, 2026
bc9047b
Fixes alignment of the help and feedback menu items
samejr Jan 21, 2026
2188f3e
adds shortcut icon to the help and feedback tooltip
samejr Jan 21, 2026
0485be6
Adds the incident panel to the collapsed state
samejr Jan 21, 2026
84b160f
Adds disable tooltip prop
samejr Jan 22, 2026
d7ade2a
Merge remote-tracking branch 'origin/main' into feat(webapp)-collapsi…
samejr Jan 24, 2026
9ff48b4
Remove v4 badge
samejr Jan 24, 2026
91d852a
Keep the askAI button visible when collapsed
samejr Jan 24, 2026
ac20a5e
Make sure the side menu can scroll
samejr Jan 24, 2026
cc879af
Animate the incident panel
samejr Jan 24, 2026
ff65c75
Colored icons for main features
samejr Jan 24, 2026
702f630
Fix for full width env selector
samejr Jan 24, 2026
89f86f1
Save the toggle states in dashboardPreferences
samejr Jan 24, 2026
2303ffe
hide divide line
samejr Jan 24, 2026
109b1ed
Fixes type error with the dashboardPreferences implementation
samejr Jan 24, 2026
9b7f500
Help button stretches to fit space
samejr Jan 24, 2026
a738e38
Adds Ask AI button to account and org side menus
samejr Jan 24, 2026
8d98bd7
Adds custom focus
samejr Jan 24, 2026
1119bd1
Coderabbit markup restructure suggestion
samejr Jan 25, 2026
53528c6
Coderabbit improvement for the deboucing
samejr Jan 25, 2026
6dc6baa
Small coderabbit fixes
samejr Jan 25, 2026
f7fcedf
Merge branch 'main' into feat(webapp)-collapsible-side-menu
samejr Jan 25, 2026
5b7c900
Set defaults in the schema
samejr Jan 26, 2026
9b477dc
Fixes a devin suggestion for calling a react hook too early
samejr Jan 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 37 additions & 24 deletions apps/webapp/app/components/AskAI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
HandThumbUpIcon,
StopIcon,
} from "@heroicons/react/20/solid";
import { cn } from "~/utils/cn";
import { type FeedbackComment, KapaProvider, type QA, useChat } from "@kapaai/react-sdk";
import { useSearchParams } from "@remix-run/react";
import DOMPurify from "dompurify";
Expand Down Expand Up @@ -37,7 +38,7 @@ function useKapaWebsiteId() {
return routeMatch?.kapa.websiteId;
}

export function AskAI() {
export function AskAI({ isCollapsed = false }: { isCollapsed?: boolean }) {
const { isManagedCloud } = useFeatures();
const websiteId = useKapaWebsiteId();

Expand All @@ -54,21 +55,23 @@ export function AskAI() {
hideShortcutKey
data-modal-override-open-class-ask-ai="true"
disabled
className={isCollapsed ? "w-full justify-center" : ""}
>
<AISparkleIcon className="size-5" />
</Button>
}
>
{() => <AskAIProvider websiteId={websiteId} />}
{() => <AskAIProvider websiteId={websiteId} isCollapsed={isCollapsed} />}
</ClientOnly>
);
}

type AskAIProviderProps = {
websiteId: string;
isCollapsed?: boolean;
};

function AskAIProvider({ websiteId }: AskAIProviderProps) {
function AskAIProvider({ websiteId, isCollapsed = false }: AskAIProviderProps) {
const [isOpen, setIsOpen] = useState(false);
const [initialQuery, setInitialQuery] = useState<string | undefined>();
const [searchParams, setSearchParams] = useSearchParams();
Expand Down Expand Up @@ -112,28 +115,38 @@ function AskAIProvider({ websiteId }: AskAIProviderProps) {
}}
botProtectionMechanism="hcaptcha"
>
<TooltipProvider disableHoverableContent>
<Tooltip>
<TooltipTrigger asChild>
<div className="inline-flex">
<Button
variant="small-menu-item"
data-action="ask-ai"
shortcut={{ modifiers: ["mod"], key: "/", enabledOnInputElements: true }}
hideShortcutKey
data-modal-override-open-class-ask-ai="true"
onClick={() => openAskAI()}
>
<AISparkleIcon className="size-5" />
</Button>
<motion.div layout="position" transition={{ duration: 0.2, ease: "easeInOut" }}>
<TooltipProvider disableHoverableContent>
<Tooltip>
<div className={isCollapsed ? "w-full" : "inline-flex"}>
<TooltipTrigger asChild>
<Button
variant="small-menu-item"
data-action="ask-ai"
shortcut={{ modifiers: ["mod"], key: "/", enabledOnInputElements: true }}
hideShortcutKey
data-modal-override-open-class-ask-ai="true"
onClick={() => openAskAI()}
className={isCollapsed ? "w-full justify-center" : ""}
>
<AISparkleIcon className="size-5" />
</Button>
</TooltipTrigger>
</div>
</TooltipTrigger>
<TooltipContent side="top" className="flex items-center gap-1 py-1.5 pl-2.5 pr-2 text-xs">
Ask AI
<ShortcutKey shortcut={{ modifiers: ["mod"], key: "/" }} variant="medium/bright" />
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipContent
side={isCollapsed ? "right" : "top"}
sideOffset={isCollapsed ? 8 : 4}
className="flex items-center gap-1 py-1.5 pl-2.5 pr-2 text-xs"
>
Ask AI
<span className="flex items-center">
<ShortcutKey shortcut={{ modifiers: ["mod"] }} variant="medium/bright" />
<ShortcutKey shortcut={{ key: "/" }} variant="medium/bright" />
</span>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</motion.div>
<AskAIDialog
initialQuery={initialQuery}
isOpen={isOpen}
Expand Down
17 changes: 10 additions & 7 deletions apps/webapp/app/components/Shortcuts.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { Keyboard } from "lucide-react";
import { useState } from "react";
import { useShortcutKeys } from "~/hooks/useShortcutKeys";
import { Button } from "./primitives/Buttons";
import { Header3 } from "./primitives/Headers";
import { Paragraph } from "./primitives/Paragraph";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
SheetTrigger
} from "./primitives/SheetV3";
import { ShortcutKey } from "./primitives/ShortcutKey";
import { Button } from "./primitives/Buttons";
import { useState } from "react";
import { useShortcutKeys } from "~/hooks/useShortcutKeys";

export function Shortcuts() {
return (
Expand All @@ -26,8 +25,8 @@ export function Shortcuts() {
fullWidth
textAlignLeft
shortcut={{ modifiers: ["shift"], key: "?", enabled: false }}
className="gap-x-0 pl-0.5"
iconSpacing="gap-x-0.5"
className="gap-x-0 pl-1.5"
iconSpacing="gap-x-1.5"
>
Shortcuts
</Button>
Expand Down Expand Up @@ -82,6 +81,10 @@ function ShortcutContent() {
<Shortcut name="Filter">
<ShortcutKey shortcut={{ key: "f" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Toggle side menu">
<ShortcutKey shortcut={{ modifiers: ["mod"]}} variant="medium/bright" />
<ShortcutKey shortcut={{ key: "b" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Select filter">
<ShortcutKey shortcut={{ key: "1" }} variant="medium/bright" />
<Paragraph variant="small" className="ml-1.5">
Expand Down
4 changes: 3 additions & 1 deletion apps/webapp/app/components/environments/EnvironmentLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,13 @@ export function EnvironmentLabel({
className,
tooltipSideOffset = 34,
tooltipSide = "right",
disableTooltip = false,
}: {
environment: Environment;
className?: string;
tooltipSideOffset?: number;
tooltipSide?: "top" | "right" | "bottom" | "left";
disableTooltip?: boolean;
}) {
const spanRef = useRef<HTMLSpanElement>(null);
const [isTruncated, setIsTruncated] = useState(false);
Expand Down Expand Up @@ -117,7 +119,7 @@ export function EnvironmentLabel({
</span>
);

if (isTruncated) {
if (isTruncated && !disableTooltip) {
return (
<SimpleTooltip
asChild
Expand Down
6 changes: 4 additions & 2 deletions apps/webapp/app/components/navigation/AccountSideMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import {
personalAccessTokensPath,
rootPath,
} from "~/utils/pathBuilder";
import { AskAI } from "../AskAI";
import { LinkButton } from "../primitives/Buttons";
import { HelpAndFeedback } from "./HelpAndFeedbackPopover";
import { SideMenuHeader } from "./SideMenuHeader";
import { SideMenuItem } from "./SideMenuItem";
import { HelpAndFeedback } from "./HelpAndFeedbackPopover";

export function AccountSideMenu({ user }: { user: User }) {
return (
Expand Down Expand Up @@ -55,8 +56,9 @@ export function AccountSideMenu({ user }: { user: User }) {
data-action="security"
/>
</div>
<div className="flex flex-col gap-1 border-t border-grid-bright p-1">
<div className="flex w-full items-center justify-between border-t border-grid-bright p-1">
<HelpAndFeedback />
<AskAI />
</div>
</div>
);
Expand Down
55 changes: 45 additions & 10 deletions apps/webapp/app/components/navigation/EnvironmentSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ChevronRightIcon, Cog8ToothIcon } from "@heroicons/react/20/solid";
import { DropdownIcon } from "~/assets/icons/DropdownIcon";
import { useNavigation } from "@remix-run/react";
import { useEffect, useRef, useState } from "react";
import { BranchEnvironmentIconSmall } from "~/assets/icons/EnvironmentIcons";
Expand All @@ -9,19 +10,19 @@ import { useOrganization, type MatchedOrganization } from "~/hooks/useOrganizati
import { useProject } from "~/hooks/useProject";
import { cn } from "~/utils/cn";
import { branchesPath, docsPath, v3BillingPath } from "~/utils/pathBuilder";
import { EnvironmentCombo } from "../environments/EnvironmentLabel";
import { EnvironmentCombo, EnvironmentIcon, EnvironmentLabel, environmentFullTitle } from "../environments/EnvironmentLabel";
import { ButtonContent } from "../primitives/Buttons";
import { Header2 } from "../primitives/Headers";
import { Paragraph } from "../primitives/Paragraph";
import {
Popover,
PopoverArrowTrigger,
PopoverContent,
PopoverMenuItem,
PopoverSectionHeader,
PopoverTrigger,
} from "../primitives/Popover";
import { TextLink } from "../primitives/TextLink";
import { SimpleTooltip } from "../primitives/Tooltip";
import { V4Badge } from "../V4Badge";
import { type SideMenuEnvironment, type SideMenuProject } from "./SideMenu";
import { Badge } from "../primitives/Badge";
Expand All @@ -31,11 +32,13 @@ export function EnvironmentSelector({
project,
environment,
className,
isCollapsed = false,
}: {
organization: MatchedOrganization;
project: SideMenuProject;
environment: SideMenuEnvironment;
className?: string;
isCollapsed?: boolean;
}) {
const { isManagedCloud } = useFeatures();
const [isMenuOpen, setIsMenuOpen] = useState(false);
Expand All @@ -50,16 +53,48 @@ export function EnvironmentSelector({

return (
<Popover onOpenChange={(open) => setIsMenuOpen(open)} open={isMenuOpen}>
<PopoverArrowTrigger
isOpen={isMenuOpen}
overflowHidden
fullWidth
className={cn("h-7 overflow-hidden py-1 pl-1.5", className)}
>
<EnvironmentCombo environment={environment} className="w-full text-2sm" />
</PopoverArrowTrigger>
<SimpleTooltip
button={
<PopoverTrigger
className={cn(
"group flex h-8 items-center rounded pl-[0.4375rem] transition-colors hover:bg-charcoal-750",
isCollapsed ? "justify-center pr-0.5" : "justify-between pr-1",
className
)}
>
<span className="flex min-w-0 flex-1 items-center gap-1.5 overflow-hidden">
<EnvironmentIcon environment={environment} className="size-5 shrink-0" />
<span
className={cn(
"flex min-w-0 items-center overflow-hidden transition-all duration-200",
isCollapsed ? "max-w-0 opacity-0" : "max-w-[200px] opacity-100"
)}
>
<EnvironmentLabel environment={environment} className="text-2sm" disableTooltip />
</span>
</span>
<span
className={cn(
"overflow-hidden transition-all duration-200",
isCollapsed ? "max-w-0 opacity-0" : "max-w-[16px] opacity-100"
)}
>
<DropdownIcon className="size-4 min-w-4 text-text-dimmed transition group-hover:text-text-bright" />
</span>
</PopoverTrigger>
}
content={environmentFullTitle(environment)}
side="right"
sideOffset={8}
hidden={!isCollapsed}
buttonClassName="!h-8"
asChild
disableHoverableContent
/>
<PopoverContent
className="min-w-[14rem] overflow-y-auto p-0 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"
side={isCollapsed ? "right" : "bottom"}
sideOffset={isCollapsed ? 8 : 4}
align="start"
style={{ maxHeight: `calc(var(--radix-popover-content-available-height) - 10vh)` }}
>
Expand Down
Loading