prepare editable content component.
This commit is contained in:
parent
de1275e2d8
commit
32d6e0c875
17
src/components/EditableContent.module.css
Normal file
17
src/components/EditableContent.module.css
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
@layer components {
|
||||||
|
.editable_layout {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: calc(var(--spacing) * 2);
|
||||||
|
line-height: 1.3em;
|
||||||
|
input {
|
||||||
|
flex: 1 0;
|
||||||
|
}
|
||||||
|
.placeholder {
|
||||||
|
color: color-mix(in oklch, var(--color-on-surface) 38%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
src/components/EditableContent.tsx
Normal file
61
src/components/EditableContent.tsx
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import { Icon } from '@iconify/react/dist/iconify.js';
|
||||||
|
import cx from 'clsx';
|
||||||
|
import { FC, ReactHTMLElement, useCallback, useRef, useState } from 'react';
|
||||||
|
import styles from './EditableContent.module.css';
|
||||||
|
|
||||||
|
type EditableContentProps = {
|
||||||
|
as?: keyof ReactHTMLElement;
|
||||||
|
value?: string | null;
|
||||||
|
onChange?: (value: string) => void;
|
||||||
|
placeholder?: string;
|
||||||
|
additionalClassName?: HTMLElement['className'];
|
||||||
|
};
|
||||||
|
|
||||||
|
const EditableContent: FC<EditableContentProps> = ({
|
||||||
|
as: Tag,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
placeholder,
|
||||||
|
additionalClassName,
|
||||||
|
}) => {
|
||||||
|
const [isEditing, setEditing] = useState(false);
|
||||||
|
const formRef = useRef<HTMLFormElement | null>(null);
|
||||||
|
const cancelEdit = useCallback(() => {
|
||||||
|
setEditing(false);
|
||||||
|
formRef.current?.reset();
|
||||||
|
}, []);
|
||||||
|
const handleSubmit = useCallback(
|
||||||
|
(data: FormData) => {
|
||||||
|
const newValue = data.get('value') as string;
|
||||||
|
if (newValue.length > 0 && newValue !== value) {
|
||||||
|
onChange?.(newValue);
|
||||||
|
}
|
||||||
|
cancelEdit();
|
||||||
|
},
|
||||||
|
[value],
|
||||||
|
);
|
||||||
|
|
||||||
|
return isEditing ? (
|
||||||
|
<form
|
||||||
|
className={cx(styles.editable_layout, additionalClassName)}
|
||||||
|
action={handleSubmit}
|
||||||
|
ref={formRef}>
|
||||||
|
<input type="text" name="value" defaultValue={value} placeholder={placeholder} />
|
||||||
|
<button type="submit" className="icon">
|
||||||
|
<Icon icon="material-symbols-light:check" />
|
||||||
|
</button>
|
||||||
|
<button type="button" className="icon" onClick={cancelEdit}>
|
||||||
|
<Icon icon="material-symbols-light:close" />
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
) : (
|
||||||
|
<div className={cx(styles.editable_layout, additionalClassName)}>
|
||||||
|
<Tag>{value ?? <span className={styles.placeholder}>{placeholder}</span>}</Tag>
|
||||||
|
<button onClick={() => setEditing(true)} className="icon">
|
||||||
|
<Icon icon="material-symbols-light:edit-square-outline" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditableContent;
|
Loading…
Reference in New Issue
Block a user