mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-09 19:45:17 +02:00
add pane size config in settings
This commit is contained in:
parent
8acb9f4385
commit
23ea1fca97
4 changed files with 71 additions and 7 deletions
|
|
@ -166,6 +166,7 @@ function AutoScrollPre({ className, children }: { className?: string; children:
|
|||
}
|
||||
|
||||
const DEFAULT_SIDEBAR_WIDTH = 256
|
||||
const DEFAULT_CHAT_PANE_WIDTH = 460
|
||||
const wikiLinkRegex = /\[\[([^[\]]+)\]\]/g
|
||||
const graphPalette = [
|
||||
{ hue: 210, sat: 72, light: 52 },
|
||||
|
|
@ -737,7 +738,7 @@ function ContentHeader({
|
|||
}
|
||||
|
||||
function App() {
|
||||
const { chatPanePlacement } = useTheme()
|
||||
const { chatPanePlacement, chatPaneSize } = useTheme()
|
||||
const isChatPaneInMiddle = chatPanePlacement === 'middle'
|
||||
|
||||
type ShortcutPane = 'left' | 'right'
|
||||
|
|
@ -5250,6 +5251,17 @@ function App() {
|
|||
const isRightPaneContext = Boolean(selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isChatHistoryOpen || isHomeOpen || isBrowserOpen)
|
||||
const isRightPaneOnlyMode = isRightPaneContext && isChatSidebarOpen && isRightPaneMaximized
|
||||
const shouldCollapseLeftPane = isRightPaneOnlyMode
|
||||
const nonChatPaneStyle = React.useMemo<React.CSSProperties>(() => {
|
||||
const style: React.CSSProperties = { maxWidth: insetMaxWidth }
|
||||
if (!isRightPaneContext || !isChatSidebarOpen || isRightPaneMaximized) return style
|
||||
if (chatPaneSize === 'chat-equal') {
|
||||
return { ...style, width: 0, flex: '1 1 0' }
|
||||
}
|
||||
if (chatPaneSize === 'chat-bigger') {
|
||||
return { ...style, width: DEFAULT_CHAT_PANE_WIDTH, flex: '0 0 auto' }
|
||||
}
|
||||
return style
|
||||
}, [chatPaneSize, insetMaxWidth, isChatSidebarOpen, isRightPaneContext, isRightPaneMaximized])
|
||||
// Collapsing: pin max-width to the snapshot px (no transition) for one frame so it's
|
||||
// binding immediately (no flex jump), then animate to 0. Expanding goes back to 100%
|
||||
// — its non-binding range lands at the end of the range, where it isn't visible.
|
||||
|
|
@ -5331,7 +5343,7 @@ function App() {
|
|||
insetAnimateMaxWidth && "transition-[max-width] duration-200 ease-linear",
|
||||
shouldCollapseLeftPane && "pointer-events-none select-none"
|
||||
)}
|
||||
style={{ maxWidth: insetMaxWidth }}
|
||||
style={nonChatPaneStyle}
|
||||
aria-hidden={shouldCollapseLeftPane}
|
||||
onMouseDownCapture={() => setActiveShortcutPane('left')}
|
||||
onFocusCapture={() => setActiveShortcutPane('left')}
|
||||
|
|
@ -6002,8 +6014,9 @@ function App() {
|
|||
{isRightPaneContext && (
|
||||
<ChatSidebar
|
||||
placement={chatPanePlacement}
|
||||
paneSize={chatPaneSize}
|
||||
className={isChatPaneInMiddle ? "order-2" : undefined}
|
||||
defaultWidth={460}
|
||||
defaultWidth={DEFAULT_CHAT_PANE_WIDTH}
|
||||
isOpen={isChatSidebarOpen}
|
||||
isMaximized={isRightPaneMaximized}
|
||||
chatTabs={chatTabs}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import { ChatInputWithMentions, type PermissionMode, type StagedAttachment, type
|
|||
import { ChatMessageAttachments } from '@/components/chat-message-attachments'
|
||||
import { useSidebar } from '@/components/ui/sidebar'
|
||||
import { wikiLabel } from '@/lib/wiki-links'
|
||||
import type { ChatPaneSize } from '@/contexts/theme-context'
|
||||
import {
|
||||
type ChatViewportAnchorState,
|
||||
type ChatTabViewState,
|
||||
|
|
@ -126,6 +127,7 @@ interface ChatSidebarProps {
|
|||
isOpen?: boolean
|
||||
isMaximized?: boolean
|
||||
placement?: 'middle' | 'right'
|
||||
paneSize?: ChatPaneSize
|
||||
className?: string
|
||||
chatTabs: ChatTab[]
|
||||
activeChatTabId: string
|
||||
|
|
@ -186,6 +188,7 @@ export function ChatSidebar({
|
|||
isOpen = true,
|
||||
isMaximized = false,
|
||||
placement = 'right',
|
||||
paneSize = 'chat-smaller',
|
||||
className,
|
||||
chatTabs,
|
||||
activeChatTabId,
|
||||
|
|
@ -251,6 +254,7 @@ export function ChatSidebar({
|
|||
const prevIsMaximizedRef = useRef(isMaximized)
|
||||
const justToggledMaximize = prevIsMaximizedRef.current !== isMaximized
|
||||
const isMiddlePlacement = placement === 'middle'
|
||||
const isResizable = paneSize === 'chat-smaller'
|
||||
|
||||
const getMaxAllowedWidth = useCallback(() => {
|
||||
if (typeof window === 'undefined') return MAX_WIDTH
|
||||
|
|
@ -508,8 +512,11 @@ export function ChatSidebar({
|
|||
// not add extra width to the right and overflow the app viewport.
|
||||
return { width: 0, flex: '1 1 auto' }
|
||||
}
|
||||
if (paneSize === 'chat-equal' || paneSize === 'chat-bigger') {
|
||||
return { width: 0, flex: '1 1 0' }
|
||||
}
|
||||
return { width, flex: '0 0 auto' }
|
||||
}, [isOpen, isMaximized, width])
|
||||
}, [isOpen, isMaximized, paneSize, width])
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -525,7 +532,7 @@ export function ChatSidebar({
|
|||
)}
|
||||
style={paneStyle}
|
||||
>
|
||||
{!isMaximized && (
|
||||
{!isMaximized && isResizable && (
|
||||
<div
|
||||
onMouseDown={handleMouseDown}
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ function ThemeOption({
|
|||
}
|
||||
|
||||
function AppearanceSettings() {
|
||||
const { theme, setTheme, chatPanePlacement, setChatPanePlacement } = useTheme()
|
||||
const { theme, setTheme, chatPanePlacement, setChatPanePlacement, chatPaneSize, setChatPaneSize } = useTheme()
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
|
@ -259,6 +259,30 @@ function AppearanceSettings() {
|
|||
onClick={() => setChatPanePlacement("middle")}
|
||||
/>
|
||||
</div>
|
||||
<h4 className="mt-6 text-sm font-medium mb-3">Chat size</h4>
|
||||
<p className="text-xs text-muted-foreground mb-4">
|
||||
Choose how much width chat gets when another pane is open
|
||||
</p>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<ThemeOption
|
||||
label="Chat smaller"
|
||||
icon={MessageCircle}
|
||||
isSelected={chatPaneSize === "chat-smaller"}
|
||||
onClick={() => setChatPaneSize("chat-smaller")}
|
||||
/>
|
||||
<ThemeOption
|
||||
label="Chat equal"
|
||||
icon={Monitor}
|
||||
isSelected={chatPaneSize === "chat-equal"}
|
||||
onClick={() => setChatPaneSize("chat-equal")}
|
||||
/>
|
||||
<ThemeOption
|
||||
label="Chat bigger"
|
||||
icon={PanelRight}
|
||||
isSelected={chatPaneSize === "chat-bigger"}
|
||||
onClick={() => setChatPaneSize("chat-bigger")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import * as React from "react"
|
|||
|
||||
export type Theme = "light" | "dark" | "system"
|
||||
export type ChatPanePlacement = "right" | "middle"
|
||||
export type ChatPaneSize = "chat-smaller" | "chat-equal" | "chat-bigger"
|
||||
|
||||
type ThemeContextProps = {
|
||||
theme: Theme
|
||||
|
|
@ -11,17 +12,24 @@ type ThemeContextProps = {
|
|||
setTheme: (theme: Theme) => void
|
||||
chatPanePlacement: ChatPanePlacement
|
||||
setChatPanePlacement: (placement: ChatPanePlacement) => void
|
||||
chatPaneSize: ChatPaneSize
|
||||
setChatPaneSize: (size: ChatPaneSize) => void
|
||||
}
|
||||
|
||||
const ThemeContext = React.createContext<ThemeContextProps | null>(null)
|
||||
|
||||
const STORAGE_KEY = "rowboat-theme"
|
||||
const CHAT_PANE_PLACEMENT_STORAGE_KEY = "rowboat-chat-pane-placement"
|
||||
const CHAT_PANE_SIZE_STORAGE_KEY = "rowboat-chat-pane-size"
|
||||
|
||||
function isChatPanePlacement(value: string | null): value is ChatPanePlacement {
|
||||
return value === "right" || value === "middle"
|
||||
}
|
||||
|
||||
function isChatPaneSize(value: string | null): value is ChatPaneSize {
|
||||
return value === "chat-smaller" || value === "chat-equal" || value === "chat-bigger"
|
||||
}
|
||||
|
||||
function getSystemTheme(): "light" | "dark" {
|
||||
if (typeof window === "undefined") return "light"
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
|
||||
|
|
@ -52,6 +60,11 @@ export function ThemeProvider({
|
|||
const stored = localStorage.getItem(CHAT_PANE_PLACEMENT_STORAGE_KEY)
|
||||
return isChatPanePlacement(stored) ? stored : "right"
|
||||
})
|
||||
const [chatPaneSize, setChatPaneSizeState] = React.useState<ChatPaneSize>(() => {
|
||||
if (typeof window === "undefined") return "chat-smaller"
|
||||
const stored = localStorage.getItem(CHAT_PANE_SIZE_STORAGE_KEY)
|
||||
return isChatPaneSize(stored) ? stored : "chat-smaller"
|
||||
})
|
||||
|
||||
const [resolvedTheme, setResolvedTheme] = React.useState<"light" | "dark">(() => {
|
||||
if (theme === "system") return getSystemTheme()
|
||||
|
|
@ -94,6 +107,11 @@ export function ThemeProvider({
|
|||
setChatPanePlacementState(placement)
|
||||
}, [])
|
||||
|
||||
const setChatPaneSize = React.useCallback((size: ChatPaneSize) => {
|
||||
localStorage.setItem(CHAT_PANE_SIZE_STORAGE_KEY, size)
|
||||
setChatPaneSizeState(size)
|
||||
}, [])
|
||||
|
||||
const contextValue = React.useMemo<ThemeContextProps>(
|
||||
() => ({
|
||||
theme,
|
||||
|
|
@ -101,8 +119,10 @@ export function ThemeProvider({
|
|||
setTheme,
|
||||
chatPanePlacement,
|
||||
setChatPanePlacement,
|
||||
chatPaneSize,
|
||||
setChatPaneSize,
|
||||
}),
|
||||
[theme, resolvedTheme, setTheme, chatPanePlacement, setChatPanePlacement]
|
||||
[theme, resolvedTheme, setTheme, chatPanePlacement, setChatPanePlacement, chatPaneSize, setChatPaneSize]
|
||||
)
|
||||
|
||||
return (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue