Skip to content

Commit

Permalink
feat: update word counter display (#192)
Browse files Browse the repository at this point in the history
  • Loading branch information
nshenderov committed Dec 12, 2024
1 parent 6625acc commit 05e18f5
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 53 deletions.
50 changes: 37 additions & 13 deletions admin/src/components/CKEReact.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useCallback, useRef, useState } from 'react';
import { useField } from '@strapi/strapi/admin';
import { Flex } from '@strapi/design-system';
import { styled, css } from 'styled-components';
import { ClassicEditor } from 'ckeditor5';
import { CKEditor } from '@ckeditor/ckeditor5-react';
import 'ckeditor5/ckeditor5.css';
Expand All @@ -22,13 +23,24 @@ export type WordCountPluginStats = {
export function CKEReact() {
const [mediaLibVisible, setMediaLibVisible] = useState<boolean>(false);
const [editorInstance, setEditorInstance] = useState<ClassicEditor | null>(null);
const [isWordsMax, setIsWordsMax] = useState(false);
const [isCharsMax, setIsCharsMax] = useState(false);

const { name, disabled, error, preset, wordsLimit, charsLimit, validateInputLength } =
useEditorContext();
const { name, disabled, preset, wordsLimit, charsLimit } = useEditorContext();
const { onChange: fieldOnChange, value: fieldValue } = useField(name);

const wordCounterRef = useRef<HTMLElement>(null);

const onEditorReady = (editor: ClassicEditor): void => {
setUpPlugins(editor);
setEditorInstance(editor);
};

const onEditorChange = (_e: any, editor: ClassicEditor): void => {
const data = editor.getData();
fieldOnChange(name, data);
};

const toggleMediaLib = useCallback(() => setMediaLibVisible(prev => !prev), [setMediaLibVisible]);

const handleChangeAssets = useCallback(
Expand All @@ -46,16 +58,6 @@ export function CKEReact() {
[toggleMediaLib, editorInstance]
);

const onEditorReady = (editor: ClassicEditor): void => {
setUpPlugins(editor);
setEditorInstance(editor);
};

const onEditorChange = (_e: any, editor: ClassicEditor): void => {
const data = editor.getData();
fieldOnChange(name, data);
};

if (!preset) {
return null;
}
Expand All @@ -70,7 +72,7 @@ export function CKEReact() {
onReady={onEditorReady}
onChange={onEditorChange}
/>
<Flex ref={wordCounterRef} color={error ? 'danger600' : 'neutral400'} />
<WordCounter ref={wordCounterRef} $isWordsMax={isWordsMax} $isCharsMax={isCharsMax} />
<MediaLib
isOpen={mediaLibVisible}
toggle={toggleMediaLib}
Expand Down Expand Up @@ -103,6 +105,8 @@ export function CKEReact() {

if (wordsLimit || charsLimit) {
wordCountPlugin.on('update', (_e, stats: WordCountPluginStats) => validateInputLength(stats));
const { words, characters } = wordCountPlugin;
validateInputLength({ words, characters });
}

wordCounterRef.current?.appendChild(wordCountPlugin.wordCountContainer);
Expand Down Expand Up @@ -137,4 +141,24 @@ export function CKEReact() {

StrapiUploadAdapterPlugin.initAdapter(config);
}

function validateInputLength(stats: WordCountPluginStats): void {
if (wordsLimit) {
setIsWordsMax(stats.words > wordsLimit);
}
if (charsLimit) {
setIsCharsMax(stats.characters > charsLimit);
}
}
}

const WordCounter = styled(Flex)<{ $isWordsMax: boolean; $isCharsMax: boolean }>`
${({ theme, $isWordsMax, $isCharsMax }) => css`
.ck-word-count__words {
color: ${$isWordsMax ? theme.colors.danger600 : theme.colors.neutral400};
}
.ck-word-count__characters {
color: ${$isCharsMax ? theme.colors.danger600 : theme.colors.neutral400};
}
`}
`;
2 changes: 1 addition & 1 deletion admin/src/components/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function Editor() {
const { name, hint, required, labelAction, label, error, preset } = useEditorContext();

return (
<Field.Root id={name} name={name} error={error ?? false} hint={hint} required={required}>
<Field.Root id={name} name={name} error={error} hint={hint} required={required}>
<Flex direction="column" alignItems="stretch" gap={1}>
<Field.Label action={labelAction}>{label}</Field.Label>
{preset ? (
Expand Down
41 changes: 3 additions & 38 deletions admin/src/components/EditorProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import type { InputProps } from '@strapi/strapi/admin';

import { type Preset, setUpLanguage, getPluginConfig } from '../config';
import type { WordCountPluginStats } from './CKEReact';

type EditorProviderBaseProps = Pick<
InputProps,
Expand All @@ -13,16 +12,14 @@ type EditorProviderBaseProps = Pick<
wordsLimit?: number;
charsLimit?: number;
isFieldLocalized: boolean;
error?: string;
};

type EditorContextValue = EditorProviderBaseProps & {
preset: Preset | null;
error: string | null;
validateInputLength: (stats: WordCountPluginStats) => void;
};

type EditorProviderProps = EditorProviderBaseProps & {
fieldError: string | undefined;
children: React.ReactElement;
};

Expand All @@ -37,7 +34,7 @@ export function useEditorContext(): EditorContextValue {
export function EditorProvider({
name,
disabled,
fieldError,
error,
placeholder,
hint,
label,
Expand All @@ -50,7 +47,6 @@ export function EditorProvider({
isFieldLocalized,
}: EditorProviderProps) {
const [preset, setPreset] = useState<Preset | null>(null);
const [error, setError] = useState<string | null>(fieldError ?? null);

useEffect(() => {
(async () => {
Expand All @@ -66,35 +62,6 @@ export function EditorProvider({
})();
}, [presetName, placeholder, isFieldLocalized]);

useEffect(() => {
setError(fieldError ?? null);
}, [fieldError]);

const validateInputLength = useCallback(
(stats: WordCountPluginStats): void => {
const maxWordsErrMsg = 'Max words limit is exceeded';
const maxCharsErrMsg = 'Max characters limit is exceeded';

setError(prevErr => {
const isWordLimitExceeded = wordsLimit && stats.words > wordsLimit;
const isCharLimitExceeded = charsLimit && stats.characters > charsLimit;
const isErrSet = prevErr && (prevErr === maxWordsErrMsg || prevErr === maxCharsErrMsg);

if (isWordLimitExceeded) {
return maxWordsErrMsg;
}
if (isCharLimitExceeded) {
return maxCharsErrMsg;
}
if (isErrSet) {
return null;
}
return prevErr;
});
},
[wordsLimit, charsLimit]
);

const EditorContextValue = useMemo(
() => ({
name,
Expand All @@ -109,7 +76,6 @@ export function EditorProvider({
error,
wordsLimit,
charsLimit,
validateInputLength,
isFieldLocalized,
}),
[
Expand All @@ -125,7 +91,6 @@ export function EditorProvider({
charsLimit,
preset,
error,
validateInputLength,
isFieldLocalized,
]
);
Expand Down
2 changes: 1 addition & 1 deletion admin/src/components/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function Field({
return (
<EditorProvider
name={name}
fieldError={error}
error={error}
disabled={disabled}
required={required}
placeholder={placeholder}
Expand Down

0 comments on commit 05e18f5

Please sign in to comment.