feat: Add label for name field and use input when editable in export dialog (#3286)
* feat: Add label for name field and use input when editable in export dialog * fix * review fix * dnt allow to edit file name when view mode * Update src/components/ProjectName.tsx Co-authored-by: David Luzar <luzar.david@gmail.com> Co-authored-by: David Luzar <luzar.david@gmail.com>
This commit is contained in:
parent
80a61db72f
commit
efb6d0825b
@ -23,7 +23,9 @@ export const actionChangeProjectName = register({
|
|||||||
label={t("labels.fileTitle")}
|
label={t("labels.fileTitle")}
|
||||||
value={appState.name || "Unnamed"}
|
value={appState.name || "Unnamed"}
|
||||||
onChange={(name: string) => updateData(name)}
|
onChange={(name: string) => updateData(name)}
|
||||||
isNameEditable={typeof appProps.name === "undefined"}
|
isNameEditable={
|
||||||
|
typeof appProps.name === "undefined" && !appState.viewModeEnabled
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
@ -525,8 +525,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
let zenModeEnabled = actionResult?.appState?.zenModeEnabled || false;
|
let zenModeEnabled = actionResult?.appState?.zenModeEnabled || false;
|
||||||
let gridSize = actionResult?.appState?.gridSize || null;
|
let gridSize = actionResult?.appState?.gridSize || null;
|
||||||
let theme = actionResult?.appState?.theme || "light";
|
let theme = actionResult?.appState?.theme || "light";
|
||||||
let name = actionResult?.appState?.name || this.state.name;
|
let name = actionResult?.appState?.name ?? this.state.name;
|
||||||
|
|
||||||
if (typeof this.props.viewModeEnabled !== "undefined") {
|
if (typeof this.props.viewModeEnabled !== "undefined") {
|
||||||
viewModeEnabled = this.props.viewModeEnabled;
|
viewModeEnabled = this.props.viewModeEnabled;
|
||||||
}
|
}
|
||||||
|
@ -31,12 +31,16 @@
|
|||||||
.ExportDialog__name {
|
.ExportDialog__name {
|
||||||
grid-column: project-name;
|
grid-column: project-name;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.TextInput {
|
.TextInput {
|
||||||
height: calc(1rem - 3px);
|
height: calc(1rem - 3px);
|
||||||
width: 200px;
|
width: 200px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
margin-left: 8px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
&--readonly {
|
&--readonly {
|
||||||
background: none;
|
background: none;
|
||||||
@ -44,6 +48,9 @@
|
|||||||
&:hover {
|
&:hover {
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
width: auto;
|
||||||
|
max-width: 200px;
|
||||||
|
padding-left: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import "./TextInput.scss";
|
import "./TextInput.scss";
|
||||||
|
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import { selectNode, removeSelection } from "../utils";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value: string;
|
value: string;
|
||||||
@ -10,17 +9,18 @@ type Props = {
|
|||||||
isNameEditable: boolean;
|
isNameEditable: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ProjectName extends Component<Props> {
|
type State = {
|
||||||
private handleFocus = (event: React.FocusEvent<HTMLElement>) => {
|
fileName: string;
|
||||||
selectNode(event.currentTarget);
|
};
|
||||||
|
export class ProjectName extends Component<Props, State> {
|
||||||
|
state = {
|
||||||
|
fileName: this.props.value,
|
||||||
};
|
};
|
||||||
|
private handleBlur = (event: any) => {
|
||||||
private handleBlur = (event: React.FocusEvent<HTMLElement>) => {
|
const value = event.target.value;
|
||||||
const value = event.currentTarget.innerText.trim();
|
|
||||||
if (value !== this.props.value) {
|
if (value !== this.props.value) {
|
||||||
this.props.onChange(value);
|
this.props.onChange(value);
|
||||||
}
|
}
|
||||||
removeSelection();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
|
private handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
|
||||||
@ -32,39 +32,30 @@ export class ProjectName extends Component<Props> {
|
|||||||
event.currentTarget.blur();
|
event.currentTarget.blur();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private makeEditable = (editable: HTMLSpanElement | null) => {
|
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
editable.contentEditable = "plaintext-only";
|
|
||||||
} catch {
|
|
||||||
editable.contentEditable = "true";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return this.props.isNameEditable ? (
|
return (
|
||||||
<span
|
<>
|
||||||
suppressContentEditableWarning
|
<label htmlFor="file-name">
|
||||||
ref={this.makeEditable}
|
{`${this.props.label}${this.props.isNameEditable ? "" : ":"}`}
|
||||||
data-type="wysiwyg"
|
</label>
|
||||||
className="TextInput"
|
{this.props.isNameEditable ? (
|
||||||
role="textbox"
|
<input
|
||||||
aria-label={this.props.label}
|
className="TextInput"
|
||||||
onBlur={this.handleBlur}
|
onBlur={this.handleBlur}
|
||||||
onKeyDown={this.handleKeyDown}
|
onKeyDown={this.handleKeyDown}
|
||||||
onFocus={this.handleFocus}
|
id="file-name"
|
||||||
>
|
value={this.state.fileName}
|
||||||
{this.props.value}
|
onChange={(event) =>
|
||||||
</span>
|
this.setState({ fileName: event.target.value })
|
||||||
) : (
|
}
|
||||||
<span
|
/>
|
||||||
className="TextInput TextInput--readonly"
|
) : (
|
||||||
aria-label={this.props.label}
|
<span className="TextInput TextInput--readonly" id="file-name">
|
||||||
>
|
{this.props.value}
|
||||||
{this.props.value}
|
</span>
|
||||||
</span>
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
"architect": "Architect",
|
"architect": "Architect",
|
||||||
"artist": "Artist",
|
"artist": "Artist",
|
||||||
"cartoonist": "Cartoonist",
|
"cartoonist": "Cartoonist",
|
||||||
"fileTitle": "File title",
|
"fileTitle": "File name",
|
||||||
"colorPicker": "Color picker",
|
"colorPicker": "Color picker",
|
||||||
"canvasBackground": "Canvas background",
|
"canvasBackground": "Canvas background",
|
||||||
"drawingCanvas": "Drawing canvas",
|
"drawingCanvas": "Drawing canvas",
|
||||||
|
@ -111,11 +111,11 @@ describe("<Excalidraw/>", () => {
|
|||||||
const { container } = await render(<Excalidraw />);
|
const { container } = await render(<Excalidraw />);
|
||||||
|
|
||||||
fireEvent.click(queryByTestId(container, "export-button")!);
|
fireEvent.click(queryByTestId(container, "export-button")!);
|
||||||
const textInput = document.querySelector(
|
const textInput: HTMLInputElement | null = document.querySelector(
|
||||||
".ExportDialog__name .TextInput",
|
".ExportDialog__name .TextInput",
|
||||||
);
|
);
|
||||||
expect(textInput?.textContent).toContain(`${t("labels.untitled")}`);
|
expect(textInput?.value).toContain(`${t("labels.untitled")}`);
|
||||||
expect(textInput?.hasAttribute("data-type")).toBe(true);
|
expect(textInput?.nodeName).toBe("INPUT");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set the name and not allow editing when the name prop is present"', async () => {
|
it('should set the name and not allow editing when the name prop is present"', async () => {
|
||||||
@ -127,8 +127,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
".ExportDialog__name .TextInput--readonly",
|
".ExportDialog__name .TextInput--readonly",
|
||||||
);
|
);
|
||||||
expect(textInput?.textContent).toEqual(name);
|
expect(textInput?.textContent).toEqual(name);
|
||||||
|
expect(textInput?.nodeName).toBe("SPAN");
|
||||||
expect(textInput?.hasAttribute("data-type")).toBe(false);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user