diff --git a/app/components/Sharing/Collection/SharePopover.tsx b/app/components/Sharing/Collection/SharePopover.tsx index 1dfbed8aa..d3ea537ee 100644 --- a/app/components/Sharing/Collection/SharePopover.tsx +++ b/app/components/Sharing/Collection/SharePopover.tsx @@ -1,12 +1,11 @@ import { isEmail } from "class-validator"; import { m } from "framer-motion"; import { observer } from "mobx-react"; -import { BackIcon, LinkIcon, UserIcon } from "outline-icons"; +import { BackIcon, UserIcon } from "outline-icons"; import * as React from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; -import styled, { useTheme } from "styled-components"; -import Flex from "@shared/components/Flex"; +import { useTheme } from "styled-components"; import Squircle from "@shared/components/Squircle"; import { CollectionPermission } from "@shared/types"; import Collection from "~/models/Collection"; @@ -14,16 +13,10 @@ import Group from "~/models/Group"; import Share from "~/models/Share"; import User from "~/models/User"; import Avatar, { AvatarSize } from "~/components/Avatar/Avatar"; -import { Inner } from "~/components/Button"; -import ButtonSmall from "~/components/ButtonSmall"; -import CopyToClipboard from "~/components/CopyToClipboard"; -import InputMemberPermissionSelect from "~/components/InputMemberPermissionSelect"; import InputSelectPermission from "~/components/InputSelectPermission"; import NudeButton from "~/components/NudeButton"; -import Tooltip from "~/components/Tooltip"; import { createAction } from "~/actions"; import { UserSection } from "~/actions/sections"; -import useActionContext from "~/hooks/useActionContext"; import useBoolean from "~/hooks/useBoolean"; import useCurrentTeam from "~/hooks/useCurrentTeam"; import useKeyDown from "~/hooks/useKeyDown"; @@ -32,12 +25,15 @@ import useStores from "~/hooks/useStores"; import { Permission } from "~/types"; import { collectionPath, urlify } from "~/utils/routeHelpers"; import { Wrapper, presence } from "../components"; +import { CopyLinkButton } from "../components/CopyLinkButton"; import { ListItem } from "../components/ListItem"; +import { PermissionAction } from "../components/PermissionAction"; import { SearchInput } from "../components/SearchInput"; import { Suggestions } from "../components/Suggestions"; import CollectionMemberList from "./CollectionMemberList"; type Props = { + /** The collection to share. */ collection: Collection; /** The existing share model, if any. */ share: Share | null | undefined; @@ -62,8 +58,6 @@ function SharePopover({ collection, visible, onRequestClose }: Props) { const [permission, setPermission] = React.useState( CollectionPermission.Read ); - const timeout = React.useRef>(); - const context = useActionContext(); useKeyDown( "Escape", @@ -82,6 +76,14 @@ function SharePopover({ collection, visible, onRequestClose }: Props) { } ); + // Hide the picker when the popover is closed + React.useEffect(() => { + if (visible) { + setPendingIds([]); + hidePicker(); + } + }, [hidePicker, visible]); + // Clear the query when picker is closed React.useEffect(() => { if (!picker) { @@ -95,20 +97,6 @@ function SharePopover({ collection, visible, onRequestClose }: Props) { } }, [visible]); - const handleCopied = React.useCallback(() => { - onRequestClose(); - - timeout.current = setTimeout(() => { - toast.message(t("Link copied to clipboard")); - }, 100); - - return () => { - if (timeout.current) { - clearTimeout(timeout.current); - } - }; - }, [onRequestClose, t]); - const handleQuery = React.useCallback( (event) => { showPicker(); @@ -284,35 +272,19 @@ function SharePopover({ collection, visible, onRequestClose }: Props) { const rightButton = picker ? ( pendingIds.length ? ( - - setPermission(value)} - value={permission} - labelHidden - nude - /> - - {t("Add")} - - + setPermission(value)} + key="invite" + /> ) : null ) : ( - - - - - - - + ); return ( @@ -373,14 +345,4 @@ function SharePopover({ collection, visible, onRequestClose }: Props) { ); } -const InputPermissionSelect = styled(InputMemberPermissionSelect)` - font-size: 13px; - height: 26px; - - ${Inner} { - line-height: 26px; - min-height: 26px; - } -`; - export default observer(SharePopover); diff --git a/app/components/Sharing/Document/SharePopover.tsx b/app/components/Sharing/Document/SharePopover.tsx index a48651c4d..f3ae1ad79 100644 --- a/app/components/Sharing/Document/SharePopover.tsx +++ b/app/components/Sharing/Document/SharePopover.tsx @@ -1,27 +1,19 @@ import { isEmail } from "class-validator"; import { m } from "framer-motion"; import { observer } from "mobx-react"; -import { BackIcon, LinkIcon } from "outline-icons"; +import { BackIcon } from "outline-icons"; import * as React from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; -import styled from "styled-components"; -import Flex from "@shared/components/Flex"; import { DocumentPermission } from "@shared/types"; import Document from "~/models/Document"; import Share from "~/models/Share"; import User from "~/models/User"; import Avatar from "~/components/Avatar"; import { AvatarSize } from "~/components/Avatar/Avatar"; -import { Inner } from "~/components/Button"; -import ButtonSmall from "~/components/ButtonSmall"; -import CopyToClipboard from "~/components/CopyToClipboard"; -import InputMemberPermissionSelect from "~/components/InputMemberPermissionSelect"; import NudeButton from "~/components/NudeButton"; -import Tooltip from "~/components/Tooltip"; import { createAction } from "~/actions"; import { UserSection } from "~/actions/sections"; -import useActionContext from "~/hooks/useActionContext"; import useBoolean from "~/hooks/useBoolean"; import useCurrentTeam from "~/hooks/useCurrentTeam"; import useKeyDown from "~/hooks/useKeyDown"; @@ -30,6 +22,8 @@ import useStores from "~/hooks/useStores"; import { Permission } from "~/types"; import { documentPath, urlify } from "~/utils/routeHelpers"; import { Separator, Wrapper, presence } from "../components"; +import { CopyLinkButton } from "../components/CopyLinkButton"; +import { PermissionAction } from "../components/PermissionAction"; import { SearchInput } from "../components/SearchInput"; import { Suggestions } from "../components/Suggestions"; import DocumentMembersList from "./DocumentMemberList"; @@ -59,13 +53,10 @@ function SharePopover({ const team = useCurrentTeam(); const { t } = useTranslation(); const can = usePolicy(document); - const linkButtonRef = React.useRef(null); - const context = useActionContext(); const [hasRendered, setHasRendered] = React.useState(visible); const { users, userMemberships } = useStores(); const [query, setQuery] = React.useState(""); const [picker, showPicker, hidePicker] = useBoolean(); - const timeout = React.useRef>(); const [invitedInSession, setInvitedInSession] = React.useState([]); const [pendingIds, setPendingIds] = React.useState([]); const collectionSharingDisabled = document.collection?.sharing === false; @@ -113,20 +104,6 @@ function SharePopover({ } }, [picker]); - const handleCopied = React.useCallback(() => { - onRequestClose(); - - timeout.current = setTimeout(() => { - toast.message(t("Link copied to clipboard")); - }, 100); - - return () => { - if (timeout.current) { - clearTimeout(timeout.current); - } - }; - }, [onRequestClose, t]); - const inviteAction = React.useMemo( () => createAction({ @@ -262,35 +239,19 @@ function SharePopover({ const rightButton = picker ? ( pendingIds.length ? ( - - setPermission(value)} - value={permission} - labelHidden - nude - /> - - {t("Add")} - - + setPermission(value)} + key="invite" + /> ) : null ) : ( - - - - - - - + ); return ( @@ -341,14 +302,4 @@ function SharePopover({ ); } -const InputPermissionSelect = styled(InputMemberPermissionSelect)` - font-size: 13px; - height: 26px; - - ${Inner} { - line-height: 26px; - min-height: 26px; - } -`; - export default observer(SharePopover); diff --git a/app/components/Sharing/components/CopyLinkButton.tsx b/app/components/Sharing/components/CopyLinkButton.tsx new file mode 100644 index 000000000..7d40576b3 --- /dev/null +++ b/app/components/Sharing/components/CopyLinkButton.tsx @@ -0,0 +1,47 @@ +import { LinkIcon } from "outline-icons"; +import * as React from "react"; +import { useTranslation } from "react-i18next"; +import { toast } from "sonner"; +import CopyToClipboard from "~/components/CopyToClipboard"; +import NudeButton from "~/components/NudeButton"; +import Tooltip from "~/components/Tooltip"; + +export function CopyLinkButton({ + url, + onCopy, +}: { + url: string; + onCopy: () => void; +}) { + const { t } = useTranslation(); + const timeout = React.useRef>(); + + const handleCopied = React.useCallback(() => { + onCopy(); + + timeout.current = setTimeout(() => { + toast.message(t("Link copied to clipboard")); + }, 100); + + return () => { + if (timeout.current) { + clearTimeout(timeout.current); + } + }; + }, [onCopy, t]); + + return ( + + + + + + + + ); +} diff --git a/app/components/Sharing/components/PermissionAction.tsx b/app/components/Sharing/components/PermissionAction.tsx new file mode 100644 index 000000000..c5fa42612 --- /dev/null +++ b/app/components/Sharing/components/PermissionAction.tsx @@ -0,0 +1,53 @@ +import * as React from "react"; +import { useTranslation } from "react-i18next"; +import styled from "styled-components"; +import Flex from "@shared/components/Flex"; +import { CollectionPermission, DocumentPermission } from "@shared/types"; +import { Inner } from "~/components/Button"; +import ButtonSmall from "~/components/ButtonSmall"; +import Fade from "~/components/Fade"; +import InputMemberPermissionSelect from "~/components/InputMemberPermissionSelect"; +import useActionContext from "~/hooks/useActionContext"; +import { Action, Permission } from "~/types"; + +export function PermissionAction({ + permission, + permissions, + action, + onChange, +}: { + permission: CollectionPermission | DocumentPermission; + permissions: Permission[]; + action: Action; + onChange: (permission: CollectionPermission | DocumentPermission) => void; +}) { + const { t } = useTranslation(); + const context = useActionContext(); + + return ( + + + + + {t("Add")} + + + + ); +} + +const InputPermissionSelect = styled(InputMemberPermissionSelect)` + font-size: 13px; + height: 26px; + + ${Inner} { + line-height: 26px; + min-height: 26px; + } +`; diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json index 924c1ccd1..b93e77472 100644 --- a/shared/i18n/locales/en_US/translation.json +++ b/shared/i18n/locales/en_US/translation.json @@ -271,9 +271,9 @@ "{{ count }} people added to the collection_plural": "{{ count }} people added to the collection", "{{ count }} people and {{ count2 }} groups added to the collection": "{{ count }} people and {{ count2 }} groups added to the collection", "{{ count }} people and {{ count2 }} groups added to the collection_plural": "{{ count }} people and {{ count2 }} groups added to the collection", - "Add": "Add", "All members": "All members", "Everyone in the workspace": "Everyone in the workspace", + "Add": "Add", "Add or invite": "Add or invite", "Viewer": "Viewer", "Editor": "Editor",