mirror of
https://github.com/usememos/memos.git
synced 2025-12-18 22:59:24 +08:00
chore: use select/dropdown instead of popover
This commit is contained in:
parent
d32924ec48
commit
c1708df7a2
9 changed files with 57 additions and 97 deletions
|
|
@ -40,7 +40,7 @@ const HomeSidebar = observer((props: Props) => {
|
||||||
return (
|
return (
|
||||||
<aside
|
<aside
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative w-full h-full overflow-auto flex flex-col justify-start items-start bg-sidebar text-sidebar-foreground",
|
"relative w-full h-full overflow-auto flex flex-col justify-start items-start bg-background text-sidebar-foreground",
|
||||||
props.className,
|
props.className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { CheckSquareIcon, Code2Icon, SquareSlashIcon } from "lucide-react";
|
import { CheckSquareIcon, Code2Icon, SquareSlashIcon } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/popover";
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../../ui/dropdown-menu";
|
||||||
import { EditorRefActions } from "../Editor";
|
import { EditorRefActions } from "../Editor";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -60,41 +60,33 @@ const MarkdownMenu = (props: Props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover>
|
<DropdownMenu>
|
||||||
<PopoverTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="ghost" size="icon">
|
<Button variant="ghost" size="icon">
|
||||||
<SquareSlashIcon className="size-5" />
|
<SquareSlashIcon className="size-5" />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</DropdownMenuTrigger>
|
||||||
<PopoverContent align="start" className="text-sm p-1">
|
<DropdownMenuContent align="start">
|
||||||
<div className="flex flex-col text-sm gap-0.5">
|
<DropdownMenuItem onClick={handleCodeBlockClick}>
|
||||||
<button
|
<Code2Icon className="w-4 h-auto text-muted-foreground" />
|
||||||
onClick={handleCodeBlockClick}
|
{t("markdown.code-block")}
|
||||||
className="flex items-center gap-2 px-2 py-1 text-left text-foreground hover:bg-background outline-none rounded"
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={handleCheckboxClick}>
|
||||||
|
<CheckSquareIcon className="w-4 h-auto text-muted-foreground" />
|
||||||
|
{t("markdown.checkbox")}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<div className="px-2 -mt-1">
|
||||||
|
<a
|
||||||
|
className="text-xs text-primary hover:underline"
|
||||||
|
href="https://www.usememos.com/docs/getting-started/content-syntax"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<Code2Icon className="w-4 h-auto" />
|
{t("markdown.content-syntax")}
|
||||||
<span>{t("markdown.code-block")}</span>
|
</a>
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={handleCheckboxClick}
|
|
||||||
className="flex items-center gap-2 px-2 py-1 text-left text-foreground hover:bg-background outline-none rounded"
|
|
||||||
>
|
|
||||||
<CheckSquareIcon className="w-4 h-auto" />
|
|
||||||
<span>{t("markdown.checkbox")}</span>
|
|
||||||
</button>
|
|
||||||
<div className="pl-2">
|
|
||||||
<a
|
|
||||||
className="text-xs text-primary hover:underline"
|
|
||||||
href="https://www.usememos.com/docs/getting-started/content-syntax"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{t("markdown.content-syntax")}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</PopoverContent>
|
</DropdownMenuContent>
|
||||||
</Popover>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
import { ChevronDownIcon } from "lucide-react";
|
|
||||||
import { useState } from "react";
|
|
||||||
import VisibilityIcon from "@/components/VisibilityIcon";
|
import VisibilityIcon from "@/components/VisibilityIcon";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { Visibility } from "@/types/proto/api/v1/memo_service";
|
import { Visibility } from "@/types/proto/api/v1/memo_service";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
|
|
||||||
|
|
@ -10,13 +7,11 @@ interface Props {
|
||||||
value: Visibility;
|
value: Visibility;
|
||||||
onChange: (visibility: Visibility) => void;
|
onChange: (visibility: Visibility) => void;
|
||||||
onOpenChange?: (open: boolean) => void;
|
onOpenChange?: (open: boolean) => void;
|
||||||
className?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const VisibilitySelector = (props: Props) => {
|
const VisibilitySelector = (props: Props) => {
|
||||||
const { value, onChange } = props;
|
const { value, onChange } = props;
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
|
|
||||||
const visibilityOptions = [
|
const visibilityOptions = [
|
||||||
{ value: Visibility.PRIVATE, label: t("memo.visibility.private") },
|
{ value: Visibility.PRIVATE, label: t("memo.visibility.private") },
|
||||||
|
|
@ -24,53 +19,26 @@ const VisibilitySelector = (props: Props) => {
|
||||||
{ value: Visibility.PUBLIC, label: t("memo.visibility.public") },
|
{ value: Visibility.PUBLIC, label: t("memo.visibility.public") },
|
||||||
];
|
];
|
||||||
|
|
||||||
const currentOption = visibilityOptions.find((option) => option.value === value);
|
|
||||||
|
|
||||||
const handleSelect = (visibility: Visibility) => {
|
|
||||||
onChange(visibility);
|
|
||||||
handleOpenChange(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOpenChange = (open: boolean) => {
|
const handleOpenChange = (open: boolean) => {
|
||||||
setOpen(open);
|
|
||||||
if (props.onOpenChange) {
|
if (props.onOpenChange) {
|
||||||
props.onOpenChange(open);
|
props.onOpenChange(open);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover open={open} onOpenChange={handleOpenChange}>
|
<Select value={value.toString()} onValueChange={onChange} onOpenChange={handleOpenChange}>
|
||||||
<PopoverTrigger asChild>
|
<SelectTrigger size="xs" className="!bg-background">
|
||||||
<button
|
<SelectValue />
|
||||||
className={cn(
|
</SelectTrigger>
|
||||||
`flex items-center justify-center gap-1 px-0.5 text-xs rounded hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 transition-colors`,
|
<SelectContent align="end">
|
||||||
props.className,
|
{visibilityOptions.map((option) => (
|
||||||
)}
|
<SelectItem key={option.value} value={option.value.toString()}>
|
||||||
type="button"
|
<VisibilityIcon className="size-3.5" visibility={option.value} />
|
||||||
>
|
{option.label}
|
||||||
<VisibilityIcon className="w-3 h-3" visibility={value} />
|
</SelectItem>
|
||||||
<span>{currentOption?.label}</span>
|
))}
|
||||||
<ChevronDownIcon className="w-3 h-3 opacity-60" />
|
</SelectContent>
|
||||||
</button>
|
</Select>
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="p-1!" align="end" sideOffset={2} alignOffset={-4}>
|
|
||||||
<div className="flex flex-col gap-0.5">
|
|
||||||
{visibilityOptions.map((option) => (
|
|
||||||
<button
|
|
||||||
key={option.value}
|
|
||||||
onClick={() => handleSelect(option.value)}
|
|
||||||
className={cn(
|
|
||||||
`flex items-center gap-1 px-1 py-1 text-xs text-left hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 rounded transition-colors`,
|
|
||||||
option.value === value ? "bg-muted" : "",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<VisibilityIcon className="w-3 h-3" visibility={option.value} />
|
|
||||||
<span>{option.label}</span>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -530,7 +530,7 @@ const MemoEditor = observer((props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute right-1 top-1 opacity-60",
|
"absolute right-1 top-1",
|
||||||
"flex flex-row justify-end items-center gap-1",
|
"flex flex-row justify-end items-center gap-1",
|
||||||
"invisible group-focus-within:visible group-hover:visible hover:visible focus-within:visible",
|
"invisible group-focus-within:visible group-hover:visible hover:visible focus-within:visible",
|
||||||
(isVisibilitySelectorOpen || memoName) && "visible",
|
(isVisibilitySelectorOpen || memoName) && "visible",
|
||||||
|
|
|
||||||
|
|
@ -33,15 +33,15 @@ const SearchBar = observer(() => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-auto flex flex-row justify-start items-center">
|
<div className="relative w-full h-auto flex flex-row justify-start items-center">
|
||||||
<SearchIcon className="absolute left-2 w-4 h-auto opacity-40 text-muted-foreground" />
|
<SearchIcon className="absolute left-2 w-4 h-auto opacity-40 text-sidebar-foreground" />
|
||||||
<input
|
<input
|
||||||
className={cn("w-full text-muted-foreground leading-6 bg-muted border border-border text-sm rounded-lg p-1 pl-8 outline-0")}
|
className={cn("w-full text-sidebar-foreground leading-6 bg-sidebar border border-border text-sm rounded-lg p-1 pl-8 outline-0")}
|
||||||
placeholder={t("memo.search-placeholder")}
|
placeholder={t("memo.search-placeholder")}
|
||||||
value={queryText}
|
value={queryText}
|
||||||
onChange={onTextChange}
|
onChange={onTextChange}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
/>
|
/>
|
||||||
<MemoDisplaySettingMenu className="absolute right-2 top-2 text-muted-foreground" />
|
<MemoDisplaySettingMenu className="absolute right-2 top-2 text-sidebar-foreground" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -41,24 +41,24 @@ const UserBanner = (props: Props) => {
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="start">
|
<DropdownMenuContent align="start">
|
||||||
<DropdownMenuItem onClick={() => navigateTo(`/u/${encodeURIComponent(currentUser.username)}`)}>
|
<DropdownMenuItem onClick={() => navigateTo(`/u/${encodeURIComponent(currentUser.username)}`)}>
|
||||||
<SquareUserIcon className="w-4 h-auto text-muted-foreground" />
|
<SquareUserIcon className="size-4 text-muted-foreground" />
|
||||||
<span className="truncate">{t("common.profile")}</span>
|
{t("common.profile")}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => navigateTo(Routes.ARCHIVED)}>
|
<DropdownMenuItem onClick={() => navigateTo(Routes.ARCHIVED)}>
|
||||||
<ArchiveIcon className="w-4 h-auto text-muted-foreground" />
|
<ArchiveIcon className="size-4 text-muted-foreground" />
|
||||||
<span className="truncate">{t("common.archived")}</span>
|
{t("common.archived")}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => navigateTo(Routes.INBOX)}>
|
<DropdownMenuItem onClick={() => navigateTo(Routes.INBOX)}>
|
||||||
<BellIcon className="w-4 h-auto text-muted-foreground" />
|
<BellIcon className="size-4 text-muted-foreground" />
|
||||||
<span className="truncate">{t("common.inbox")}</span>
|
{t("common.inbox")}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => navigateTo(Routes.SETTING)}>
|
<DropdownMenuItem onClick={() => navigateTo(Routes.SETTING)}>
|
||||||
<SettingsIcon className="w-4 h-auto text-muted-foreground" />
|
<SettingsIcon className="size-4 text-muted-foreground" />
|
||||||
<span className="truncate">{t("common.settings")}</span>
|
{t("common.settings")}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={handleSignOut}>
|
<DropdownMenuItem onClick={handleSignOut}>
|
||||||
<LogOutIcon className="w-4 h-auto text-muted-foreground" />
|
<LogOutIcon className="size-4 text-muted-foreground" />
|
||||||
<span className="truncate">{t("common.sign-out")}</span>
|
{t("common.sign-out")}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,14 @@ function SelectTrigger({
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
||||||
size?: "sm" | "default";
|
size?: "xs" | "sm" | "default";
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.Trigger
|
<SelectPrimitive.Trigger
|
||||||
data-slot="select-trigger"
|
data-slot="select-trigger"
|
||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-border data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-2 py-1 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-8 data-[size=sm]:h-7 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"border-border data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between rounded-md border bg-transparent text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-8 data-[size=default]:gap-2 data-[size=default]:px-2 data-[size=default]:py-1 data-[size=sm]:h-7 data-[size=sm]:gap-2 data-[size=sm]:px-2 data-[size=sm]:py-1 data-[size=xs]:h-6 data-[size=xs]:gap-1 data-[size=xs]:px-1 data-[size=xs]:py-0.5 data-[size=xs]:text-xs *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@
|
||||||
"visibility": {
|
"visibility": {
|
||||||
"disabled": "Public memos are disabled",
|
"disabled": "Public memos are disabled",
|
||||||
"private": "Private",
|
"private": "Private",
|
||||||
"protected": "Workspace",
|
"protected": "Protected",
|
||||||
"public": "Public"
|
"public": "Public"
|
||||||
},
|
},
|
||||||
"list": "List",
|
"list": "List",
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@
|
||||||
"visibility": {
|
"visibility": {
|
||||||
"disabled": "Memo publik dinonaktifkan",
|
"disabled": "Memo publik dinonaktifkan",
|
||||||
"private": "Pribadi",
|
"private": "Pribadi",
|
||||||
"protected": "Workspace",
|
"protected": "Protected",
|
||||||
"public": "Publik"
|
"public": "Publik"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -346,4 +346,4 @@
|
||||||
"delete-tag": "Hapus Tag",
|
"delete-tag": "Hapus Tag",
|
||||||
"no-tag-found": "Tidak ada tag ditemukan"
|
"no-tag-found": "Tidak ada tag ditemukan"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Reference in a new issue