save work

This commit is contained in:
Red Adaya 2024-10-10 19:52:06 +08:00
parent 57bfa6b8bd
commit 4dd4acfa8c
4 changed files with 332 additions and 1 deletions

View File

@ -251,7 +251,6 @@ const EmojiPalette = memo(({ scopeRef, className }: EmojiPaletteProps) => {
{isPaletteVisible && (
<Palette anchorRef={anchorRef} scopeRef={scopeRef} className="emoji-palette-content">
<Input placeholder="Search emojis..." value={searchTerm} onChange={handleSearchChange} />
<div className="emoji-grid">
{filteredEmojis.length > 0 ? (
filteredEmojis.map((item, index) => (

View File

@ -0,0 +1,96 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
.input {
display: flex;
align-items: flex-start;
border-radius: 6px;
position: relative;
min-height: 24px;
min-width: 50px;
width: 100%;
gap: 6px;
border: 2px solid var(--form-element-border-color);
background: var(--form-element-bg-color);
padding: 8px;
&:hover {
cursor: text;
}
&.focused {
border-color: var(--form-element-primary-color);
}
&.disabled {
opacity: 0.75;
}
&.error {
border-color: var(--form-element-error-color);
}
&-inner {
display: flex;
flex-direction: column;
position: relative;
flex-grow: 1;
--inner-padding: 5px 0 5px 16px;
&-label {
padding: var(--inner-padding);
margin-bottom: -10px;
font-size: 12.5px;
transition: all 0.3s;
color: var(--form-element-label-color);
line-height: 10px;
user-select: none;
&.float {
font-size: 10px;
top: 5px;
}
&.offset-left {
left: 0;
}
}
&-input {
width: 100%;
height: 100%;
border: none;
padding: var(--inner-padding);
font-size: 12px;
outline: none;
background-color: transparent;
color: var(--form-element-text-color);
line-height: 20px;
resize: none; // Disable manual resizing by the user
&.offset-left {
padding: 5px 16px 5px 0;
}
&:placeholder-shown {
user-select: none;
}
}
}
&.no-label {
height: auto;
textarea {
height: auto;
}
}
}
// Multi-Line Input Customization for textarea
textarea.input-inner-input {
overflow: hidden; // Hide native scrollbars, and let height expand automatically
min-height: 32px; // Minimum height of textarea
max-height: 150px; // Set a maximum height before scrolling is triggered
line-height: 1.5em; // Control line height for better readability
}

View File

@ -0,0 +1,52 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import type { Meta, StoryObj } from "@storybook/react";
import { InputMultiLine } from "./intputmultiline";
const meta: Meta<typeof InputMultiLine> = {
title: "Elements/InputMultiLine",
component: InputMultiLine,
args: {
label: "Message",
placeholder: "Type your message here...",
className: "custom-input-class",
},
argTypes: {
label: {
description: "Label for the input field",
},
placeholder: {
description: "Placeholder text for the input",
},
className: {
description: "Custom class for input styling",
},
decoration: {
description: "Input decorations for start or end positions",
},
},
};
export default meta;
type Story = StoryObj<typeof InputMultiLine>;
export const DefaultInput: Story = {
render: (args) => {
return <InputMultiLine {...args} />;
},
args: {
label: "Message",
placeholder: "Type your message...",
},
};
export const InputWithErrorState: Story = {
render: (args) => {
return <InputMultiLine {...args} required={true} />;
},
args: {
label: "Required Message",
placeholder: "This field is required...",
},
};

View File

@ -0,0 +1,184 @@
import { clsx } from "clsx";
import React, { forwardRef, useEffect, useRef, useState } from "react";
import "./inputmultiline.less";
interface InputDecorationProps {
startDecoration?: React.ReactNode;
endDecoration?: React.ReactNode;
}
interface InputMultiLineProps {
label?: string;
value?: string;
className?: string;
onChange?: (value: string) => void;
onKeyDown?: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;
onFocus?: () => void;
onBlur?: () => void;
placeholder?: string;
defaultValue?: string;
decoration?: InputDecorationProps;
required?: boolean;
maxLength?: number;
autoFocus?: boolean;
disabled?: boolean;
inputRef?: React.MutableRefObject<HTMLTextAreaElement>;
}
const InputMultiLine = forwardRef<HTMLDivElement, InputMultiLineProps>(
(
{
label,
value,
className,
onChange,
onKeyDown,
onFocus,
onBlur,
placeholder,
defaultValue = "",
decoration,
required,
maxLength,
autoFocus,
disabled,
inputRef,
}: InputMultiLineProps,
ref
) => {
const [focused, setFocused] = useState(false);
const [internalValue, setInternalValue] = useState(defaultValue);
const [error, setError] = useState(false);
const [hasContent, setHasContent] = useState(Boolean(value || defaultValue));
const internalInputRef = useRef<HTMLTextAreaElement>(null);
useEffect(() => {
if (value !== undefined) {
setFocused(Boolean(value));
}
}, [value]);
const handleComponentFocus = () => {
if (internalInputRef.current && !internalInputRef.current.contains(document.activeElement)) {
internalInputRef.current.focus();
}
};
const handleComponentBlur = () => {
if (internalInputRef.current?.contains(document.activeElement)) {
internalInputRef.current.blur();
}
};
const handleSetInputRef = (elem: HTMLTextAreaElement) => {
if (inputRef) {
inputRef.current = elem;
}
internalInputRef.current = elem;
};
const handleFocus = () => {
setFocused(true);
onFocus && onFocus();
};
const handleBlur = () => {
if (internalInputRef.current) {
const inputValue = internalInputRef.current.value;
if (required && !inputValue) {
setError(true);
setFocused(false);
} else {
setError(false);
setFocused(false);
}
}
onBlur && onBlur();
};
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const inputValue = e.target.value;
if (required && !inputValue) {
setError(true);
setHasContent(false);
} else {
setError(false);
setHasContent(Boolean(inputValue));
}
if (value === undefined) {
setInternalValue(inputValue);
}
onChange && onChange(inputValue);
handleAutoResize(e.target);
};
const handleAutoResize = (textarea: HTMLTextAreaElement) => {
textarea.style.height = "auto"; // Reset height to calculate new height
textarea.style.height = `${textarea.scrollHeight}px`; // Set new height based on content
};
useEffect(() => {
if (internalInputRef.current) {
handleAutoResize(internalInputRef.current);
}
}, [internalValue]);
const inputValue = value ?? internalValue;
return (
<div
ref={ref}
className={clsx("input", className, {
focused: focused,
error: error,
disabled: disabled,
"no-label": !label,
})}
onFocus={handleComponentFocus}
onBlur={handleComponentBlur}
tabIndex={-1}
>
{decoration?.startDecoration && <>{decoration.startDecoration}</>}
<div className="input-inner">
{label && (
<label
className={clsx("input-inner-label", {
float: hasContent || focused || placeholder,
"offset-left": decoration?.startDecoration,
})}
htmlFor={label}
>
{label}
</label>
)}
<textarea
className={clsx("input-inner-input", {
"offset-left": decoration?.startDecoration,
})}
ref={handleSetInputRef}
id={label}
value={inputValue}
onChange={handleInputChange}
onFocus={handleFocus}
onBlur={handleBlur}
onKeyDown={onKeyDown}
placeholder={placeholder}
maxLength={maxLength}
autoFocus={autoFocus}
disabled={disabled}
rows={1}
onInput={(e) => handleAutoResize(e.currentTarget)}
/>
</div>
{decoration?.endDecoration && <>{decoration.endDecoration}</>}
</div>
);
}
);
export { InputMultiLine };
export type { InputDecorationProps, InputMultiLineProps };