mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
Migrate remaining select menus to Radix (#9599)
* InputMemberPermissionSelect * api key expiry * invite permission * templatize location
This commit is contained in:
@@ -2,37 +2,49 @@ import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { s } from "@shared/styles";
|
||||
import InputSelect, { Props as SelectProps } from "~/components/InputSelect";
|
||||
import { InputSelectNew, Option } from "~/components/InputSelectNew";
|
||||
import { EmptySelectValue, Permission } from "~/types";
|
||||
|
||||
type Props = Pick<
|
||||
React.ComponentProps<typeof InputSelectNew>,
|
||||
"value" | "onChange" | "disabled" | "className"
|
||||
>;
|
||||
|
||||
export default function InputMemberPermissionSelect(
|
||||
props: Partial<SelectProps> & { permissions: Permission[] }
|
||||
props: Props & { permissions: Permission[] }
|
||||
) {
|
||||
const { value, onChange, ...rest } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const options = React.useMemo<Option[]>(
|
||||
() =>
|
||||
props.permissions.reduce((acc, permission) => {
|
||||
if (permission.divider) {
|
||||
acc.push({ type: "separator" });
|
||||
}
|
||||
acc.push({
|
||||
...permission,
|
||||
type: "item",
|
||||
});
|
||||
return acc;
|
||||
}, [] as Option[]),
|
||||
[props.permissions]
|
||||
);
|
||||
|
||||
return (
|
||||
<Select
|
||||
label={t("Permissions")}
|
||||
options={props.permissions}
|
||||
ariaLabel={t("Permissions")}
|
||||
onChange={onChange}
|
||||
options={options}
|
||||
value={value || EmptySelectValue}
|
||||
labelHidden
|
||||
onChange={onChange}
|
||||
ariaLabel={t("Permissions")}
|
||||
label={t("Permissions")}
|
||||
hideLabel
|
||||
nude
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const Select = styled(InputSelect)`
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
border-color: transparent;
|
||||
box-shadow: none;
|
||||
const Select = styled(InputSelectNew)`
|
||||
color: ${s("textSecondary")};
|
||||
|
||||
select {
|
||||
margin: 0;
|
||||
}
|
||||
` as React.ComponentType<SelectProps>;
|
||||
`;
|
||||
|
||||
@@ -11,7 +11,6 @@ import Flex from "./Flex";
|
||||
import { LabelText } from "./Input";
|
||||
import NudeButton from "./NudeButton";
|
||||
import Scrollable from "./Scrollable";
|
||||
import { IconWrapper } from "./Sidebar/components/SidebarLink";
|
||||
import Tooltip from "./Tooltip";
|
||||
import {
|
||||
Drawer,
|
||||
@@ -375,6 +374,18 @@ const Description = styled(Text)`
|
||||
}
|
||||
`;
|
||||
|
||||
const IconWrapper = styled.span`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-left: -4px;
|
||||
margin-right: 4px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
const IconSpacer = styled.div`
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
||||
@@ -3,10 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { s } from "@shared/styles";
|
||||
import { CollectionPermission } from "@shared/types";
|
||||
import {
|
||||
InputSelectNew,
|
||||
Option as OptionNew,
|
||||
} from "~/components/InputSelectNew";
|
||||
import { InputSelectNew, Option } from "~/components/InputSelectNew";
|
||||
import { EmptySelectValue } from "~/types";
|
||||
|
||||
type Props = {
|
||||
@@ -21,7 +18,7 @@ export const InputSelectPermission = React.forwardRef<HTMLButtonElement, Props>(
|
||||
const { value, onChange, shrink, ...rest } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const options = React.useMemo<OptionNew[]>(
|
||||
const options = React.useMemo<Option[]>(
|
||||
() => [
|
||||
{
|
||||
type: "item",
|
||||
|
||||
@@ -161,7 +161,6 @@ export const AccessControlList = observer(
|
||||
actions={
|
||||
<div style={{ marginRight: -8 }}>
|
||||
<InputMemberPermissionSelect
|
||||
style={{ margin: 0 }}
|
||||
permissions={permissions}
|
||||
onChange={async (
|
||||
permission:
|
||||
@@ -189,8 +188,6 @@ export const AccessControlList = observer(
|
||||
}}
|
||||
disabled={!can.update}
|
||||
value={membership.permission}
|
||||
labelHidden
|
||||
nude
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
@@ -215,7 +212,6 @@ export const AccessControlList = observer(
|
||||
actions={
|
||||
<div style={{ marginRight: -8 }}>
|
||||
<InputMemberPermissionSelect
|
||||
style={{ margin: 0 }}
|
||||
permissions={permissions}
|
||||
onChange={async (
|
||||
permission:
|
||||
@@ -243,8 +239,6 @@ export const AccessControlList = observer(
|
||||
}}
|
||||
disabled={!can.update}
|
||||
value={membership.permission}
|
||||
labelHidden
|
||||
nude
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -160,7 +160,6 @@ function DocumentMembersList({ document, invitedInSession }: Props) {
|
||||
actions={
|
||||
<div style={{ marginRight: -8 }}>
|
||||
<InputMemberPermissionSelect
|
||||
style={{ margin: 0 }}
|
||||
permissions={permissions}
|
||||
onChange={async (
|
||||
permission: DocumentPermission | typeof EmptySelectValue
|
||||
@@ -180,8 +179,6 @@ function DocumentMembersList({ document, invitedInSession }: Props) {
|
||||
}}
|
||||
disabled={!can.manageUsers}
|
||||
value={membership.permission}
|
||||
labelHidden
|
||||
nude
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -30,8 +30,6 @@ export function PermissionAction({
|
||||
permissions={permissions}
|
||||
onChange={onChange}
|
||||
value={permission}
|
||||
labelHidden
|
||||
nude
|
||||
/>
|
||||
<ButtonSmall action={action} context={context}>
|
||||
{t("Add")}
|
||||
|
||||
@@ -4,13 +4,12 @@ import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { AvatarSize } from "~/components/Avatar";
|
||||
import CollectionIcon from "~/components/Icons/CollectionIcon";
|
||||
import InputSelect, { Option } from "~/components/InputSelect";
|
||||
import { InputSelectNew, Option } from "~/components/InputSelectNew";
|
||||
import TeamLogo from "~/components/TeamLogo";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useRequest from "~/hooks/useRequest";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import Label from "./Label";
|
||||
|
||||
type Props = {
|
||||
/** Collection ID to select by default. */
|
||||
@@ -37,13 +36,10 @@ const SelectLocation = ({ defaultCollectionId, onSelect }: Props) => {
|
||||
|
||||
const workspaceOption: Option | null = can.createTemplate
|
||||
? {
|
||||
label: (
|
||||
<Label
|
||||
icon={<TeamLogo model={team} size={AvatarSize.Toast} />}
|
||||
value={t("Workspace")}
|
||||
/>
|
||||
),
|
||||
type: "item",
|
||||
label: t("Workspace"),
|
||||
value: "workspace",
|
||||
icon: <TeamLogo model={team} size={AvatarSize.Toast} />,
|
||||
}
|
||||
: null;
|
||||
|
||||
@@ -54,13 +50,10 @@ const SelectLocation = ({ defaultCollectionId, onSelect }: Props) => {
|
||||
|
||||
if (canCollection.createDocument) {
|
||||
memo.push({
|
||||
label: (
|
||||
<Label
|
||||
icon={<CollectionIcon collection={collection} />}
|
||||
value={collection.name}
|
||||
/>
|
||||
),
|
||||
type: "item",
|
||||
label: collection.name,
|
||||
value: collection.id,
|
||||
icon: <CollectionIcon collection={collection} />,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -71,16 +64,7 @@ const SelectLocation = ({ defaultCollectionId, onSelect }: Props) => {
|
||||
|
||||
const options: Option[] = workspaceOption
|
||||
? collectionOptions.length
|
||||
? [
|
||||
workspaceOption,
|
||||
...collectionOptions.map((opt, idx) => {
|
||||
if (idx !== 0) {
|
||||
return opt;
|
||||
}
|
||||
opt.divider = true;
|
||||
return opt;
|
||||
}),
|
||||
]
|
||||
? [workspaceOption, { type: "separator" }, ...collectionOptions]
|
||||
: [workspaceOption]
|
||||
: collectionOptions;
|
||||
|
||||
@@ -100,9 +84,9 @@ const SelectLocation = ({ defaultCollectionId, onSelect }: Props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<InputSelect
|
||||
value={defaultCollectionId ?? "workspace"}
|
||||
<InputSelectNew
|
||||
options={options}
|
||||
value={defaultCollectionId ?? "workspace"}
|
||||
onChange={handleSelection}
|
||||
ariaLabel={t("Location")}
|
||||
label={t("Location")}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ApiKeyValidation } from "@shared/validations";
|
||||
import Button from "~/components/Button";
|
||||
import Flex from "~/components/Flex";
|
||||
import Input from "~/components/Input";
|
||||
import InputSelect, { Option } from "~/components/InputSelect";
|
||||
import { InputSelectNew, Option } from "~/components/InputSelectNew";
|
||||
import Text from "~/components/Text";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useUserLocale from "~/hooks/useUserLocale";
|
||||
@@ -42,6 +42,7 @@ function ApiKeyNew({ onSubmit }: Props) {
|
||||
const expiryOptions = React.useMemo<Option[]>(
|
||||
() =>
|
||||
[...ExpiryValues.entries()].map(([expType, { label }]) => ({
|
||||
type: "item",
|
||||
label,
|
||||
value: expType,
|
||||
})),
|
||||
@@ -123,12 +124,11 @@ function ApiKeyNew({ onSubmit }: Props) {
|
||||
</Text>
|
||||
<Flex align="center" gap={16}>
|
||||
<StyledExpirySelect
|
||||
options={expiryOptions}
|
||||
value={expiryType}
|
||||
onChange={handleExpiryTypeChange}
|
||||
ariaLabel={t("Expiration")}
|
||||
label={t("Expiration")}
|
||||
value={expiryType}
|
||||
options={expiryOptions}
|
||||
onChange={handleExpiryTypeChange}
|
||||
skipBodyScroll
|
||||
/>
|
||||
{expiryType === ExpiryType.Custom ? (
|
||||
<ExpiryDatePicker
|
||||
@@ -153,8 +153,9 @@ function ApiKeyNew({ onSubmit }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
const StyledExpirySelect = styled(InputSelect)`
|
||||
width: 150px;
|
||||
const StyledExpirySelect = styled(InputSelectNew)`
|
||||
width: 150px !important;
|
||||
margin-bottom: 16px;
|
||||
`;
|
||||
|
||||
const StyledExpiryText = styled(Text)`
|
||||
|
||||
@@ -12,7 +12,7 @@ import { UserValidation } from "@shared/validations";
|
||||
import Button from "~/components/Button";
|
||||
import Flex from "~/components/Flex";
|
||||
import Input from "~/components/Input";
|
||||
import InputSelect from "~/components/InputSelect";
|
||||
import { InputSelectNew, Option } from "~/components/InputSelectNew";
|
||||
import { ResizingHeightContainer } from "~/components/ResizingHeightContainer";
|
||||
import Text from "~/components/Text";
|
||||
import Tooltip from "~/components/Tooltip";
|
||||
@@ -131,11 +131,12 @@ function Invite({ onSubmit }: Props) {
|
||||
</span>
|
||||
) : undefined;
|
||||
|
||||
const options = React.useMemo(() => {
|
||||
const memo = [];
|
||||
const options = React.useMemo<Option[]>(() => {
|
||||
const memo: Option[] = [];
|
||||
|
||||
if (user.isAdmin) {
|
||||
memo.push({
|
||||
type: "item",
|
||||
label: t("Admin"),
|
||||
description: t("Can manage all workspace settings"),
|
||||
value: UserRole.Admin,
|
||||
@@ -145,11 +146,13 @@ function Invite({ onSubmit }: Props) {
|
||||
return [
|
||||
...memo,
|
||||
{
|
||||
type: "item",
|
||||
label: t("Editor"),
|
||||
description: t("Can create, edit, and delete documents"),
|
||||
value: UserRole.Member,
|
||||
},
|
||||
{
|
||||
type: "item",
|
||||
label: t("Viewer"),
|
||||
description: t("Can view and comment"),
|
||||
value: UserRole.Viewer,
|
||||
@@ -188,12 +191,12 @@ function Invite({ onSubmit }: Props) {
|
||||
</Text>
|
||||
)}
|
||||
<Flex gap={12} column>
|
||||
<InputSelect
|
||||
label={t("Invite as")}
|
||||
ariaLabel={t("Role")}
|
||||
<InputSelectNew
|
||||
options={options}
|
||||
onChange={(r) => setRole(r as UserRole)}
|
||||
value={role}
|
||||
ariaLabel={t("Role")}
|
||||
label={t("Invite as")}
|
||||
/>
|
||||
|
||||
<ResizingHeightContainer style={{ minHeight: 72, marginBottom: 8 }}>
|
||||
|
||||
@@ -758,8 +758,8 @@
|
||||
"Invite people to join your workspace. They can sign in with {{signinMethods}} or use their email address.": "Invite people to join your workspace. They can sign in with {{signinMethods}} or use their email address.",
|
||||
"Invite members to join your workspace. They will need to sign in with {{signinMethods}}.": "Invite members to join your workspace. They will need to sign in with {{signinMethods}}.",
|
||||
"As an admin you can also <2>enable email sign-in</2>.": "As an admin you can also <2>enable email sign-in</2>.",
|
||||
"Invite as": "Invite as",
|
||||
"Role": "Role",
|
||||
"Invite as": "Invite as",
|
||||
"Email": "Email",
|
||||
"Add another": "Add another",
|
||||
"Inviting": "Inviting",
|
||||
|
||||
Reference in New Issue
Block a user