import { uniq } from "es-toolkit/compat"; import { observer } from "mobx-react"; import { useMemo, useEffect, useCallback, Suspense } from "react"; import { Controller, useForm } from "react-hook-form"; import { Trans, useTranslation } from "react-i18next"; import styled from "styled-components"; import Icon from "@shared/components/Icon"; import { randomElement } from "@shared/random"; import { CollectionPermission, TeamPreference } from "@shared/types"; import type { Option } from "~/components/InputSelect"; import { IconLibrary } from "@shared/utils/IconLibrary"; import { colorPalette } from "@shared/utils/collections"; import { CollectionValidation } from "@shared/validations"; import type Collection from "~/models/Collection"; import Button from "~/components/Button"; import { Collapsible } from "~/components/Collapsible"; import Input from "~/components/Input"; import { InputSelect } from "~/components/InputSelect"; import { InputSelectPermission } from "~/components/InputSelectPermission"; import { createLazyComponent } from "~/components/LazyLoad"; import Switch from "~/components/Switch"; import Text from "~/components/Text"; import useBoolean from "~/hooks/useBoolean"; import useCurrentTeam from "~/hooks/useCurrentTeam"; import useStores from "~/hooks/useStores"; import { EmptySelectValue } from "~/types"; import { HStack } from "../primitives/HStack"; import { useDialogContext } from "~/components/DialogContext"; const IconPicker = createLazyComponent(() => import("~/components/IconPicker")); export type FormData = { name: string; icon: string; color: string | null; sharing: boolean; permission: CollectionPermission | undefined; commenting?: boolean | null; templateManagement: CollectionPermission; }; const useIconColor = (collection?: Collection) => { const { collections } = useStores(); const hasMultipleCollections = collections.orderedData.length > 1; const collectionColors = uniq( collections.orderedData.map((c) => c.color).filter(Boolean) ) as string[]; const iconColor = useMemo( () => collection?.color ?? // If all the existing collections have the same color, use that color, // otherwise pick a random color from the palette (hasMultipleCollections && collectionColors.length === 1 ? collectionColors[0] : randomElement(colorPalette)), [collection?.color] ); return iconColor; }; export const CollectionForm = observer(function CollectionForm_({ handleSubmit, collection, }: { handleSubmit: (data: FormData) => void; collection?: Collection; }) { const team = useCurrentTeam(); const { t } = useTranslation(); const dialog = useDialogContext(); const [hasOpenedIconPicker, setHasOpenedIconPicker] = useBoolean(false); const templateManagementOptions = useMemo( () => [ { type: "item", label: t("Managers"), value: CollectionPermission.Admin, }, { type: "item", label: t("Members"), value: CollectionPermission.ReadWrite, }, ], [t] ); const iconColor = useIconColor(collection); const fallbackIcon = ( ); const { register, handleSubmit: formHandleSubmit, formState, watch, control, setValue, setFocus, } = useForm({ mode: "all", defaultValues: { name: collection?.name ?? "", icon: collection?.icon, sharing: collection?.sharing ?? true, permission: collection?.permission, commenting: collection?.commenting ?? true, templateManagement: collection?.templateManagement ?? CollectionPermission.Admin, color: iconColor, }, }); const values = watch(); // Preload the IconPicker component on mount useEffect(() => { void IconPicker.preload(); }, []); useEffect(() => { // If the user hasn't picked an icon yet, go ahead and suggest one based on // the name of the collection. It's the little things sometimes. if (!hasOpenedIconPicker && !collection) { setValue( "icon", IconLibrary.findIconByKeyword(values.name) ?? values.icon ?? "collection" ); } }, [collection, hasOpenedIconPicker, setValue, values.name, values.icon]); useEffect(() => { setTimeout(() => setFocus("name", { shouldSelect: true }), 100); }, [setFocus]); const handleIconChange = useCallback( (icon: string, color: string) => { if (icon !== values.icon) { setFocus("name"); } setValue("icon", icon); setValue("color", color); }, [setFocus, setValue, values.icon] ); const initial = values.name.charAt(0).toUpperCase(); const options = ( <> ( <> { field.onChange(value as CollectionPermission); }} options={templateManagementOptions} label={t("Manage templates")} /> {t( "Choose who can create and edit templates in this collection." )} )} /> {team.sharing && ( ( )} /> )} {team.getPreference(TeamPreference.Commenting) && ( ( )} /> )} ); return (
Collections are used to group documents and choose permissions } autoComplete="off" autoFocus flex /> {/* Following controls are available in create flow, but moved elsewhere for edit */} {!collection && ( ( { field.onChange(value === EmptySelectValue ? null : value); }} help={t( "The default access for workspace members, you can share with more users or groups later." )} /> )} /> )} {collection ? ( options ) : ( dialog.setAnimating(true)} > {options} )} ); }); const StyledIconPicker = styled(IconPicker.Component)` margin-left: 4px; margin-right: 4px; `;