mirror of
https://github.com/coder/coder.git
synced 2026-11-08 05:32:55 +08:00
closes DES-22030
## Summary
Mobile view cleanup for the agents page — all changes are behind the
`md:` breakpoint so desktop is unchanged.
**Dropdowns:** Full-width on mobile with dynamic positioning via a
`--mobile-dropdown-bottom` CSS custom property set by a `ResizeObserver`
on the chat input box. Three position variants: `-bottom` (above chat
input), `-top` (below header), `-top-below-header` (below sidebar
header). Viewport branching uses a new `isBelowMdViewport()` helper (`<
768px`) so 640–767 px landscape phones pick the mobile branch instead of
the desktop flyout.
**Layout:** On the main agents page, mobile ordering is header → chat
list → chat input using CSS `order` and `contents` on the content
wrapper. The chat input aligns to the bottom of available space. The
sidebar list uses a top/bottom fade mask on mobile to hint at scrollable
content.
**Header:** Settings, Analytics, sound, and notification icons
consolidated into a single meatball menu dropdown on mobile.
Sound/notification toggles use `e.preventDefault()` to keep the menu
open for state feedback. Chime and notification state is lifted into
`AgentCreatePage` and passed down, so the mobile meatball menu and the
desktop `ChimeButton`/`WebPushButton` stay in sync.
**Workspace pill:** Icon-only on mobile (`size-7` round button with
`StatusIcon`), full pill on desktop. Tooltip hidden on mobile to prevent
ghost tooltip after dropdown close.
**Plus menu:** Workspace picker replaces the flyout with an inline
sub-panel on mobile (back button + search list). Desktop flyout
unchanged. `modal={false}` prevents double-tap when switching between
dropdowns.
**Model selector:** Truncated via `shrink` + `min-w-0` on mobile
(flex-based, no fixed max-width), inline provider/context subtext per
item, tooltip hidden on mobile. Added `open` / `onOpenChange` /
`onTriggerTouchStart` props for external control.
**Consistency:** All back/close buttons normalized to `ArrowLeftIcon`.
Right panel, sidebar settings, header `mobileBack`, and workspace
sub-panel all match the chat top bar pattern.
**Misc polish:** Chat tree nodes use `select-none` +
`-webkit-touch-callout:none` on coarse pointers to suppress the
long-press selection/callout on mobile.
<details>
<summary>Files changed (18)</summary>
- `site/src/index.css` — mobile dropdown CSS with 3 position variants
- `site/src/utils/mobile.ts` — new `isBelowMdViewport()` helper
(`<768px`)
- `site/src/pages/AgentsPage/AgentChatPageView.tsx` — bottom padding
`pb-3`
- `site/src/pages/AgentsPage/AgentCreatePage.tsx` — lift chime + webpush
state; pass handlers to header and buttons
- `site/src/pages/AgentsPage/AgentsPageView.tsx` — `contents` wrapper +
sidebar `border-b`
- `site/src/pages/AgentsPage/components/AgentChatInput.tsx` —
`ResizeObserver` composer ref, `plusMenuView` state, inline workspace
picker, `modal={false}`, mobile branching via `isBelowMdViewport`, bg
- `site/src/pages/AgentsPage/components/AgentCreateForm.tsx` —
`order-last` + `items-end` on mobile
- `site/src/pages/AgentsPage/components/AgentPageHeader.tsx` — meatball
menu (controlled chime/webpush props), `ArrowLeftIcon`, `order-first`,
padding, desktop/mobile branching via `matchMedia`
- `site/src/pages/AgentsPage/components/AgentPageHeader.stories.tsx` —
new Storybook coverage + `play` assertions that toggle state stays in
sync across breakpoints
- `site/src/pages/AgentsPage/components/ChimeButton.tsx` — optional
controlled `enabled` / `onToggle` props
- `site/src/pages/AgentsPage/components/WebPushButton.tsx` — optional
controlled `webPush` / `onToggle` props
-
`site/src/pages/AgentsPage/components/ChatElements/CompactOrgSelector.tsx`
— full-width dropdown class
- `site/src/pages/AgentsPage/components/ChatElements/ModelSelector.tsx`
— truncation, inline subtext, tooltip hidden on mobile, new props
(`open`, `onOpenChange`, `onTriggerTouchStart`)
- `site/src/pages/AgentsPage/components/ChatTopBar.tsx` — full-width
dropdown class
- `site/src/pages/AgentsPage/components/ContextUsageIndicator.tsx` —
full-width dropdown class (mobile branch)
- `site/src/pages/AgentsPage/components/Sidebar/AgentsSidebar.tsx` —
`ArrowLeftIcon`, filter dropdown class, top/bottom fade mask on scroll
area, `select-none` on tree nodes
- `site/src/pages/AgentsPage/components/Sidebar/SidebarTabView.tsx` —
`ArrowLeftIcon`, padding, back button placement
- `site/src/pages/AgentsPage/components/WorkspacePill.tsx` — compact
icon trigger, tooltip hidden on mobile, full-width dropdown class
</details>
> 🤖 Generated by Coder Agents
---------
Co-authored-by: Jaayden Halko <jaayden@coder.com>
328 lines
9.4 KiB
CSS
328 lines
9.4 KiB
CSS
/*
|
||
To enable opacity in colors, please use the new HSL format.
|
||
Related issue: https://github.com/shadcn-ui/ui/issues/805#issuecomment-1616021820
|
||
*/
|
||
|
||
@tailwind base;
|
||
@tailwind components;
|
||
@tailwind utilities;
|
||
|
||
@font-face {
|
||
font-family: "seti";
|
||
src: url("/seti.woff") format("woff");
|
||
font-weight: normal;
|
||
font-style: normal;
|
||
font-display: swap;
|
||
}
|
||
|
||
@layer base {
|
||
:root,
|
||
.light {
|
||
--content-primary: 240 10% 4%;
|
||
--content-secondary: 240 5% 34%;
|
||
--content-link: 221 83% 53%;
|
||
--content-invert: 0 0% 98%;
|
||
--content-disabled: 240 5% 65%;
|
||
--content-success: 142 72% 29%;
|
||
--content-warning: 27 96% 61%;
|
||
--content-destructive: 0 84% 60%;
|
||
--surface-primary: 0 0% 100%;
|
||
--surface-secondary: 240 5% 96%;
|
||
--surface-tertiary: 240 6% 90%;
|
||
--surface-quaternary: 240 5% 84%;
|
||
--surface-invert-primary: 240 4% 16%;
|
||
--surface-invert-secondary: 240 5% 26%;
|
||
--surface-destructive: 0 93% 94%;
|
||
--surface-green: 141 79% 85%;
|
||
--surface-grey: 240 5% 96%;
|
||
--surface-orange: 34 100% 92%;
|
||
--surface-sky: 191, 67%, 89%;
|
||
--surface-red: 0 93% 94%;
|
||
--surface-purple: 259, 100%, 95%;
|
||
--surface-magenta: 289 100% 98%;
|
||
--border-purple: 255 92% 76%;
|
||
--border-default: 240 6% 90%;
|
||
--border-success: 142 76% 36%;
|
||
--border-sky: 203 90% 40%;
|
||
--border-green: 138 82% 82%;
|
||
--border-magenta: 295, 68%, 46%;
|
||
--border-warning: 30.66, 97.16%, 72.35%;
|
||
--border-destructive: 0 84% 60%;
|
||
--border-secondary: 240 5% 65%;
|
||
--overlay-default: 240 5% 84% / 80%;
|
||
--radius: 0.5rem;
|
||
--highlight-purple: 271, 61%, 35%;
|
||
--highlight-green: 143 64% 24%;
|
||
--highlight-orange: 30 100% 32%;
|
||
--highlight-sky: 195, 61%, 22%;
|
||
--highlight-red: 0 74% 42%;
|
||
--highlight-magenta: 295, 68%, 40%;
|
||
--syntax-key: 211 95% 33%;
|
||
--syntax-string: 0 77% 36%;
|
||
--syntax-number: 158 88% 28%;
|
||
--syntax-boolean: 240 100% 50%;
|
||
--git-added: 142 72% 29%;
|
||
--git-deleted: 0 74% 42%;
|
||
--git-modified: 17 88% 40%;
|
||
--git-merged: 271 91% 65%;
|
||
--git-added-bright: 142 72% 29%;
|
||
--git-deleted-bright: 0 74% 42%;
|
||
--git-merged-bright: 272 72% 47%;
|
||
--surface-git-added: 141 84% 93%;
|
||
--surface-git-deleted: 0 93% 94%;
|
||
--surface-git-merged: 269 100% 95%;
|
||
--border: 240 5.9% 90%;
|
||
--input: 240 5.9% 90%;
|
||
--ring: 240 10% 3.9%;
|
||
/*
|
||
shadcn-compatible aliases consumed by streamdown and
|
||
internal components (e.g. shimmer). These names are
|
||
intentionally generic so they match shadcn conventions.
|
||
Do NOT rename without updating every consumer.
|
||
*/
|
||
--background: var(--surface-primary);
|
||
--foreground: var(--content-primary);
|
||
--muted: var(--surface-secondary);
|
||
--muted-foreground: var(--content-secondary);
|
||
--primary: var(--content-link);
|
||
--primary-foreground: var(--surface-primary);
|
||
--avatar-lg: 2.5rem;
|
||
--avatar-default: 2rem;
|
||
--avatar-sm: 1.5rem;
|
||
}
|
||
.dark {
|
||
--content-primary: 0 0% 100%;
|
||
--content-secondary: 240 5% 65%;
|
||
--content-link: 213 94% 68%;
|
||
--content-invert: 240 10% 4%;
|
||
--content-disabled: 240 5% 26%;
|
||
--content-success: 142 76% 36%;
|
||
--content-warning: 31 97% 72%;
|
||
--content-destructive: 0 91% 71%;
|
||
--surface-primary: 240 10% 4%;
|
||
--surface-secondary: 240 6% 10%;
|
||
--surface-tertiary: 240 4% 16%;
|
||
--surface-quaternary: 240 5% 26%;
|
||
--surface-invert-primary: 240 6% 90%;
|
||
--surface-invert-secondary: 240 5% 65%;
|
||
--surface-destructive: 0 75% 15%;
|
||
--surface-green: 145 80% 10%;
|
||
--surface-grey: 240 6% 10%;
|
||
--surface-orange: 13 81% 15%;
|
||
--surface-sky: 196, 67%, 12%;
|
||
--surface-red: 0 75% 15%;
|
||
--surface-purple: 268, 68%, 17%;
|
||
--surface-magenta: 291, 74%, 15%;
|
||
--border-purple: 255 92% 76%;
|
||
--border-default: 240 4% 16%;
|
||
--border-success: 142 76% 36%;
|
||
--border-magenta: 292, 100%, 78%;
|
||
--border-warning: 30.66, 97.16%, 72.35%;
|
||
--border-destructive: 0 91% 71%;
|
||
--border-sky: 194 90% 62%;
|
||
--border-green: 143 77% 87%;
|
||
--border-secondary: 240 5% 26%;
|
||
--overlay-default: 240 10% 4% / 80%;
|
||
--highlight-purple: 269, 100%, 74%;
|
||
--highlight-green: 141 79% 85%;
|
||
--highlight-orange: 31 100% 70%;
|
||
--highlight-sky: 188, 75%, 80%;
|
||
--highlight-red: 0 91% 71%;
|
||
--highlight-magenta: 292, 100%, 78%;
|
||
--syntax-key: 201 98% 80%;
|
||
--syntax-string: 18 47% 64%;
|
||
--syntax-number: 100 28% 73%;
|
||
--syntax-boolean: 207 61% 59%;
|
||
--git-added: 142 77% 73%;
|
||
--git-deleted: 0 94% 82%;
|
||
--git-modified: 31 97% 72%;
|
||
--git-merged: 271 91% 65%;
|
||
--git-added-bright: 142 71% 45%;
|
||
--git-deleted-bright: 0 91% 71%;
|
||
--git-merged-bright: 270 95% 75%;
|
||
--surface-git-added: 145 80% 10%;
|
||
--surface-git-deleted: 0 75% 15%;
|
||
--surface-git-merged: 274 87% 21%;
|
||
--border: 240 3.7% 15.9%;
|
||
--input: 240 3.7% 15.9%;
|
||
--ring: 240 4.9% 83.9%;
|
||
/* shadcn-compatible aliases – see :root block for details. */
|
||
--background: var(--surface-primary);
|
||
--foreground: var(--content-primary);
|
||
--muted: var(--surface-secondary);
|
||
--muted-foreground: var(--content-secondary);
|
||
--primary: var(--content-link);
|
||
--primary-foreground: var(--surface-primary);
|
||
}
|
||
}
|
||
|
||
@layer components {
|
||
/* Map each stripe variant to a color token so the
|
||
pseudo-element rules can stay DRY. */
|
||
.navbar-stripe-devel {
|
||
--stripe-color: var(--content-warning);
|
||
}
|
||
|
||
.navbar-stripe-rc {
|
||
--stripe-color: var(--border-sky);
|
||
}
|
||
|
||
/* Thin stripe bars at the top and bottom edges of the
|
||
navbar. Using pseudo-elements keeps the stripes out of
|
||
the content area so nav links stay readable. */
|
||
.navbar-stripe-devel::before,
|
||
.navbar-stripe-devel::after,
|
||
.navbar-stripe-rc::before,
|
||
.navbar-stripe-rc::after {
|
||
content: "";
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
height: 4px;
|
||
background: repeating-linear-gradient(
|
||
-45deg,
|
||
transparent,
|
||
transparent 4px,
|
||
hsl(var(--stripe-color) / 0.5) 4px,
|
||
hsl(var(--stripe-color) / 0.5) 8px
|
||
);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.navbar-stripe-devel::before,
|
||
.navbar-stripe-rc::before {
|
||
top: 0;
|
||
}
|
||
|
||
.navbar-stripe-devel::after,
|
||
.navbar-stripe-rc::after {
|
||
bottom: 0;
|
||
}
|
||
|
||
@media (max-width: 767px) {
|
||
/*
|
||
* Full-width mobile dropdowns. We set a --mobile-dropdown-bottom
|
||
* custom property on the chat input container so the dropdown
|
||
* position tracks the actual input box, not a hardcoded offset.
|
||
*/
|
||
[data-radix-popper-content-wrapper]:has(> .mobile-full-width-dropdown) {
|
||
position: fixed !important;
|
||
left: 1rem !important;
|
||
width: calc(100vw - 2rem) !important;
|
||
min-width: 0 !important;
|
||
transform: none !important;
|
||
bottom: var(--mobile-dropdown-bottom, 5rem) !important;
|
||
top: auto !important;
|
||
}
|
||
[data-radix-popper-content-wrapper]:has(> .mobile-full-width-dropdown-top) {
|
||
bottom: auto !important;
|
||
top: var(--mobile-dropdown-top, 3.5rem) !important;
|
||
}
|
||
[data-radix-popper-content-wrapper]:has(
|
||
> .mobile-full-width-dropdown-top-below-header
|
||
) {
|
||
bottom: auto !important;
|
||
top: 5rem !important;
|
||
}
|
||
.mobile-full-width-dropdown {
|
||
width: 100% !important;
|
||
min-width: 0 !important;
|
||
max-width: none !important;
|
||
}
|
||
}
|
||
}
|
||
@layer base {
|
||
* {
|
||
@apply border-border;
|
||
}
|
||
|
||
html,
|
||
body,
|
||
#root,
|
||
#storybook-root {
|
||
@apply h-full;
|
||
}
|
||
|
||
button,
|
||
input {
|
||
@apply font-sans;
|
||
}
|
||
|
||
input:-webkit-autofill,
|
||
input:-webkit-autofill:hover,
|
||
input:-webkit-autofill:focus,
|
||
input:-webkit-autofill:active {
|
||
-webkit-box-shadow: 0 0 0 100px hsl(var(--surface-primary)) inset !important;
|
||
}
|
||
|
||
::placeholder {
|
||
@apply text-content-secondary;
|
||
}
|
||
|
||
fieldset {
|
||
@apply border-none p-0 m-0 w-full;
|
||
}
|
||
|
||
/* Global scrollbar color baseline: surface-quaternary thumb on
|
||
transparent track. scrollbar-color is inherited, so this cascades
|
||
to all scroll containers. We intentionally do NOT set
|
||
scrollbar-width globally because any non-auto value opts out of
|
||
overlay scrollbars on macOS/Chromium, causing a permanent gutter
|
||
on every container (even when content does not overflow). */
|
||
:root {
|
||
scrollbar-color: hsl(var(--surface-quaternary)) transparent;
|
||
}
|
||
|
||
/* Prevent layout shift when modals open by maintaining scrollbar width.
|
||
scrollbar-gutter: stable reserves space for the scrollbar so Radix's
|
||
scroll-bar compensation (margin-right/padding-right) is unnecessary
|
||
and causes double spacing. We zero it out with !important to win
|
||
over Radix's injected !important styles. The @supports guard ensures
|
||
we only do this on browsers that actually support scrollbar-gutter.
|
||
|
||
There’s a related issue on GitHub: Radix UI Primitives Issue #3251
|
||
https://github.com/radix-ui/primitives/issues/3251
|
||
*/
|
||
@supports (scrollbar-gutter: stable) {
|
||
html {
|
||
scrollbar-gutter: stable;
|
||
}
|
||
|
||
html body[data-scroll-locked] {
|
||
--removed-body-scroll-bar-size: 0 !important;
|
||
margin-right: 0 !important;
|
||
overflow-y: scroll !important;
|
||
}
|
||
}
|
||
|
||
/*
|
||
This is a temporary fix for MUI Modals/Popovers until they are removed.
|
||
When html has scrollbar-gutter: stable, the browser reserves space for the scrollbar.
|
||
MUI Modals/Popovers, when locking body scroll, add `overflow: hidden` and `padding-right`
|
||
to the body to compensate for the scrollbar they are hiding. This added padding-right
|
||
conflicts with the already reserved gutter space, causing a layout shift.
|
||
This rule overrides MUI's added padding-right on the body specifically when MUI
|
||
is likely to have set both overflow:hidden and padding-right.
|
||
*/
|
||
body[style*="overflow: hidden"][style*="padding-right"] {
|
||
padding-right: 0px;
|
||
}
|
||
}
|
||
|
||
@layer utilities {
|
||
.text-xs-mono {
|
||
@apply text-xs font-mono font-medium;
|
||
}
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
*,
|
||
*::before,
|
||
*::after {
|
||
animation-duration: 0.01ms !important;
|
||
animation-iteration-count: 1 !important;
|
||
transition-duration: 0.01ms !important;
|
||
scroll-behavior: auto !important;
|
||
}
|
||
}
|