mirror of
https://github.com/outline/outline.git
synced 2025-12-21 02:29:41 -06:00
InputSelectPermission component (#9585)
This commit is contained in:
@@ -14,7 +14,7 @@ import Collection from "~/models/Collection";
|
|||||||
import Button from "~/components/Button";
|
import Button from "~/components/Button";
|
||||||
import Flex from "~/components/Flex";
|
import Flex from "~/components/Flex";
|
||||||
import Input from "~/components/Input";
|
import Input from "~/components/Input";
|
||||||
import InputSelectPermission from "~/components/InputSelectPermission";
|
import { InputSelectPermission } from "~/components/InputSelectPermission";
|
||||||
import { createLazyComponent } from "~/components/LazyLoad";
|
import { createLazyComponent } from "~/components/LazyLoad";
|
||||||
import Switch from "~/components/Switch";
|
import Switch from "~/components/Switch";
|
||||||
import Text from "~/components/Text";
|
import Text from "~/components/Text";
|
||||||
@@ -172,7 +172,7 @@ export const CollectionForm = observer(function CollectionForm_({
|
|||||||
) => {
|
) => {
|
||||||
field.onChange(value === EmptySelectValue ? null : value);
|
field.onChange(value === EmptySelectValue ? null : value);
|
||||||
}}
|
}}
|
||||||
note={t(
|
help={t(
|
||||||
"The default access for workspace members, you can share with more users or groups later."
|
"The default access for workspace members, you can share with more users or groups later."
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
|
import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
|
||||||
|
import { QuestionMarkIcon } from "outline-icons";
|
||||||
import { transparentize } from "polished";
|
import { transparentize } from "polished";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
import { s } from "@shared/styles";
|
||||||
import Text from "~/components/Text";
|
import Text from "~/components/Text";
|
||||||
import useMobile from "~/hooks/useMobile";
|
import useMobile from "~/hooks/useMobile";
|
||||||
import Separator from "./ContextMenu/Separator";
|
import Separator from "./ContextMenu/Separator";
|
||||||
import Flex from "./Flex";
|
import Flex from "./Flex";
|
||||||
import { LabelText } from "./Input";
|
import { LabelText } from "./Input";
|
||||||
|
import NudeButton from "./NudeButton";
|
||||||
import Scrollable from "./Scrollable";
|
import Scrollable from "./Scrollable";
|
||||||
import { IconWrapper } from "./Sidebar/components/SidebarLink";
|
import { IconWrapper } from "./Sidebar/components/SidebarLink";
|
||||||
|
import Tooltip from "./Tooltip";
|
||||||
import {
|
import {
|
||||||
Drawer,
|
Drawer,
|
||||||
DrawerContent,
|
DrawerContent,
|
||||||
@@ -53,7 +57,7 @@ type Props = {
|
|||||||
/* Options to display in the select menu. */
|
/* Options to display in the select menu. */
|
||||||
options: Option[];
|
options: Option[];
|
||||||
/* Current chosen value. */
|
/* Current chosen value. */
|
||||||
value?: string;
|
value?: string | null;
|
||||||
/* Callback when an option is selected. */
|
/* Callback when an option is selected. */
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
/* ARIA label for accessibility. */
|
/* ARIA label for accessibility. */
|
||||||
@@ -66,231 +70,259 @@ type Props = {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
/* When true, width of the menu trigger is restricted. Otherwise, takes up the full width of parent. */
|
/* When true, width of the menu trigger is restricted. Otherwise, takes up the full width of parent. */
|
||||||
short?: boolean;
|
short?: boolean;
|
||||||
|
/** Display a tooltip with the descriptive help text about the select menu. */
|
||||||
|
help?: string;
|
||||||
} & TriggerButtonProps;
|
} & TriggerButtonProps;
|
||||||
|
|
||||||
export function InputSelectNew(props: Props) {
|
export const InputSelectNew = React.forwardRef<HTMLButtonElement, Props>(
|
||||||
const {
|
(props, ref) => {
|
||||||
options,
|
const {
|
||||||
value,
|
options,
|
||||||
onChange,
|
value,
|
||||||
ariaLabel,
|
onChange,
|
||||||
label,
|
ariaLabel,
|
||||||
hideLabel,
|
label,
|
||||||
disabled,
|
hideLabel,
|
||||||
short,
|
short,
|
||||||
...triggerProps
|
help,
|
||||||
} = props;
|
...triggerProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
const [localValue, setLocalValue] = React.useState(value);
|
const [localValue, setLocalValue] = React.useState(value);
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
|
|
||||||
const triggerRef =
|
const contentRef =
|
||||||
React.useRef<React.ElementRef<typeof InputSelectTrigger>>(null);
|
React.useRef<React.ElementRef<typeof InputSelectContent>>(null);
|
||||||
const contentRef =
|
|
||||||
React.useRef<React.ElementRef<typeof InputSelectContent>>(null);
|
|
||||||
|
|
||||||
const isMobile = useMobile();
|
const isMobile = useMobile();
|
||||||
|
|
||||||
const placeholder = `Select a ${ariaLabel.toLowerCase()}`;
|
const placeholder = `Select a ${ariaLabel.toLowerCase()}`;
|
||||||
const optionsHaveIcon = options.some(
|
const optionsHaveIcon = options.some(
|
||||||
(opt) => opt.type === "item" && !!opt.icon
|
(opt) => opt.type === "item" && !!opt.icon
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderOption = React.useCallback(
|
const renderOption = React.useCallback(
|
||||||
(option: Option) => {
|
(option: Option) => {
|
||||||
if (option.type === "separator") {
|
if (option.type === "separator") {
|
||||||
return <InputSelectSeparator />;
|
return <InputSelectSeparator />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InputSelectItem key={option.value} value={option.value}>
|
||||||
|
<Option option={option} optionsHaveIcon={optionsHaveIcon} />
|
||||||
|
</InputSelectItem>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[optionsHaveIcon]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onValueChange = React.useCallback(
|
||||||
|
async (val: string) => {
|
||||||
|
setLocalValue(val);
|
||||||
|
onChange(val);
|
||||||
|
},
|
||||||
|
[onChange, setLocalValue]
|
||||||
|
);
|
||||||
|
|
||||||
|
const enablePointerEvents = React.useCallback(() => {
|
||||||
|
if (contentRef.current) {
|
||||||
|
contentRef.current.style.pointerEvents = "auto";
|
||||||
}
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const disablePointerEvents = React.useCallback(() => {
|
||||||
|
if (contentRef.current) {
|
||||||
|
contentRef.current.style.pointerEvents = "none";
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setLocalValue(value);
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
return (
|
return (
|
||||||
<InputSelectItem key={option.value} value={option.value}>
|
<MobileSelect
|
||||||
<Option option={option} optionsHaveIcon={optionsHaveIcon} />
|
ref={ref}
|
||||||
</InputSelectItem>
|
{...props}
|
||||||
|
value={localValue}
|
||||||
|
onChange={onValueChange}
|
||||||
|
placeholder={placeholder}
|
||||||
|
optionsHaveIcon={optionsHaveIcon}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
},
|
|
||||||
[optionsHaveIcon]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onValueChange = React.useCallback(
|
|
||||||
async (val: string) => {
|
|
||||||
setLocalValue(val);
|
|
||||||
onChange(val);
|
|
||||||
},
|
|
||||||
[onChange, setLocalValue]
|
|
||||||
);
|
|
||||||
|
|
||||||
const enablePointerEvents = React.useCallback(() => {
|
|
||||||
if (contentRef.current) {
|
|
||||||
contentRef.current.style.pointerEvents = "auto";
|
|
||||||
}
|
}
|
||||||
}, []);
|
|
||||||
|
|
||||||
const disablePointerEvents = React.useCallback(() => {
|
|
||||||
if (contentRef.current) {
|
|
||||||
contentRef.current.style.pointerEvents = "none";
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
setLocalValue(value);
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
if (isMobile) {
|
|
||||||
return (
|
return (
|
||||||
<MobileSelect
|
<Wrapper short={short}>
|
||||||
{...props}
|
<Label text={label} hidden={hideLabel ?? false} help={help} />
|
||||||
value={localValue}
|
<InputSelectRoot
|
||||||
onChange={onValueChange}
|
open={open}
|
||||||
placeholder={placeholder}
|
onOpenChange={setOpen}
|
||||||
optionsHaveIcon={optionsHaveIcon}
|
value={localValue ?? undefined}
|
||||||
/>
|
onValueChange={onValueChange}
|
||||||
|
>
|
||||||
|
<InputSelectTrigger
|
||||||
|
ref={ref}
|
||||||
|
placeholder={placeholder}
|
||||||
|
{...triggerProps}
|
||||||
|
/>
|
||||||
|
<InputSelectContent
|
||||||
|
ref={contentRef}
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
onAnimationStart={disablePointerEvents}
|
||||||
|
onAnimationEnd={enablePointerEvents}
|
||||||
|
>
|
||||||
|
{options.map(renderOption)}
|
||||||
|
</InputSelectContent>
|
||||||
|
</InputSelectRoot>
|
||||||
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
return (
|
InputSelectNew.displayName = "InputSelect";
|
||||||
<Wrapper short={short}>
|
|
||||||
<Label text={label} hidden={hideLabel ?? false} />
|
|
||||||
<InputSelectRoot
|
|
||||||
open={open}
|
|
||||||
onOpenChange={setOpen}
|
|
||||||
value={localValue}
|
|
||||||
onValueChange={onValueChange}
|
|
||||||
>
|
|
||||||
<InputSelectTrigger
|
|
||||||
ref={triggerRef}
|
|
||||||
placeholder={placeholder}
|
|
||||||
{...triggerProps}
|
|
||||||
/>
|
|
||||||
<InputSelectContent
|
|
||||||
ref={contentRef}
|
|
||||||
aria-label={ariaLabel}
|
|
||||||
onAnimationStart={disablePointerEvents}
|
|
||||||
onAnimationEnd={enablePointerEvents}
|
|
||||||
>
|
|
||||||
{options.map(renderOption)}
|
|
||||||
</InputSelectContent>
|
|
||||||
</InputSelectRoot>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type MobileSelectProps = Props & {
|
type MobileSelectProps = Props & {
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
optionsHaveIcon: boolean;
|
optionsHaveIcon: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function MobileSelect(props: MobileSelectProps) {
|
const MobileSelect = React.forwardRef<HTMLButtonElement, MobileSelectProps>(
|
||||||
const {
|
(props, ref) => {
|
||||||
options,
|
const {
|
||||||
value,
|
options,
|
||||||
onChange,
|
value,
|
||||||
ariaLabel,
|
onChange,
|
||||||
label,
|
ariaLabel,
|
||||||
hideLabel,
|
label,
|
||||||
disabled,
|
hideLabel,
|
||||||
short,
|
disabled,
|
||||||
placeholder,
|
short,
|
||||||
optionsHaveIcon,
|
placeholder,
|
||||||
...triggerProps
|
optionsHaveIcon,
|
||||||
} = props;
|
...triggerProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
const contentRef = React.useRef<React.ElementRef<typeof DrawerContent>>(null);
|
const contentRef =
|
||||||
|
React.useRef<React.ElementRef<typeof DrawerContent>>(null);
|
||||||
|
|
||||||
const selectedOption = React.useMemo(
|
const selectedOption = React.useMemo(
|
||||||
() =>
|
() =>
|
||||||
value
|
value
|
||||||
? options.find((opt) => opt.type === "item" && opt.value === value)
|
? options.find((opt) => opt.type === "item" && opt.value === value)
|
||||||
: undefined,
|
: undefined,
|
||||||
[value, options]
|
[value, options]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSelect = React.useCallback(
|
const handleSelect = React.useCallback(
|
||||||
async (val: string) => {
|
async (val: string) => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
onChange(val);
|
onChange(val);
|
||||||
},
|
},
|
||||||
[onChange]
|
[onChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderOption = React.useCallback(
|
const renderOption = React.useCallback(
|
||||||
(option: Option) => {
|
(option: Option) => {
|
||||||
if (option.type === "separator") {
|
if (option.type === "separator") {
|
||||||
return <Separator />;
|
return <Separator />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSelected = option === selectedOption;
|
const isSelected = option === selectedOption;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectItemWrapper
|
<SelectItemWrapper
|
||||||
key={option.value}
|
key={option.value}
|
||||||
onClick={() => handleSelect(option.value)}
|
onClick={() => handleSelect(option.value)}
|
||||||
data-state={isSelected ? "checked" : "unchecked"}
|
data-state={isSelected ? "checked" : "unchecked"}
|
||||||
>
|
|
||||||
<Option option={option} optionsHaveIcon={optionsHaveIcon} />
|
|
||||||
{isSelected && <SelectItemIndicator />}
|
|
||||||
</SelectItemWrapper>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[handleSelect, selectedOption, optionsHaveIcon]
|
|
||||||
);
|
|
||||||
|
|
||||||
const enablePointerEvents = React.useCallback(() => {
|
|
||||||
if (contentRef.current) {
|
|
||||||
contentRef.current.style.pointerEvents = "auto";
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const disablePointerEvents = React.useCallback(() => {
|
|
||||||
if (contentRef.current) {
|
|
||||||
contentRef.current.style.pointerEvents = "none";
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<Label text={label} hidden={hideLabel ?? false} />
|
|
||||||
<Drawer open={open} onOpenChange={setOpen}>
|
|
||||||
<DrawerTrigger asChild>
|
|
||||||
<SelectButton
|
|
||||||
{...triggerProps}
|
|
||||||
neutral
|
|
||||||
disclosure
|
|
||||||
data-placeholder={selectedOption ? false : ""}
|
|
||||||
>
|
>
|
||||||
{selectedOption ? (
|
<Option option={option} optionsHaveIcon={optionsHaveIcon} />
|
||||||
<Option
|
{isSelected && <SelectItemIndicator />}
|
||||||
option={selectedOption as Item}
|
</SelectItemWrapper>
|
||||||
optionsHaveIcon={optionsHaveIcon}
|
);
|
||||||
/>
|
},
|
||||||
) : (
|
[handleSelect, selectedOption, optionsHaveIcon]
|
||||||
<>{placeholder}</>
|
);
|
||||||
)}
|
|
||||||
</SelectButton>
|
|
||||||
</DrawerTrigger>
|
|
||||||
<DrawerContent
|
|
||||||
ref={contentRef}
|
|
||||||
aria-label={ariaLabel}
|
|
||||||
onAnimationStart={disablePointerEvents}
|
|
||||||
onAnimationEnd={enablePointerEvents}
|
|
||||||
>
|
|
||||||
<DrawerTitle hidden={!label}>{label ?? ariaLabel}</DrawerTitle>
|
|
||||||
<StyledScrollable hiddenScrollbars>
|
|
||||||
{options.map(renderOption)}
|
|
||||||
</StyledScrollable>
|
|
||||||
</DrawerContent>
|
|
||||||
</Drawer>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Label({ text, hidden }: { text: string; hidden: boolean }) {
|
const enablePointerEvents = React.useCallback(() => {
|
||||||
const labelText = <LabelText>{text}</LabelText>;
|
if (contentRef.current) {
|
||||||
|
contentRef.current.style.pointerEvents = "auto";
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const disablePointerEvents = React.useCallback(() => {
|
||||||
|
if (contentRef.current) {
|
||||||
|
contentRef.current.style.pointerEvents = "none";
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<Label text={label} hidden={hideLabel ?? false} />
|
||||||
|
<Drawer open={open} onOpenChange={setOpen}>
|
||||||
|
<DrawerTrigger asChild>
|
||||||
|
<SelectButton
|
||||||
|
ref={ref}
|
||||||
|
{...triggerProps}
|
||||||
|
neutral
|
||||||
|
disclosure
|
||||||
|
data-placeholder={selectedOption ? false : ""}
|
||||||
|
>
|
||||||
|
{selectedOption ? (
|
||||||
|
<Option
|
||||||
|
option={selectedOption as Item}
|
||||||
|
optionsHaveIcon={optionsHaveIcon}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>{placeholder}</>
|
||||||
|
)}
|
||||||
|
</SelectButton>
|
||||||
|
</DrawerTrigger>
|
||||||
|
<DrawerContent
|
||||||
|
ref={contentRef}
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
onAnimationStart={disablePointerEvents}
|
||||||
|
onAnimationEnd={enablePointerEvents}
|
||||||
|
>
|
||||||
|
<DrawerTitle hidden={!label}>{label ?? ariaLabel}</DrawerTitle>
|
||||||
|
<StyledScrollable hiddenScrollbars>
|
||||||
|
{options.map(renderOption)}
|
||||||
|
</StyledScrollable>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
MobileSelect.displayName = "InputSelect";
|
||||||
|
|
||||||
|
function Label({
|
||||||
|
text,
|
||||||
|
hidden,
|
||||||
|
help,
|
||||||
|
}: {
|
||||||
|
text: string;
|
||||||
|
hidden: boolean;
|
||||||
|
help?: string;
|
||||||
|
}) {
|
||||||
|
const content = (
|
||||||
|
<Flex align="center" gap={2} style={{ marginBottom: "4px" }}>
|
||||||
|
<LabelText style={{ paddingBottom: 0 }}>{text}</LabelText>
|
||||||
|
{help ? (
|
||||||
|
<Tooltip content={help}>
|
||||||
|
<TooltipButton size={18}>
|
||||||
|
<QuestionMarkIcon size={18} />
|
||||||
|
</TooltipButton>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
|
||||||
return hidden ? (
|
return hidden ? (
|
||||||
<VisuallyHidden.Root>{labelText}</VisuallyHidden.Root>
|
<VisuallyHidden.Root>{content}</VisuallyHidden.Root>
|
||||||
) : (
|
) : (
|
||||||
labelText
|
content
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,3 +384,12 @@ const IconSpacer = styled.div`
|
|||||||
const StyledScrollable = styled(Scrollable)`
|
const StyledScrollable = styled(Scrollable)`
|
||||||
max-height: 75vh;
|
max-height: 75vh;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const TooltipButton = styled(NudeButton)`
|
||||||
|
color: ${s("textSecondary")};
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&[aria-expanded="true"] {
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,54 +1,67 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { $Diff } from "utility-types";
|
|
||||||
import { s } from "@shared/styles";
|
import { s } from "@shared/styles";
|
||||||
import { CollectionPermission } from "@shared/types";
|
import { CollectionPermission } from "@shared/types";
|
||||||
|
import {
|
||||||
|
InputSelectNew,
|
||||||
|
Option as OptionNew,
|
||||||
|
} from "~/components/InputSelectNew";
|
||||||
import { EmptySelectValue } from "~/types";
|
import { EmptySelectValue } from "~/types";
|
||||||
import InputSelect, { Props, Option, InputSelectRef } from "./InputSelect";
|
|
||||||
|
|
||||||
function InputSelectPermission(
|
type Props = {
|
||||||
props: $Diff<
|
shrink?: boolean;
|
||||||
Props,
|
} & Pick<
|
||||||
{
|
React.ComponentProps<typeof InputSelectNew>,
|
||||||
options: Array<Option>;
|
"value" | "onChange" | "disabled" | "hideLabel" | "nude" | "help"
|
||||||
ariaLabel: string;
|
>;
|
||||||
}
|
|
||||||
>,
|
|
||||||
ref: React.RefObject<InputSelectRef>
|
|
||||||
) {
|
|
||||||
const { value, onChange, ...rest } = props;
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
export const InputSelectPermission = React.forwardRef<HTMLButtonElement, Props>(
|
||||||
<Select
|
(props, ref) => {
|
||||||
ref={ref}
|
const { value, onChange, shrink, ...rest } = props;
|
||||||
label={t("Permission")}
|
const { t } = useTranslation();
|
||||||
options={[
|
|
||||||
|
const options = React.useMemo<OptionNew[]>(
|
||||||
|
() => [
|
||||||
{
|
{
|
||||||
|
type: "item",
|
||||||
label: t("View only"),
|
label: t("View only"),
|
||||||
value: CollectionPermission.Read,
|
value: CollectionPermission.Read,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
type: "item",
|
||||||
label: t("Can edit"),
|
label: t("Can edit"),
|
||||||
value: CollectionPermission.ReadWrite,
|
value: CollectionPermission.ReadWrite,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
divider: true,
|
type: "separator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "item",
|
||||||
label: t("No access"),
|
label: t("No access"),
|
||||||
value: EmptySelectValue,
|
value: EmptySelectValue,
|
||||||
},
|
},
|
||||||
]}
|
],
|
||||||
ariaLabel={t("Default access")}
|
[t]
|
||||||
value={value || EmptySelectValue}
|
);
|
||||||
onChange={onChange}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Select = styled(InputSelect)`
|
return (
|
||||||
|
<Select
|
||||||
|
ref={ref}
|
||||||
|
options={options}
|
||||||
|
value={value || EmptySelectValue}
|
||||||
|
onChange={onChange}
|
||||||
|
ariaLabel={t("Default access")}
|
||||||
|
label={t("Permission")}
|
||||||
|
$shrink={shrink}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
InputSelectPermission.displayName = "InputSelectPermission";
|
||||||
|
|
||||||
|
const Select = styled(InputSelectNew)<{ $shrink?: boolean }>`
|
||||||
color: ${s("textSecondary")};
|
color: ${s("textSecondary")};
|
||||||
|
${({ $shrink }) => !$shrink && "margin-bottom: 16px;"}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default React.forwardRef(InputSelectPermission);
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { CollectionPermission } from "@shared/types";
|
|||||||
import Collection from "~/models/Collection";
|
import Collection from "~/models/Collection";
|
||||||
import { Avatar, GroupAvatar, AvatarSize } from "~/components/Avatar";
|
import { Avatar, GroupAvatar, AvatarSize } from "~/components/Avatar";
|
||||||
import InputMemberPermissionSelect from "~/components/InputMemberPermissionSelect";
|
import InputMemberPermissionSelect from "~/components/InputMemberPermissionSelect";
|
||||||
import InputSelectPermission from "~/components/InputSelectPermission";
|
import { InputSelectPermission } from "~/components/InputSelectPermission";
|
||||||
import Scrollable from "~/components/Scrollable";
|
import Scrollable from "~/components/Scrollable";
|
||||||
import useMaxHeight from "~/hooks/useMaxHeight";
|
import useMaxHeight from "~/hooks/useMaxHeight";
|
||||||
import usePolicy from "~/hooks/usePolicy";
|
import usePolicy from "~/hooks/usePolicy";
|
||||||
@@ -121,7 +121,6 @@ export const AccessControlList = observer(
|
|||||||
actions={
|
actions={
|
||||||
<div style={{ marginRight: -8 }}>
|
<div style={{ marginRight: -8 }}>
|
||||||
<InputSelectPermission
|
<InputSelectPermission
|
||||||
style={{ margin: 0 }}
|
|
||||||
onChange={(
|
onChange={(
|
||||||
value: CollectionPermission | typeof EmptySelectValue
|
value: CollectionPermission | typeof EmptySelectValue
|
||||||
) => {
|
) => {
|
||||||
@@ -131,8 +130,9 @@ export const AccessControlList = observer(
|
|||||||
}}
|
}}
|
||||||
disabled={!can.update}
|
disabled={!can.update}
|
||||||
value={collection?.permission}
|
value={collection?.permission}
|
||||||
labelHidden
|
hideLabel
|
||||||
nude
|
nude
|
||||||
|
shrink
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ const InputSelectTrigger = React.forwardRef<
|
|||||||
React.ElementRef<typeof InputSelectPrimitive.Trigger>,
|
React.ElementRef<typeof InputSelectPrimitive.Trigger>,
|
||||||
InputSelectTriggerProps
|
InputSelectTriggerProps
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
const { placeholder, children, ...buttonProps } = props;
|
const { placeholder, children, nude, ...buttonProps } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InputSelectPrimitive.Trigger ref={ref} asChild>
|
<InputSelectPrimitive.Trigger ref={ref} asChild>
|
||||||
<SelectButton neutral disclosure {...buttonProps}>
|
<SelectButton neutral disclosure $nude={nude} {...buttonProps}>
|
||||||
<InputSelectPrimitive.Value placeholder={placeholder} />
|
<InputSelectPrimitive.Value placeholder={placeholder} />
|
||||||
</SelectButton>
|
</SelectButton>
|
||||||
</InputSelectPrimitive.Trigger>
|
</InputSelectPrimitive.Trigger>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { AttachmentPreset, CollectionPermission } from "@shared/types";
|
|||||||
import { bytesToHumanReadable } from "@shared/utils/files";
|
import { bytesToHumanReadable } from "@shared/utils/files";
|
||||||
import Button from "~/components/Button";
|
import Button from "~/components/Button";
|
||||||
import Flex from "~/components/Flex";
|
import Flex from "~/components/Flex";
|
||||||
import InputSelectPermission from "~/components/InputSelectPermission";
|
import { InputSelectPermission } from "~/components/InputSelectPermission";
|
||||||
import LoadingIndicator from "~/components/LoadingIndicator";
|
import LoadingIndicator from "~/components/LoadingIndicator";
|
||||||
import Text from "~/components/Text";
|
import Text from "~/components/Text";
|
||||||
import useStores from "~/hooks/useStores";
|
import useStores from "~/hooks/useStores";
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { ImportInput } from "@shared/schema";
|
|||||||
import { CollectionPermission, IntegrationService } from "@shared/types";
|
import { CollectionPermission, IntegrationService } from "@shared/types";
|
||||||
import Button from "~/components/Button";
|
import Button from "~/components/Button";
|
||||||
import Flex from "~/components/Flex";
|
import Flex from "~/components/Flex";
|
||||||
import InputSelectPermission from "~/components/InputSelectPermission";
|
import { InputSelectPermission } from "~/components/InputSelectPermission";
|
||||||
import Text from "~/components/Text";
|
import Text from "~/components/Text";
|
||||||
import useBoolean from "~/hooks/useBoolean";
|
import useBoolean from "~/hooks/useBoolean";
|
||||||
import useStores from "~/hooks/useStores";
|
import useStores from "~/hooks/useStores";
|
||||||
|
|||||||
@@ -289,11 +289,11 @@
|
|||||||
"Flags": "Flags",
|
"Flags": "Flags",
|
||||||
"Select a color": "Select a color",
|
"Select a color": "Select a color",
|
||||||
"Loading": "Loading",
|
"Loading": "Loading",
|
||||||
"Permission": "Permission",
|
|
||||||
"View only": "View only",
|
"View only": "View only",
|
||||||
"Can edit": "Can edit",
|
"Can edit": "Can edit",
|
||||||
"No access": "No access",
|
"No access": "No access",
|
||||||
"Default access": "Default access",
|
"Default access": "Default access",
|
||||||
|
"Permission": "Permission",
|
||||||
"Change Language": "Change Language",
|
"Change Language": "Change Language",
|
||||||
"Dismiss": "Dismiss",
|
"Dismiss": "Dismiss",
|
||||||
"You’re offline.": "You’re offline.",
|
"You’re offline.": "You’re offline.",
|
||||||
|
|||||||
Reference in New Issue
Block a user