Migrate remaining select menus to Radix (#9599)

* InputMemberPermissionSelect

* api key expiry

* invite permission

* templatize location
This commit is contained in:
Hemachandar
2025-07-11 08:26:04 +05:30
committed by GitHub
parent 2e31f87b34
commit e469e5771e
10 changed files with 71 additions and 74 deletions
+29 -17
View File
@@ -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>;
`;
+12 -1
View File
@@ -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;
+2 -5
View File
@@ -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")}
+8 -7
View File
@@ -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)`
+9 -6
View File
@@ -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 }}>
+1 -1
View File
@@ -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",