diff --git a/src-web/components/Settings/SettingsAppearance.tsx b/src-web/components/Settings/SettingsAppearance.tsx index c63b3a6f..7eedcbb5 100644 --- a/src-web/components/Settings/SettingsAppearance.tsx +++ b/src-web/components/Settings/SettingsAppearance.tsx @@ -14,7 +14,7 @@ import { Editor } from '../core/Editor'; import type { IconProps } from '../core/Icon'; import { Icon } from '../core/Icon'; import { IconButton } from '../core/IconButton'; -import type { SelectOption } from '../core/Select'; +import type { SelectProps } from '../core/Select'; import { Select } from '../core/Select'; import { Separator } from '../core/Separator'; import { HStack, VStack } from '../core/Stacks'; @@ -64,14 +64,14 @@ export function SettingsAppearance() { return null; } - const lightThemes: SelectOption[] = themes + const lightThemes: SelectProps['options'] = themes .filter((theme) => !isThemeDark(theme)) .map((theme) => ({ label: theme.name, value: theme.id, })); - const darkThemes: SelectOption[] = themes + const darkThemes: SelectProps['options'] = themes .filter((theme) => isThemeDark(theme)) .map((theme) => ({ label: theme.name, @@ -131,8 +131,9 @@ export function SettingsAppearance() { name="lightTheme" label="Light Theme" size="sm" + className="flex-1" value={activeTheme.light.id} - options={[{ label: 'Light Mode Themes', options: lightThemes }]} + options={lightThemes} onChange={(themeLight) => { trackEvent('theme', 'update', { theme: themeLight, appearance: 'light' }); updateSettings.mutateAsync({ ...settings, themeLight }); @@ -143,11 +144,12 @@ export function SettingsAppearance() { parseInt(value) >= 0} onChange={(v) => updateWorkspace.mutate({ settingRequestTimeout: parseInt(v) || 0 })} + type="number" /> (function Button {isLoading ? ( ) : leftSlot ? ( -
{leftSlot}
+
{leftSlot}
) : null}
void; onClose?: () => void; + fullWidth?: boolean; hotKeyAction?: HotkeyAction; } @@ -73,7 +74,7 @@ export interface DropdownRef { } export const Dropdown = forwardRef(function Dropdown( - { children, items, onOpen, onClose, hotKeyAction }: DropdownProps, + { children, items, onOpen, onClose, hotKeyAction, fullWidth }: DropdownProps, ref, ) { const [isOpen, _setIsOpen] = useState(false); @@ -153,6 +154,7 @@ export const Dropdown = forwardRef(function Dropdown | null; onClose: () => void; showTriangle?: boolean; + fullWidth?: boolean; isOpen: boolean; } @@ -211,6 +214,7 @@ const Menu = forwardRef, MenuPro className, isOpen, items, + fullWidth, onClose, triggerShape, defaultSelectedIndex, @@ -359,21 +363,23 @@ const Menu = forwardRef, MenuPro const heightAbove = triggerShape.top; const heightBelow = docRect.height - triggerShape.bottom; const hSpaceRemaining = docRect.width - triggerShape.left; - const top = triggerShape?.bottom + 5; + const top = triggerShape.bottom + 5; const onRight = hSpaceRemaining < 200; const upsideDown = heightAbove > heightBelow && heightBelow < 200; + const triggerWidth = triggerShape.right - triggerShape.left; const containerStyles = { top: !upsideDown ? top : undefined, bottom: upsideDown ? docRect.height - top : undefined, - right: onRight ? docRect.width - triggerShape?.right : undefined, - left: !onRight ? triggerShape?.left : undefined, + right: onRight ? docRect.width - triggerShape.right : undefined, + left: !onRight ? triggerShape.left : undefined, + minWidth: fullWidth ? triggerWidth : undefined, }; const size = { top: '-0.2rem', width: '0.4rem', height: '0.4rem' }; const triangleStyles = onRight ? { right: width / 2, marginRight: '-0.2rem', ...size } : { left: width / 2, marginLeft: '-0.2rem', ...size }; return { containerStyles, triangleStyles }; - }, [triggerShape]); + }, [fullWidth, triggerShape]); const filteredItems = useMemo( () => items.filter((i) => getNodeText(i.label).toLowerCase().includes(filter.toLowerCase())), diff --git a/src-web/components/core/RadioDropdown.tsx b/src-web/components/core/RadioDropdown.tsx index 2ed68567..2f689d93 100644 --- a/src-web/components/core/RadioDropdown.tsx +++ b/src-web/components/core/RadioDropdown.tsx @@ -50,5 +50,9 @@ export function RadioDropdown({ [items, extraItems, value, onChange], ); - return {children}; + return ( + + {children} + + ); } diff --git a/src-web/components/core/Select.tsx b/src-web/components/core/Select.tsx index 79b15dfb..96c0a94e 100644 --- a/src-web/components/core/Select.tsx +++ b/src-web/components/core/Select.tsx @@ -1,9 +1,14 @@ import classNames from 'classnames'; import type { CSSProperties, ReactNode } from 'react'; import { useState } from 'react'; +import { useOsInfo } from '../../hooks/useOsInfo'; +import type { ButtonProps } from './Button'; +import { Button } from './Button'; +import type { RadioDropdownItem } from './RadioDropdown'; +import { RadioDropdown } from './RadioDropdown'; import { HStack } from './Stacks'; -interface Props { +export interface SelectProps { name: string; label: string; labelPosition?: 'top' | 'left'; @@ -11,22 +16,12 @@ interface Props { hideLabel?: boolean; value: T; leftSlot?: ReactNode; - options: SelectOption[] | SelectOptionGroup[]; + options: RadioDropdownItem[]; onChange: (value: T) => void; - size?: 'xs' | 'sm' | 'md' | 'lg'; + size?: ButtonProps['size']; className?: string; } -export interface SelectOption { - label: string; - value: T; -} - -export interface SelectOptionGroup { - label: string; - options: SelectOption[]; -} - export function Select({ labelPosition = 'top', name, @@ -39,7 +34,8 @@ export function Select({ onChange, className, size = 'md', -}: Props) { +}: SelectProps) { + const osInfo = useOsInfo(); const [focused, setFocused] = useState(false); const id = `input-${name}`; return ( @@ -49,7 +45,7 @@ export function Select({ 'x-theme-input', 'w-full', 'pointer-events-auto', // Just in case we're placing in disabled parent - labelPosition === 'left' && 'flex items-center gap-2', + labelPosition === 'left' && 'grid grid-cols-[auto_1fr] items-center gap-2', labelPosition === 'top' && 'flex-row gap-0.5', )} > @@ -59,45 +55,54 @@ export function Select({ > {label} - - {leftSlot &&
{leftSlot}
} - onChange(e.target.value as T)} + onFocus={() => setFocused(true)} + onBlur={() => setFocused(false)} + className={classNames('pr-7 w-full outline-none bg-transparent')} + > + {options.map((o) => { + if (o.type === 'separator') return null; + return ( + + ); + })} + +
+ ) : ( + // Use custom "select" component until Tauri can be configured to have select menus not always appear in + // light mode + + + + )}
); }