fix(web): improve mobile control spacing
This commit is contained in:
parent
418398587c
commit
1052c04d33
7 changed files with 40 additions and 20 deletions
|
|
@ -25,9 +25,9 @@ const AuthFooter = ({ className }: Props) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className={cn("mt-4 flex flex-row items-center justify-center w-full gap-2", className)}>
|
||||
<LocaleSelect value={currentLocale} onChange={handleLocaleChange} />
|
||||
<ThemeSelect value={currentTheme} onValueChange={handleThemeChange} />
|
||||
<div className={cn("mx-auto mt-4 grid w-full max-w-xs grid-cols-2 gap-2", className)}>
|
||||
<LocaleSelect value={currentLocale} onChange={handleLocaleChange} className="w-full" />
|
||||
<ThemeSelect value={currentTheme} onValueChange={handleThemeChange} className="w-full" compact />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -28,8 +28,13 @@ export const LocaleSearchList = (props: LocaleSearchListProps) => {
|
|||
);
|
||||
|
||||
return (
|
||||
<div className={cn("w-64 max-w-[calc(100vw-2rem)]", className)}>
|
||||
<div className="relative p-1">
|
||||
<div
|
||||
className={cn(
|
||||
"flex max-h-[min(24rem,calc(100vh-2rem))] w-[var(--radix-popover-trigger-width)] min-w-48 max-w-[calc(100vw-2rem)] flex-col",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="relative shrink-0 p-1">
|
||||
<SearchIcon className="absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
value={query}
|
||||
|
|
@ -40,7 +45,7 @@ export const LocaleSearchList = (props: LocaleSearchListProps) => {
|
|||
aria-label={t("common.language")}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-1 max-h-72 overflow-y-auto p-1" role="listbox" aria-label={t("common.language")}>
|
||||
<div className="mt-1 min-h-0 flex-1 touch-pan-y overflow-y-auto p-1" role="listbox" aria-label={t("common.language")}>
|
||||
{filteredLocales.map((locale) => (
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -53,9 +58,11 @@ export const LocaleSearchList = (props: LocaleSearchListProps) => {
|
|||
)}
|
||||
onClick={() => onChange(locale)}
|
||||
>
|
||||
{value === locale ? <CheckIcon className="size-4 text-primary" /> : <span className="size-4" />}
|
||||
<span className="min-w-0 flex-1 truncate">{getLocaleDisplayName(locale)}</span>
|
||||
<span className="flex min-w-0 flex-1 items-baseline gap-1.5">
|
||||
<span className="truncate">{getLocaleDisplayName(locale)}</span>
|
||||
<span className="shrink-0 text-xs text-muted-foreground">{locale}</span>
|
||||
</span>
|
||||
{value === locale ? <CheckIcon className="size-4 shrink-0 text-primary" /> : <span className="size-4 shrink-0" />}
|
||||
</button>
|
||||
))}
|
||||
{filteredLocales.length === 0 && (
|
||||
|
|
@ -94,11 +101,11 @@ const LocalePicker = (props: LocalePickerProps) => {
|
|||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button type="button" variant="outline" className={cn("w-full justify-between", className)}>
|
||||
<span className="flex min-w-0 items-center gap-2">
|
||||
<GlobeIcon className="size-4 text-muted-foreground" />
|
||||
<span className="flex min-w-0 flex-1 items-center gap-2">
|
||||
<GlobeIcon className="size-4 shrink-0 text-muted-foreground" />
|
||||
<span className="truncate">{getLocaleDisplayName(value)}</span>
|
||||
</span>
|
||||
<ChevronDownIcon className="size-4 text-muted-foreground" />
|
||||
<ChevronDownIcon className="size-4 shrink-0 text-muted-foreground" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start" className="p-0">
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@ import { loadLocale } from "@/utils/i18n";
|
|||
interface Props {
|
||||
value: Locale;
|
||||
onChange: (locale: Locale) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const LocaleSelect: FC<Props> = (props: Props) => {
|
||||
const { onChange, value } = props;
|
||||
const { className, onChange, value } = props;
|
||||
|
||||
const handleSelectChange = async (locale: Locale) => {
|
||||
// Apply locale globally immediately
|
||||
|
|
@ -17,7 +18,7 @@ const LocaleSelect: FC<Props> = (props: Props) => {
|
|||
onChange(locale);
|
||||
};
|
||||
|
||||
return <LocalePicker value={value} onChange={handleSelectChange} />;
|
||||
return <LocalePicker value={value} onChange={handleSelectChange} className={className} />;
|
||||
};
|
||||
|
||||
export default LocaleSelect;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ interface ThemeSelectProps {
|
|||
value?: string;
|
||||
onValueChange?: (theme: string) => void;
|
||||
className?: string;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
const THEME_ICONS: Record<string, ReactElement> = {
|
||||
|
|
@ -16,8 +17,9 @@ const THEME_ICONS: Record<string, ReactElement> = {
|
|||
paper: <Palette className="w-4 h-4" />,
|
||||
};
|
||||
|
||||
const ThemeSelect = ({ value, onValueChange, className }: ThemeSelectProps = {}) => {
|
||||
const ThemeSelect = ({ value, onValueChange, className, compact = false }: ThemeSelectProps = {}) => {
|
||||
const currentTheme = value || "system";
|
||||
const triggerLabel = currentTheme === "system" ? "System" : THEME_OPTIONS.find((option) => option.value === currentTheme)?.label;
|
||||
|
||||
const handleThemeChange = (newTheme: string) => {
|
||||
// Apply theme globally immediately
|
||||
|
|
@ -31,8 +33,9 @@ const ThemeSelect = ({ value, onValueChange, className }: ThemeSelectProps = {})
|
|||
return (
|
||||
<Select value={currentTheme} onValueChange={handleThemeChange}>
|
||||
<SelectTrigger className={className}>
|
||||
<div className="flex items-center gap-2">
|
||||
<SelectValue placeholder="Select theme" />
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2">
|
||||
{compact && THEME_ICONS[currentTheme]}
|
||||
{compact ? <span className="truncate">{triggerLabel}</span> : <SelectValue className="truncate" placeholder="Select theme" />}
|
||||
</div>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
|
|
|||
|
|
@ -151,8 +151,8 @@ const UserMenu = (props: Props) => {
|
|||
<GlobeIcon className="size-4 text-muted-foreground" />
|
||||
{t("common.language")}
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent className="p-0">
|
||||
<LocaleSearchList value={currentLocale} onChange={handleLocaleChange} />
|
||||
<DropdownMenuSubContent className="max-h-[min(24rem,var(--radix-dropdown-menu-content-available-height))] overflow-y-auto p-0">
|
||||
<LocaleSearchList value={currentLocale} onChange={handleLocaleChange} className="w-64" />
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuSub>
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ const SponsorLogo = ({ sponsor }: { sponsor: Sponsor }) => {
|
|||
const About = () => {
|
||||
return (
|
||||
<section className="mx-auto w-full max-w-5xl min-h-full flex flex-col justify-start items-start sm:pt-3 md:pt-6 pb-8">
|
||||
<div className="w-full px-4 sm:px-6">
|
||||
<div className="w-full">
|
||||
<div className="w-full rounded-xl border border-border bg-background px-4 py-4 text-muted-foreground">
|
||||
<SettingSection
|
||||
title="About Memos"
|
||||
|
|
|
|||
|
|
@ -41,6 +41,15 @@ describe("<About>", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("does not add nested horizontal page padding on mobile", () => {
|
||||
const { container } = render(<About />);
|
||||
|
||||
const contentWrapper = container.querySelector("section > div");
|
||||
|
||||
expect(contentWrapper).toHaveClass("w-full");
|
||||
expect(contentWrapper).not.toHaveClass("px-4");
|
||||
});
|
||||
|
||||
it("uses dark sponsor logos when the app theme is dark", () => {
|
||||
document.documentElement.setAttribute("data-theme", "default-dark");
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue