diff --git a/frontend/src/components/group/GroupCreateBox.tsx b/frontend/src/components/group/GroupCreateBox.tsx index bc29a53..af464fd 100644 --- a/frontend/src/components/group/GroupCreateBox.tsx +++ b/frontend/src/components/group/GroupCreateBox.tsx @@ -2,6 +2,7 @@ import {Box, Details, Form, Idiomatic as I, useFormState} from "@steffo/bluelib- import * as React from "react" import {useAuthorizationContext} from "../../contexts/authorization" import {useCacheContext} from "../../contexts/cache" +import {useFormSlug} from "../../hooks/useFormSlug" import {ManagedResource, ManagedViewSet} from "../../hooks/useManagedViewSet" import {SophonResearchGroup} from "../../types/SophonTypes" import {Validators} from "../../utils/Validators" @@ -37,7 +38,7 @@ export function GroupCreateBox({viewSet, resource}: GroupCreateBoxProps): JSX.El const name = useFormState( resource?.value.name ?? "", - Validators.notZeroLength, + Validators.mustContainElements, ) const description = @@ -59,10 +60,7 @@ export function GroupCreateBox({viewSet, resource}: GroupCreateBoxProps): JSX.El ) const slug = - React.useMemo( - () => resource ? resource.value.slug : name.value.replaceAll(/[^A-Za-z0-9-]/g, "-").toLowerCase(), - [resource, name], - ) + useFormSlug(resource, name.value) const canAdministrate = React.useMemo( diff --git a/frontend/src/components/notebook/NotebookCreateBox.tsx b/frontend/src/components/notebook/NotebookCreateBox.tsx index a217309..dfe7dd3 100644 --- a/frontend/src/components/notebook/NotebookCreateBox.tsx +++ b/frontend/src/components/notebook/NotebookCreateBox.tsx @@ -2,6 +2,7 @@ import {Box, Details, Form, Idiomatic as I, useFormState} from "@steffo/bluelib- import * as React from "react" import {useAuthorizationContext} from "../../contexts/authorization" import {useProjectContext} from "../../contexts/project" +import {useFormSlug} from "../../hooks/useFormSlug" import {ManagedResource, ManagedViewSet} from "../../hooks/useManagedViewSet" import {SophonNotebook} from "../../types/SophonTypes" import {Validators} from "../../utils/Validators" @@ -20,14 +21,11 @@ export function NotebookCreateBox({viewSet, resource}: NotebookCreateBoxProps): const name = useFormState( resource?.value.name ?? "", - Validators.notZeroLength, + Validators.mustContainElements, ) const slug = - React.useMemo( - () => resource ? resource.value.slug : name.value.replaceAll(/[^A-Za-z0-9-]/g, "-").toLowerCase(), - [resource, name], - ) + useFormSlug(resource, name.value) const image = useFormState( diff --git a/frontend/src/components/project/ProjectCreateBox.tsx b/frontend/src/components/project/ProjectCreateBox.tsx index c45661f..40de85c 100644 --- a/frontend/src/components/project/ProjectCreateBox.tsx +++ b/frontend/src/components/project/ProjectCreateBox.tsx @@ -2,6 +2,7 @@ import {Box, Details, Form, Idiomatic as I, useFormState} from "@steffo/bluelib- import * as React from "react" import {useAuthorizationContext} from "../../contexts/authorization" import {useGroupContext} from "../../contexts/group" +import {useFormSlug} from "../../hooks/useFormSlug" import {ManagedResource, ManagedViewSet} from "../../hooks/useManagedViewSet" import {SophonResearchProject} from "../../types/SophonTypes" import {Validators} from "../../utils/Validators" @@ -20,7 +21,7 @@ export function ProjectCreateBox({viewSet, resource}: ProjectCreateBoxProps): JS const name = useFormState( resource?.value.name ?? "", - Validators.notZeroLength, + Validators.mustContainElements, ) const description = @@ -36,10 +37,7 @@ export function ProjectCreateBox({viewSet, resource}: ProjectCreateBoxProps): JS ) const slug = - React.useMemo( - () => resource ? resource.value.slug : name.value.replaceAll(/[^A-Za-z0-9-]/g, "-").toLowerCase(), - [resource, name], - ) + useFormSlug(resource, name.value) const canAdministrate = React.useMemo( diff --git a/frontend/src/hooks/useFormSlug.ts b/frontend/src/hooks/useFormSlug.ts new file mode 100644 index 0000000..26945a2 --- /dev/null +++ b/frontend/src/hooks/useFormSlug.ts @@ -0,0 +1,18 @@ +import * as React from "react" +import {WithSlug} from "../types/ExtraTypes" +import {toSlug} from "../utils/Slug" +import {ManagedResource} from "./useManagedViewSet" + + +/** + * Hook to simplify displaying the "Slug" form field. + * + * @param resource - The {@link ManagedResource} that is currently being edited; if set, the slug will be fixed to the one of the resource. + * @param str - The string to convert to a slug if no resource is set. + */ +export function useFormSlug(resource: ManagedResource | undefined, str: string): string { + return React.useMemo( + () => resource ? resource.value.slug : toSlug(str), + [resource, str], + ) +} diff --git a/frontend/src/types/ExtraTypes.ts b/frontend/src/types/ExtraTypes.ts index e2810b7..c6ad749 100644 --- a/frontend/src/types/ExtraTypes.ts +++ b/frontend/src/types/ExtraTypes.ts @@ -31,4 +31,12 @@ export interface WithResource { */ export interface WithViewSet { viewSet: ManagedViewSet, +} + + +/** + * Props including a slug. + */ +export interface WithSlug { + slug: string, } \ No newline at end of file diff --git a/frontend/src/utils/Slug.ts b/frontend/src/utils/Slug.ts new file mode 100644 index 0000000..072b54c --- /dev/null +++ b/frontend/src/utils/Slug.ts @@ -0,0 +1,3 @@ +export function toSlug(str: string): string { + return str.replaceAll(/[^A-Za-z0-9-]/g, "-").toLowerCase() +} \ No newline at end of file