fix field input

This commit is contained in:
rizrmd 2024-05-16 20:38:31 -07:00
parent cf6b8b0728
commit 285051b571
6 changed files with 761 additions and 4 deletions

View File

@ -85,7 +85,7 @@ export const FieldInput: FC<{
<div
className={cx(
!["toogle", "button", "radio","checkbox"].includes(arg.sub_type)
? "field-outer c-flex c-flex-1 c-flex-row c-rounded c-border c-text-sm"
? "field-outer c-flex c-flex-1 c-flex-row c-rounded c-border c-text-sm c-flex-wrap"
: "",
fm.status === "loading"
? css`

View File

@ -0,0 +1,172 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {mergeRegister} from '@lexical/utils';
import {
$getSelection,
$isRangeSelection,
CAN_REDO_COMMAND,
CAN_UNDO_COMMAND,
FORMAT_ELEMENT_COMMAND,
FORMAT_TEXT_COMMAND,
REDO_COMMAND,
SELECTION_CHANGE_COMMAND,
UNDO_COMMAND,
} from 'lexical';
import {useCallback, useEffect, useRef, useState} from 'react';
const LowPriority = 1;
function Divider() {
return <div className="divider" />;
}
export default function ToolbarPlugin() {
const [editor] = useLexicalComposerContext();
const toolbarRef = useRef(null);
const [canUndo, setCanUndo] = useState(false);
const [canRedo, setCanRedo] = useState(false);
const [isBold, setIsBold] = useState(false);
const [isItalic, setIsItalic] = useState(false);
const [isUnderline, setIsUnderline] = useState(false);
const [isStrikethrough, setIsStrikethrough] = useState(false);
const $updateToolbar = useCallback(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
// Update text format
setIsBold(selection.hasFormat('bold'));
setIsItalic(selection.hasFormat('italic'));
setIsUnderline(selection.hasFormat('underline'));
setIsStrikethrough(selection.hasFormat('strikethrough'));
}
}, []);
useEffect(() => {
return mergeRegister(
editor.registerUpdateListener(({editorState}) => {
editorState.read(() => {
$updateToolbar();
});
}),
editor.registerCommand(
SELECTION_CHANGE_COMMAND,
(_payload, _newEditor) => {
$updateToolbar();
return false;
},
LowPriority,
),
editor.registerCommand(
CAN_UNDO_COMMAND,
(payload) => {
setCanUndo(payload);
return false;
},
LowPriority,
),
editor.registerCommand(
CAN_REDO_COMMAND,
(payload) => {
setCanRedo(payload);
return false;
},
LowPriority,
),
);
}, [editor, $updateToolbar]);
return (
<div className="toolbar" ref={toolbarRef}>
<button
disabled={!canUndo}
onClick={() => {
editor.dispatchCommand(UNDO_COMMAND, undefined);
}}
className="toolbar-item spaced"
aria-label="Undo">
<i className="format undo" />
</button>
<button
disabled={!canRedo}
onClick={() => {
editor.dispatchCommand(REDO_COMMAND, undefined);
}}
className="toolbar-item"
aria-label="Redo">
<i className="format redo" />
</button>
<Divider />
<button
onClick={() => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold');
}}
className={'toolbar-item spaced ' + (isBold ? 'active' : '')}
aria-label="Format Bold">
<i className="format bold" />
</button>
<button
onClick={() => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic');
}}
className={'toolbar-item spaced ' + (isItalic ? 'active' : '')}
aria-label="Format Italics">
<i className="format italic" />
</button>
<button
onClick={() => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline');
}}
className={'toolbar-item spaced ' + (isUnderline ? 'active' : '')}
aria-label="Format Underline">
<i className="format underline" />
</button>
<button
onClick={() => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough');
}}
className={'toolbar-item spaced ' + (isStrikethrough ? 'active' : '')}
aria-label="Format Strikethrough">
<i className="format strikethrough" />
</button>
<Divider />
<button
onClick={() => {
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'left');
}}
className="toolbar-item spaced"
aria-label="Left Align">
<i className="format left-align" />
</button>
<button
onClick={() => {
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'center');
}}
className="toolbar-item spaced"
aria-label="Center Align">
<i className="format center-align" />
</button>
<button
onClick={() => {
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'right');
}}
className="toolbar-item spaced"
aria-label="Right Align">
<i className="format right-align" />
</button>
<button
onClick={() => {
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'justify');
}}
className="toolbar-item"
aria-label="Justify Align">
<i className="format justify-align" />
</button>{' '}
</div>
);
}

View File

@ -0,0 +1,25 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {TreeView} from '@lexical/react/LexicalTreeView';
export default function TreeViewPlugin(): JSX.Element {
const [editor] = useLexicalComposerContext();
return (
<TreeView
viewClassName="tree-view-output"
treeTypeButtonClassName="debug-treetype-button"
timeTravelPanelClassName="debug-timetravel-panel"
timeTravelButtonClassName="debug-timetravel-button"
timeTravelPanelSliderClassName="debug-timetravel-panel-slider"
timeTravelPanelButtonClassName="debug-timetravel-panel-button"
editor={editor}
/>
);
}

View File

@ -0,0 +1,451 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
body {
margin: 0;
background: #eee;
font-family: system-ui, -apple-system, BlinkMacSystemFont, '.SFNSText-Regular',
sans-serif;
font-weight: 500;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.other h2 {
font-size: 18px;
color: #444;
margin-bottom: 7px;
}
.other a {
color: #777;
text-decoration: underline;
font-size: 14px;
}
.other ul {
padding: 0;
margin: 0;
list-style-type: none;
}
.App {
font-family: sans-serif;
text-align: center;
}
h1 {
font-size: 24px;
color: #333;
}
.ltr {
text-align: left;
}
.rtl {
text-align: right;
}
.editor-container {
margin: 20px auto 20px auto;
border-radius: 2px;
max-width: 600px;
color: #000;
position: relative;
line-height: 20px;
font-weight: 400;
text-align: left;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
.editor-inner {
background: #fff;
position: relative;
}
.editor-input {
min-height: 150px;
resize: none;
font-size: 15px;
caret-color: rgb(5, 5, 5);
position: relative;
tab-size: 1;
outline: 0;
padding: 15px 10px;
caret-color: #444;
}
.editor-placeholder {
color: #999;
overflow: hidden;
position: absolute;
text-overflow: ellipsis;
top: 15px;
left: 10px;
font-size: 15px;
user-select: none;
display: inline-block;
pointer-events: none;
}
.editor-text-bold {
font-weight: bold;
}
.editor-text-italic {
font-style: italic;
}
.editor-text-underline {
text-decoration: underline;
}
.editor-text-strikethrough {
text-decoration: line-through;
}
.editor-text-underlineStrikethrough {
text-decoration: underline line-through;
}
.editor-text-code {
background-color: rgb(240, 242, 245);
padding: 1px 0.25rem;
font-family: Menlo, Consolas, Monaco, monospace;
font-size: 94%;
}
.editor-link {
color: rgb(33, 111, 219);
text-decoration: none;
}
.tree-view-output {
display: block;
background: #222;
color: #fff;
padding: 5px;
font-size: 12px;
white-space: pre-wrap;
margin: 1px auto 10px auto;
max-height: 250px;
position: relative;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
overflow: auto;
line-height: 14px;
}
.editor-code {
background-color: rgb(240, 242, 245);
font-family: Menlo, Consolas, Monaco, monospace;
display: block;
padding: 8px 8px 8px 52px;
line-height: 1.53;
font-size: 13px;
margin: 0;
margin-top: 8px;
margin-bottom: 8px;
tab-size: 2;
/* white-space: pre; */
overflow-x: auto;
position: relative;
}
.editor-code:before {
content: attr(data-gutter);
position: absolute;
background-color: #eee;
left: 0;
top: 0;
border-right: 1px solid #ccc;
padding: 8px;
color: #777;
white-space: pre-wrap;
text-align: right;
min-width: 25px;
}
.editor-code:after {
content: attr(data-highlight-language);
top: 0;
right: 3px;
padding: 3px;
font-size: 10px;
text-transform: uppercase;
position: absolute;
color: rgba(0, 0, 0, 0.5);
}
.editor-tokenComment {
color: slategray;
}
.editor-tokenPunctuation {
color: #999;
}
.editor-tokenProperty {
color: #905;
}
.editor-tokenSelector {
color: #690;
}
.editor-tokenOperator {
color: #9a6e3a;
}
.editor-tokenAttr {
color: #07a;
}
.editor-tokenVariable {
color: #e90;
}
.editor-tokenFunction {
color: #dd4a68;
}
.editor-paragraph {
margin: 0;
margin-bottom: 8px;
position: relative;
}
.editor-paragraph:last-child {
margin-bottom: 0;
}
.editor-heading-h1 {
font-size: 24px;
color: rgb(5, 5, 5);
font-weight: 400;
margin: 0;
margin-bottom: 12px;
padding: 0;
}
.editor-heading-h2 {
font-size: 15px;
color: rgb(101, 103, 107);
font-weight: 700;
margin: 0;
margin-top: 10px;
padding: 0;
text-transform: uppercase;
}
.editor-quote {
margin: 0;
margin-left: 20px;
font-size: 15px;
color: rgb(101, 103, 107);
border-left-color: rgb(206, 208, 212);
border-left-width: 4px;
border-left-style: solid;
padding-left: 16px;
}
.editor-list-ol {
padding: 0;
margin: 0;
margin-left: 16px;
}
.editor-list-ul {
padding: 0;
margin: 0;
margin-left: 16px;
}
.editor-listitem {
margin: 8px 32px 8px 32px;
}
.editor-nested-listitem {
list-style-type: none;
}
pre::-webkit-scrollbar {
background: transparent;
width: 10px;
}
pre::-webkit-scrollbar-thumb {
background: #999;
}
.debug-timetravel-panel {
overflow: hidden;
padding: 0 0 10px 0;
margin: auto;
display: flex;
}
.debug-timetravel-panel-slider {
padding: 0;
flex: 8;
}
.debug-timetravel-panel-button {
padding: 0;
border: 0;
background: none;
flex: 1;
color: #fff;
font-size: 12px;
}
.debug-timetravel-panel-button:hover {
text-decoration: underline;
}
.debug-timetravel-button {
border: 0;
padding: 0;
font-size: 12px;
top: 10px;
right: 15px;
position: absolute;
background: none;
color: #fff;
}
.debug-timetravel-button:hover {
text-decoration: underline;
}
.toolbar {
display: flex;
margin-bottom: 1px;
background: #fff;
padding: 4px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
vertical-align: middle;
}
.toolbar button.toolbar-item {
border: 0;
display: flex;
background: none;
border-radius: 10px;
padding: 8px;
cursor: pointer;
vertical-align: middle;
}
.toolbar button.toolbar-item:disabled {
cursor: not-allowed;
}
.toolbar button.toolbar-item.spaced {
margin-right: 2px;
}
.toolbar button.toolbar-item i.format {
background-size: contain;
display: inline-block;
height: 18px;
width: 18px;
margin-top: 2px;
vertical-align: -0.25em;
display: flex;
opacity: 0.6;
}
.toolbar button.toolbar-item:disabled i.format {
opacity: 0.2;
}
.toolbar button.toolbar-item.active {
background-color: rgba(223, 232, 250, 0.3);
}
.toolbar button.toolbar-item.active i {
opacity: 1;
}
.toolbar .toolbar-item:hover:not([disabled]) {
background-color: #eee;
}
.toolbar .divider {
width: 1px;
background-color: #eee;
margin: 0 4px;
}
.toolbar .toolbar-item .text {
display: flex;
line-height: 20px;
width: 200px;
vertical-align: middle;
font-size: 14px;
color: #777;
text-overflow: ellipsis;
width: 70px;
overflow: hidden;
height: 20px;
text-align: left;
}
.toolbar .toolbar-item .icon {
display: flex;
width: 20px;
height: 20px;
user-select: none;
margin-right: 8px;
line-height: 16px;
background-size: contain;
}
i.undo {
background-image: url(icons/arrow-counterclockwise.svg);
}
i.redo {
background-image: url(icons/arrow-clockwise.svg);
}
i.bold {
background-image: url(icons/type-bold.svg);
}
i.italic {
background-image: url(icons/type-italic.svg);
}
i.underline {
background-image: url(icons/type-underline.svg);
}
i.strikethrough {
background-image: url(icons/type-strikethrough.svg);
}
i.left-align {
background-image: url(icons/text-left.svg);
}
i.center-align {
background-image: url(icons/text-center.svg);
}
i.right-align {
background-image: url(icons/text-right.svg);
}
i.justify-align {
background-image: url(icons/justify.svg);
}

View File

@ -7,6 +7,7 @@ import { FieldToggle } from "./TypeToggle";
import { FieldButton } from "./TypeButton";
import { FieldRadio } from "./TypeRadio";
import { FieldCheckbox } from "./TypeCheckbox";
import { FieldTag } from "./TypeTag";
export const MultiOption: FC<{
field: FieldLocal;
@ -17,10 +18,10 @@ export const MultiOption: FC<{
<>
{arg.sub_type === "checkbox" ? (
<FieldCheckbox field={field} fm={fm} arg={arg}/>
) : arg.sub_type === "tag" ? (
<>{arg.sub_type}</>
): arg.sub_type === "button" ? (
<FieldButton arg={arg} field={field} fm={fm} />
): arg.sub_type === "tag" ? (
<FieldTag arg={arg} field={field} fm={fm} />
) : (
<></>
)}

108
comps/form/field/type/TypeTag.tsx Executable file
View File

@ -0,0 +1,108 @@
import { FC, useEffect } from "react";
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
import { useLocal } from "@/utils/use-local";
import get from "lodash.get";
export const FieldTag: FC<{
field: FieldLocal;
fm: FMLocal;
arg: FieldProp;
}> = ({ field, fm, arg }) => {
const local = useLocal({
ref: null as any,
focus: false as boolean,
value: null as any,
});
let value: any = fm.data[field.name];
let tags: Array<string> = typeof value === "string" ? value.split(",") : [];
if(isEditor){
tags = ["sample","sample"]
}
return (
<div className="c-flex-grow c-flex-row c-flex c-w-full c-h-full">
<div
className={cx(
"c-px-2 c-flex c-flex-row c-items-center c-flex-wrap c-flex-grow c-gap-1 c-m-1"
)}
onClick={() => {
if (local.ref) {
local.ref.focus();
}
}}
>
{tags.map((item) => {
return (
<div className="c-cursor-text c-flex-row c-flex c-items-center c-text-xs c-font-medium c-rounded c-border c-border-black">
<span className="c-flex-grow c-px-2.5 c-py-0.5">{item}</span>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
className="c-px-1 c-border-l c-border-black c-cursor-pointer "
viewBox="0 0 40 40"
onClick={() => {
// delete tag, pakai filter
let tag: Array<string> = tags.filter((e) => e !== item) || [];
// jadiin value string
let value = tags.join(",");
fm.data[field.name] = value;
fm.render();
}}
>
<path
fill="currentColor"
d="M21.499 19.994L32.755 8.727a1.064 1.064 0 0 0-.001-1.502c-.398-.396-1.099-.398-1.501.002L20 18.494L8.743 7.224c-.4-.395-1.101-.393-1.499.002a1.05 1.05 0 0 0-.309.751c0 .284.11.55.309.747L18.5 19.993L7.245 31.263a1.064 1.064 0 0 0 .003 1.503c.193.191.466.301.748.301h.006c.283-.001.556-.112.745-.305L20 21.495l11.257 11.27c.199.198.465.308.747.308a1.058 1.058 0 0 0 1.061-1.061c0-.283-.11-.55-.31-.747z"
/>
</svg>
</div>
);
})}
<input
ref={(el) => (local.ref = el)}
type={"text"}
value={local.value}
onClick={() => {}}
onChange={(ev) => {
local.value = ev.currentTarget.value;
local.render();
}}
onKeyDown={(event) => {
if (event.key === "Enter") {
event.preventDefault();
// detect string kosong
if (local.value !== "" && local.value) {
// jadiin array atau split
let tag: Array<string> = local.value.split(",") || [];
// filter tag dari value gk boleh sama
tag = tag.filter((e) => !tags.includes(e));
// concat
tags = tags.concat(tag);
// jadiin value string
let value = tags.join(",");
local.value = "";
local.render();
fm.data[field.name] = value;
fm.render();
}
event.preventDefault();
event.stopPropagation();
}
}}
disabled={field.disabled}
className={cx(
"c-flex-grow c-flex-1 c-items-center c-bg-transparent c-outline-none c-px-2 c-text-sm",
"c-max-w-full"
)}
spellCheck={false}
onFocus={() => {
console.log("focus?");
}}
onBlur={() => {
console.log("blur?");
}}
/>
</div>
</div>
);
};