Skip to content

Commit

Permalink
feat: legg til felt for å bedre slugs i collections
Browse files Browse the repository at this point in the history
  • Loading branch information
piofinn committed Jan 31, 2025
1 parent c37c5f3 commit e0035f6
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 5 deletions.
2 changes: 1 addition & 1 deletion ny-portal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"@payloadcms/db-postgres": "latest",
"@payloadcms/next": "latest",
"@payloadcms/richtext-lexical": "latest",
"@payloadcms/ui": "^3.20.0",
"@payloadcms/ui": "latest",
"@types/mdx": "^2.0.13",
"clsx": "^2.1.1",
"cross-env": "^7.0.3",
Expand Down
97 changes: 97 additions & 0 deletions ny-portal/src/fields/slug/SlugComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"use client";
import {
useField,
Button,
TextInput,
FieldLabel,
useFormFields,
useForm,
} from "@payloadcms/ui";
import { TextFieldClientProps } from "payload";
import React, { useCallback, useEffect } from "react";
import { formatSlug } from "./formatSlug";
import "./index.scss";

type SlugComponentProps = {
fieldToUse: string;
checkboxFieldPath: string;
} & TextFieldClientProps;

export const SlugComponent: React.FC<SlugComponentProps> = ({
field,
fieldToUse,
checkboxFieldPath: checkboxFieldPathFromProps,
path,
readOnly: readOnlyFromProps,
}) => {
const { label } = field;

const checkboxFieldPath = path?.includes(".")
? `${path}.${checkboxFieldPathFromProps}`
: checkboxFieldPathFromProps;

const { value, setValue } = useField<string>({ path: path || field.name });

const { dispatchFields } = useForm();

// The value of the checkbox
// We're using separate useFormFields to minimise re-renders
const checkboxValue = useFormFields(([fields]) => {
return fields[checkboxFieldPath]?.value as string;
});

// The value of the field we're listening to for the slug
const targetFieldValue = useFormFields(([fields]) => {
return fields[fieldToUse]?.value as string;
});

useEffect(() => {
if (checkboxValue) {
if (targetFieldValue) {
const formattedSlug = formatSlug(targetFieldValue);

if (value !== formattedSlug) setValue(formattedSlug);
} else {
if (value !== "") setValue("");
}
}
}, [targetFieldValue, checkboxValue, setValue, value]);

const handleLock = useCallback(
(e: React.MouseEvent<Element>) => {
e.preventDefault();

dispatchFields({
type: "UPDATE",
path: checkboxFieldPath,
value: !checkboxValue,
});
},
[checkboxValue, checkboxFieldPath, dispatchFields],
);

const readOnly = readOnlyFromProps || checkboxValue;

return (
<div className="field-type slug-field-component">
<div className="label-wrapper">
<FieldLabel htmlFor={`field-${path}`} label={label} />

<Button
className="lock-button"
buttonStyle="none"
onClick={handleLock}
>
{checkboxValue ? "Unlock" : "Lock"}
</Button>
</div>

<TextInput
value={value}
onChange={setValue}
path={path || field.name}
readOnly={Boolean(readOnly)}
/>
</div>
);
};
25 changes: 25 additions & 0 deletions ny-portal/src/fields/slug/formatSlug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { FieldHook } from "payload";

export const formatSlug = (val: string): string =>
val
.replace(/ /g, "-")
.replace(/[^\w-]+/g, "")
.toLowerCase();

export const formatSlugHook =
(fallback: string): FieldHook =>
({ data, operation, value }) => {
if (typeof value === "string") {
return formatSlug(value);
}

if (operation === "create" || !data?.slug) {
const fallbackData = data?.[fallback];

if (fallbackData && typeof fallbackData === "string") {
return formatSlug(fallbackData);
}
}

return value;
};
12 changes: 12 additions & 0 deletions ny-portal/src/fields/slug/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.slug-field-component {
.label-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
}

.lock-button {
margin: 0;
padding-bottom: 0.3125rem;
}
}
56 changes: 56 additions & 0 deletions ny-portal/src/fields/slug/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { CheckboxField, TextField } from "payload";
import { formatSlugHook } from "./formatSlug";

type Overrides = {
slugOverrides?: Partial<TextField>;
checkboxOverrides?: Partial<CheckboxField>;
};

type Slug = (
fieldToUse?: string,
overrides?: Overrides,
) => [TextField, CheckboxField];

export const slugField: Slug = (fieldToUse = "title", overrides = {}) => {
const { slugOverrides, checkboxOverrides } = overrides;

const checkBoxField: CheckboxField = {
name: "slugLock",
type: "checkbox",
defaultValue: true,
admin: {
hidden: true,
position: "sidebar",
},
...checkboxOverrides,
};

// @ts-expect-error - ts mismatch Partial<TextField> with TextField
const slugField: TextField = {
name: "slug",
type: "text",
index: true,
unique: true,
label: "Slug",
...(slugOverrides || {}),
hooks: {
// Kept this in for hook or API based updates
beforeValidate: [formatSlugHook(fieldToUse)],
},
admin: {
position: "sidebar",
...(slugOverrides?.admin || {}),
components: {
Field: {
path: "@/fields/slug/SlugComponent#SlugComponent",
clientProps: {
fieldToUse,
checkboxFieldPath: checkBoxField.name,
},
},
},
},
};

return [slugField, checkBoxField];
};
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit e0035f6

Please sign in to comment.