feat: add assistant window controls

This commit is contained in:
ajnart committed 2026-08-14 11:32:11 +02:00
1 parent bb6e8e5c5a
commit 517d9320dc
3 files changed
+144 -15

No files matched your search

@@ -117,6 +117,15 @@
animation: assistant-panel-in 180ms cubic-bezier(0.22, 1, 0.36, 1);
}
.floatingPanel[data-maximized] {
inset: 1rem;
width: auto;
height: auto;
min-height: 0;
transform: none;
animation: none;
}
.widgetPanel {
container-name: assistant-widget;
container-type: size;
@@ -150,6 +159,7 @@
}
.panelHeader {
min-width: 0;
min-height: 3.75rem;
flex: 0 0 auto;
padding: 0.65rem 0.75rem 0.65rem 0.9rem;
@@ -166,9 +176,52 @@
}
.panelActions {
min-width: 0;
flex: 0 0 auto;
}
.windowControls {
flex: 0 0 auto;
margin: 0;
padding: 0;
border: 0;
}
.windowControl {
width: 1.75rem;
height: 1.75rem;
border: 0;
color: rgba(0, 0, 0, 0.72);
transition:
filter 160ms cubic-bezier(0.22, 1, 0.36, 1),
transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
.windowControl:hover {
filter: brightness(0.94) saturate(1.08);
}
.windowControl:active {
transform: scale(0.92);
}
.windowControl:focus-visible {
outline: 2px solid var(--mantine-primary-color-filled);
outline-offset: 2px;
}
.windowControlClose {
background: var(--mantine-color-red-6);
}
.windowControlMinimize {
background: var(--mantine-color-yellow-5);
}
.windowControlMaximize {
background: var(--mantine-color-green-6);
}
.panelAction {
width: 2.625rem;
height: 2.625rem;
@@ -322,12 +375,15 @@
min-height: 0;
flex: 1;
flex-direction: column;
overflow-x: hidden;
overflow-y: auto;
}
.messages {
width: 100%;
min-width: 0;
max-width: 44rem;
box-sizing: border-box;
margin-inline: auto;
padding: 0.75rem 1rem 1.5rem;
}
@@ -1276,6 +1332,7 @@
position: relative;
display: flex;
width: 100%;
min-width: 0;
max-width: none;
flex-direction: column;
align-items: stretch;
@@ -1368,11 +1425,15 @@
.composerFooter {
min-width: 0;
max-width: 100%;
overflow: hidden;
padding-inline: 0.2rem;
}
.composerControls {
min-width: 0;
max-width: 100%;
flex: 1 1 auto;
overflow: hidden;
}
@@ -1482,7 +1543,8 @@
inset-inline: 0;
bottom: calc(100% + 0.45rem);
max-height: min(22rem, 45dvh);
overflow: auto;
overflow-x: hidden;
overflow-y: auto;
padding: 0.35rem;
border: 1px solid var(--mantine-color-default-border);
border-radius: var(--mantine-radius-md);
@@ -1492,12 +1554,17 @@
.triggerList {
display: grid;
min-width: 0;
max-width: 100%;
gap: 0.15rem;
}
.triggerItem {
display: flex;
width: 100%;
min-width: 0;
max-width: 100%;
box-sizing: border-box;
min-height: 2.75rem;
align-items: center;
gap: 0.55rem;
@@ -1599,6 +1666,14 @@
min-height: min(20rem, calc(100dvh - 1rem - env(safe-area-inset-bottom, 0px)));
}
.floatingPanel[data-maximized] {
inset: 0;
width: 100vw;
height: 100dvh;
min-height: 0;
border-radius: 0;
}
.panelHeader {
min-height: 3.5rem;
padding: 0.5rem;
@@ -1642,6 +1717,14 @@
}
@media (max-width: 30em) {
.panelIdentity > :first-child {
display: none;
}
.panelIdentityText {
max-width: 7rem;
}
.historyButton {
width: 2.625rem;
max-width: 2.625rem;
@@ -1726,6 +1809,7 @@
@media (prefers-reduced-motion: reduce) {
.activityBar,
.floatingPanel,
.windowControl,
.disclosureIcon,
.reasoningChevron {
animation: none;
@@ -78,6 +78,7 @@ import {
IconApps,
IconArrowUp,
IconArrowsMaximize,
IconArrowsMinimize,
IconAt,
IconCheck,
IconChevronDown,
@@ -2731,6 +2732,9 @@ interface AssistantConversationSurfaceProps extends AssistantConversationControl
pendingAction: AssistantPendingAction | undefined;
onExpand?: () => void;
onMinimize?: () => void;
onWindowClose?: () => void;
onToggleMaximize?: () => void;
maximized?: boolean;
}
export const AssistantConversationSurface = ({
@@ -2747,6 +2751,9 @@ export const AssistantConversationSurface = ({
onReasoningChange,
onExpand,
onMinimize,
onWindowClose,
onToggleMaximize,
maximized = false,
}: AssistantConversationSurfaceProps) => {
const t = useScopedI18n("common.assistant");
const reducedMotion = useReducedMotion();
@@ -2761,6 +2768,48 @@ export const AssistantConversationSurface = ({
return (
<>
<Group className={classes.panelHeader} justify="space-between" wrap="nowrap" gap="xs">
{onWindowClose && onMinimize && onToggleMaximize && (
<Group
component="fieldset"
className={classes.windowControls}
gap={4}
wrap="nowrap"
aria-label={t("windowControls")}
>
<Tooltip label={t("close")}>
<ActionIcon
className={`${classes.windowControl} ${classes.windowControlClose}`}
onClick={onWindowClose}
aria-label={t("close")}
>
<IconX size={12} stroke={2.4} />
</ActionIcon>
</Tooltip>
<Tooltip label={t("minimize")}>
<ActionIcon
className={`${classes.windowControl} ${classes.windowControlMinimize}`}
onClick={onMinimize}
aria-label={t("minimize")}
>
<IconMinus size={12} stroke={2.4} />
</ActionIcon>
</Tooltip>
<Tooltip label={maximized ? t("restore") : t("maximize")}>
<ActionIcon
className={`${classes.windowControl} ${classes.windowControlMaximize}`}
onClick={onToggleMaximize}
aria-label={maximized ? t("restore") : t("maximize")}
aria-pressed={maximized}
>
{maximized ? (
<IconArrowsMinimize size={11} stroke={2.4} />
) : (
<IconArrowsMaximize size={11} stroke={2.4} />
)}
</ActionIcon>
</Tooltip>
</Group>
)}
<Group className={classes.panelIdentity} gap="xs" wrap="nowrap">
<ThemeIcon variant="light" color="red" radius="xl">
<IconRobot size={18} />
@@ -2800,19 +2849,6 @@ export const AssistantConversationSurface = ({
</ActionIcon>
</Tooltip>
)}
{onMinimize && (
<Tooltip label={t("minimize")}>
<ActionIcon
className={classes.panelAction}
variant="subtle"
color="gray"
onClick={onMinimize}
aria-label={t("minimize")}
>
<IconMinus size={18} />
</ActionIcon>
</Tooltip>
)}
</Group>
</Group>
<AssistantQuestionPortalProvider target={questionPortalTarget}>
@@ -2889,6 +2925,7 @@ export const AssistantPanel = ({
}: AssistantPanelProps) => {
const t = useScopedI18n("common.assistant");
const previousFocusRef = useRef<HTMLElement | null>(null);
const [maximized, setMaximized] = useState(false);
useWindowEvent("keydown", (event) => {
if (opened && event.key === "Escape") onClose();
@@ -2928,7 +2965,7 @@ export const AssistantPanel = ({
/>
)}
{opened && (
<dialog className={classes.floatingPanel} aria-label={t("title")} open>
<dialog className={classes.floatingPanel} data-maximized={maximized || undefined} aria-label={t("title")} open>
<AssistantConversationSurface
isRunning={isRunning}
pendingAction={pendingAction}
@@ -2942,6 +2979,12 @@ export const AssistantPanel = ({
onReasoningChange={onReasoningChange}
autoFocusComposer
onMinimize={onClose}
onWindowClose={() => {
onDismissActivity();
onClose();
}}
onToggleMaximize={() => setMaximized((current) => !current)}
maximized={maximized}
/>
</dialog>
)}