Skip to content
Snippets Groups Projects
Unverified Commit 932e8bf7 authored by Alexander Polyankin's avatar Alexander Polyankin Committed by GitHub
Browse files

Move EditableText to core components (#23508)

parent 0a986a58
No related branches found
No related tags found
No related merge requests found
Showing with 170 additions and 91 deletions
import React from "react";
import { ComponentStory } from "@storybook/react";
import { useArgs } from "@storybook/client-api";
import EditableText from "./EditableText";
export default {
title: "Query Builder/EditableText",
title: "Core/EditableText",
component: EditableText,
};
const Template: ComponentStory<typeof EditableText> = args => {
const [{ initialValue }, updateArgs] = useArgs();
const handleChange = (value: string | null | undefined) =>
updateArgs({ initialValue: value });
console.log(initialValue);
return <EditableText initialValue={initialValue} onChange={handleChange} />;
return <EditableText {...args} />;
};
export const Default = Template.bind({});
Default.args = {
initialValue:
"Users with their LTV, Source, and State. Number of new saved questions the last 12 weeks by the method used to create it: GUI or SQL",
initialValue: "Question",
placeholder: "Enter title",
};
export const Multiline = Template.bind({});
Multiline.args = {
initialValue: "Question",
placeholder: "Enter title",
isMultiline: true,
};
export const WithMaxWidth = Template.bind({});
WithMaxWidth.args = {
initialValue: "Question",
placeholder: "Enter title",
style: { maxWidth: 500 },
};
import styled from "@emotion/styled";
import { color } from "metabase/lib/colors";
import { css } from "@emotion/react";
export const SharedStyles = css`
border: 1px solid transparent;
border-radius: 0.25rem;
padding: 0.5rem;
grid-area: 1 / 1 / 2 / 2;
font-size: 0.875rem;
line-height: 1.25rem;
min-height: 0;
export const EditableTextRoot = styled.div`
position: relative;
color: ${color("text-dark")};
`;
interface EditableTextRootProps {
value: string | null;
}
padding: 0.25rem;
border: 1px solid transparent;
export const EditableTextRoot = styled.div<EditableTextRootProps>`
display: grid;
max-width: 500px;
&:hover,
&:focus-within {
border-color: ${color("border")};
}
&:after {
content: "${props => props.value?.replace(/\n/g, "\\00000a")} ";
white-space: pre-wrap;
content: attr(data-value);
visibility: hidden;
${SharedStyles}
white-space: pre-wrap;
word-wrap: break-word;
}
`;
export const EditableTextArea = styled.textarea`
resize: none;
overflow: hidden;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
min-height: 0;
padding: inherit;
color: inherit;
font-size: inherit;
font-weight: inherit;
line-height: inherit;
cursor: pointer;
border: none;
resize: none;
outline: none;
&:hover,
&:focus {
border: 1px solid ${color("border")};
}
overflow: hidden;
background: transparent;
&:focus {
cursor: text;
}
${SharedStyles}
`;
import React, {
ChangeEvent,
KeyboardEvent,
forwardRef,
HTMLAttributes,
Ref,
useCallback,
useState,
} from "react";
import { EditableTextArea, EditableTextRoot } from "./EditableText.styled";
export type EditableTextAttributes = Omit<
HTMLAttributes<HTMLDivElement>,
"onChange"
>;
export interface EditableTextProps extends EditableTextAttributes {
initialValue?: string | null;
placeholder?: string;
isMultiline?: boolean;
onChange?: (value: string) => void;
"data-testid"?: string;
}
const EditableText = forwardRef(function EditableText(
{
initialValue,
placeholder,
isMultiline = false,
onChange,
"data-testid": dataTestId,
...props
}: EditableTextProps,
ref: Ref<HTMLDivElement>,
) {
const [inputValue, setInputValue] = useState(initialValue ?? "");
const [submitValue, setSubmitValue] = useState(initialValue ?? "");
const handleBlur = useCallback(() => {
if (inputValue !== submitValue) {
setSubmitValue(inputValue);
onChange?.(inputValue);
}
}, [inputValue, submitValue, onChange]);
const handleChange = useCallback(
(event: ChangeEvent<HTMLTextAreaElement>) => {
setInputValue(event.currentTarget.value);
},
[],
);
const handleKeyDown = useCallback(
(event: KeyboardEvent<HTMLTextAreaElement>) => {
if (event.key === "Escape") {
setInputValue(submitValue);
} else if (event.key === "Enter" && !isMultiline) {
event.preventDefault();
event.currentTarget.blur();
}
},
[submitValue, isMultiline],
);
return (
<EditableTextRoot {...props} ref={ref} data-value={`${inputValue}\u00A0`}>
<EditableTextArea
value={inputValue}
placeholder={placeholder}
data-testid={dataTestId}
onBlur={handleBlur}
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
</EditableTextRoot>
);
});
export default EditableText;
import React, { useState, useRef } from "react";
import { KEY_ESCAPE, KEY_ENTER } from "metabase/lib/keyboard";
import {
EditableTextRoot,
EditableTextArea,
SharedStyles,
} from "./EditableText.styled";
interface EditableTextProps {
initialValue: string | null;
onChange?: (val: string | null) => void;
submitOnEnter?: boolean;
"data-testid"?: string;
placeholder?: string;
}
const EditableText = ({
initialValue,
onChange,
submitOnEnter,
"data-testid": dataTestid,
placeholder,
}: EditableTextProps) => {
const [value, setValue] = useState<string | null>(initialValue);
const textArea = useRef<HTMLTextAreaElement>(null);
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setValue(e.target.value);
};
const handleBlur = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const {
target: { value },
} = e;
if (onChange && value !== initialValue) {
onChange(value);
}
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === KEY_ESCAPE) {
setValue(initialValue);
}
if (e.key === KEY_ENTER && submitOnEnter) {
textArea.current?.blur();
e.preventDefault();
}
};
return (
<EditableTextRoot value={value}>
<EditableTextArea
placeholder={placeholder}
value={value || ""}
onChange={handleChange}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
rows={1}
cols={1}
ref={textArea}
data-testid={dataTestid}
/>
</EditableTextRoot>
);
};
export default Object.assign(EditableText, {
SharedStyles,
Root: EditableTextRoot,
TextArea: EditableTextArea,
});
import React from "react";
import { t } from "ttag";
import PropTypes from "prop-types";
import { PLUGIN_MODERATION } from "metabase/plugins";
import { color } from "metabase/lib/colors";
import EditableText from "../EditableText";
import {
HeaderRoot,
HeaderReviewIcon,
HeaderTitle,
} from "./SavedQuestionHeaderButton.styled";
SavedQuestionHeaderButton.propTypes = {
......@@ -28,11 +25,10 @@ function SavedQuestionHeaderButton({ className, question, onSave }) {
return (
<HeaderRoot>
<EditableText
<HeaderTitle
initialValue={question.displayName()}
onChange={onSave}
submitOnEnter
placeholder={t`A nice title`}
onChange={onSave}
data-testid="saved-question-header-title"
/>
{reviewIconName && (
......
import styled from "@emotion/styled";
import EditableText from "../EditableText/EditableText";
import Icon from "metabase/components/Icon";
import EditableText from "metabase/core/components/EditableText";
export const HeaderRoot = styled.div`
${EditableText.Root}:after, ${EditableText.TextArea} {
font-size: 1.25rem;
font-weight: 700;
line-height: 1.5rem;
padding: 0.25rem;
}
display: flex;
align-items: center;
`;
export const HeaderTitle = styled(EditableText)`
font-size: 1.25rem;
font-weight: 700;
line-height: 1.5rem;
`;
export const HeaderReviewIcon = styled(Icon)`
padding-left: 0.25rem;
`;
import React from "react";
import { t } from "ttag";
import {
PLUGIN_MODERATION,
......@@ -13,7 +14,7 @@ import QuestionActivityTimeline from "metabase/query_builder/components/Question
import Question from "metabase-lib/lib/Question";
import { Card } from "metabase-types/types/Card";
import EditableText from "../../EditableText";
import EditableText from "metabase/core/components/EditableText";
import { Root, ContentSection } from "./QuestionInfoSidebar.styled";
interface QuestionInfoSidebarProps {
......@@ -49,8 +50,9 @@ export const QuestionInfoSidebar = ({
<ContentSection>
<EditableText
initialValue={description}
placeholder={t`Description`}
isMultiline
onChange={handleSave}
placeholder="Description"
/>
<PLUGIN_MODERATION.QuestionModerationSection question={question} />
</ContentSection>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment