diff --git a/CHANGELOG.md b/CHANGELOG.md index 0942981..316383f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. This projec ### Changed ### Fixed +- Mobile display issues. - Editing a secret clears its masked value. - Array label properties crashes app. diff --git a/src/components/common/GlobalSearch.tsx b/src/components/common/GlobalSearch.tsx index 43e4b8b..435a380 100644 --- a/src/components/common/GlobalSearch.tsx +++ b/src/components/common/GlobalSearch.tsx @@ -61,7 +61,12 @@ function friendlyName(viewName: string): string { return parts[parts.length - 1]; } -export function GlobalSearch() { +interface GlobalSearchProps { + onAfterSelect?: () => void; + autoFocus?: boolean; +} + +export function GlobalSearch({ onAfterSelect, autoFocus }: GlobalSearchProps = {}) { const { t } = useTranslation(); const navigate = useNavigate(); const GROUP_LABELS: Record = { @@ -146,8 +151,9 @@ export function GlobalSearch() { setQuery(''); setDebouncedQuery(''); navigate(path); + onAfterSelect?.(); }, - [schema, navigate], + [schema, navigate, onAfterSelect], ); const handleKeyDown = useCallback( @@ -172,69 +178,68 @@ export function GlobalSearch() { const showDropdown = dropdownOpen && debouncedQuery.trim().length > 0; return ( -
-
- - { - handleQueryChange(e.target.value); - setDropdownOpen(true); - }} - onFocus={() => { - if (query.trim()) setDropdownOpen(true); - }} - onKeyDown={handleKeyDown} - className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 pl-9 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" - /> +
+ + { + handleQueryChange(e.target.value); + setDropdownOpen(true); + }} + onFocus={() => { + if (query.trim()) setDropdownOpen(true); + }} + onKeyDown={handleKeyDown} + className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 pl-9 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" + /> - {showDropdown && ( -
- {results.length === 0 ? ( -
- {t('globalSearch.noResults', 'No results found.')} -
- ) : ( -
- {Array.from(groups.entries()).map(([type, entries]) => ( -
-
{GROUP_LABELS[type]}
- {entries.map((entry) => { - const flatIdx = results.indexOf(entry); - const objectKind = schema ? getObjectKind(schema, entry.viewName) : null; - const { label: actionLabel, Icon: ActionIcon } = getActionInfo(entry.type, objectKind, t); + {showDropdown && ( +
+ {results.length === 0 ? ( +
+ {t('globalSearch.noResults', 'No results found.')} +
+ ) : ( +
+ {Array.from(groups.entries()).map(([type, entries]) => ( +
+
{GROUP_LABELS[type]}
+ {entries.map((entry) => { + const flatIdx = results.indexOf(entry); + const objectKind = schema ? getObjectKind(schema, entry.viewName) : null; + const { label: actionLabel, Icon: ActionIcon } = getActionInfo(entry.type, objectKind, t); - return ( - - ); - })} -
- ))} -
- )} -
- )} -
+ return ( + + ); + })} +
+ ))} +
+ )} +
+ )}
); } diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index c83d672..d69be26 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -10,7 +10,6 @@ import * as LucideIcons from 'lucide-react'; const { ChevronDown, Lock } = LucideIcons; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; -import { ScrollArea } from '@/components/ui/scroll-area'; import { EnterpriseUpsell } from '@/components/common/EnterpriseUpsell'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; @@ -262,6 +261,7 @@ export function Sidebar() { const activeSection = useUIStore((s) => s.activeSection); const setActiveSection = useUIStore((s) => s.setActiveSection); const sidebarOpen = useUIStore((s) => s.sidebarOpen); + const setSidebarOpen = useUIStore((s) => s.setSidebarOpen); const schema = useSchemaStore((s) => s.schema); const edition = useAccountStore((s) => s.edition); const hasObjectPermission = useAccountStore((s) => s.hasObjectPermission); @@ -282,6 +282,13 @@ export function Sidebar() { } }, [schema, layouts, activeSection, setActiveSection]); + useEffect(() => { + if (typeof window === 'undefined') return; + if (window.matchMedia('(max-width: 767px)').matches) { + setSidebarOpen(false); + } + }, [location.pathname, setSidebarOpen]); + if (!sidebarOpen || !schema) return null; const layout: Layout | undefined = layouts.find((l) => l.name === activeSection); @@ -297,58 +304,65 @@ export function Sidebar() { }; return ( - + ); } diff --git a/src/components/layout/TopBar.tsx b/src/components/layout/TopBar.tsx index 7e31032..739cee0 100644 --- a/src/components/layout/TopBar.tsx +++ b/src/components/layout/TopBar.tsx @@ -7,9 +7,10 @@ import { Link, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import * as LucideIcons from 'lucide-react'; -const { Sun, Moon, User, LogOut, Check, Menu, Sparkles } = LucideIcons; +const { Sun, Moon, User, LogOut, Check, Menu, Sparkles, Search } = LucideIcons; import { Button } from '@/components/ui/button'; import { GlobalSearch } from '@/components/common/GlobalSearch'; +import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'; import { DropdownMenu, DropdownMenuContent, @@ -53,6 +54,7 @@ export function TopBar() { const hasPermission = useAccountStore((s) => s.hasPermission); const schema = useSchemaStore((s) => s.schema); const [upsellOpen, setUpsellOpen] = useState(false); + const [mobileSearchOpen, setMobileSearchOpen] = useState(false); const navigableLayouts = schema ? visibleLayouts(schema, edition, (prefix) => hasObjectPermission(prefix, 'Get'), hasPermission) @@ -68,11 +70,30 @@ export function TopBar() { - +
+ +
-
+
{edition !== 'enterprise' && setUpsellOpen(false)} />} + + + + + {t('search', 'Search')} + setMobileSearchOpen(false)} /> + + + diff --git a/src/components/ui/toast.tsx b/src/components/ui/toast.tsx index 02ed720..c62a39c 100644 --- a/src/components/ui/toast.tsx +++ b/src/components/ui/toast.tsx @@ -15,7 +15,7 @@ const ToastViewport = React.forwardRef 0 ? value.map((v) => coerceLabel(v, '')).filter(Boolean).join(', ') : fallback; + if (Array.isArray(value)) + return value.length > 0 + ? value + .map((v) => coerceLabel(v, '')) + .filter(Boolean) + .join(', ') + : fallback; if (typeof value === 'object') { const keys = Object.keys(value as Record); return keys.length > 0 ? keys.join(', ') : fallback; diff --git a/src/pages/AdminPanel.tsx b/src/pages/AdminPanel.tsx index ccf5af6..76e80ac 100644 --- a/src/pages/AdminPanel.tsx +++ b/src/pages/AdminPanel.tsx @@ -248,7 +248,7 @@ export default function AdminPanel() {
diff --git a/src/stores/uiStore.ts b/src/stores/uiStore.ts index 95e6b38..8bfd068 100644 --- a/src/stores/uiStore.ts +++ b/src/stores/uiStore.ts @@ -34,7 +34,7 @@ export const useUIStore = create()( (set, get) => ({ theme: typeof window !== 'undefined' && window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light', - sidebarOpen: true, + sidebarOpen: typeof window !== 'undefined' ? (window.matchMedia?.('(min-width: 768px)').matches ?? true) : true, activeSection: '', toggleTheme: () => {