scm-ui: new repository layout

This commit is contained in:
Sebastian Sdorra
2019-10-07 10:57:09 +02:00
parent 09c7def874
commit c05798e254
417 changed files with 3620 additions and 52971 deletions

View File

@@ -0,0 +1,86 @@
//@flow
import React from "react";
import { AddButton } from "../buttons";
import InputField from "./InputField";
type Props = {
addEntry: string => void,
disabled: boolean,
buttonLabel: string,
fieldLabel: string,
errorMessage: string,
helpText?: string,
validateEntry?: string => boolean
};
type State = {
entryToAdd: string
};
class AddEntryToTableField extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
entryToAdd: ""
};
}
isValid = () => {
const {validateEntry} = this.props;
if (!this.state.entryToAdd || this.state.entryToAdd === "" || !validateEntry) {
return true;
} else {
return validateEntry(this.state.entryToAdd);
}
};
render() {
const {
disabled,
buttonLabel,
fieldLabel,
errorMessage,
helpText
} = this.props;
return (
<div className="field">
<InputField
label={fieldLabel}
errorMessage={errorMessage}
onChange={this.handleAddEntryChange}
validationError={!this.isValid()}
value={this.state.entryToAdd}
onReturnPressed={this.appendEntry}
disabled={disabled}
helpText={helpText}
/>
<AddButton
label={buttonLabel}
action={this.addButtonClicked}
disabled={disabled || this.state.entryToAdd ==="" || !this.isValid()}
/>
</div>
);
}
addButtonClicked = (event: Event) => {
event.preventDefault();
this.appendEntry();
};
appendEntry = () => {
const { entryToAdd } = this.state;
this.props.addEntry(entryToAdd);
this.setState({ ...this.state, entryToAdd: "" });
};
handleAddEntryChange = (entryname: string) => {
this.setState({
...this.state,
entryToAdd: entryname
});
};
}
export default AddEntryToTableField;

View File

@@ -0,0 +1,89 @@
//@flow
import React from "react";
import type { AutocompleteObject, SelectValue } from "@scm-manager/ui-types";
import Autocomplete from "../Autocomplete";
import AddButton from "../buttons/AddButton";
type Props = {
addEntry: SelectValue => void,
disabled: boolean,
buttonLabel: string,
fieldLabel: string,
helpText?: string,
loadSuggestions: string => Promise<AutocompleteObject>,
placeholder?: string,
loadingMessage?: string,
noOptionsMessage?: string
};
type State = {
selectedValue?: SelectValue
};
class AutocompleteAddEntryToTableField extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { selectedValue: undefined };
}
render() {
const {
disabled,
buttonLabel,
fieldLabel,
helpText,
loadSuggestions,
placeholder,
loadingMessage,
noOptionsMessage
} = this.props;
const { selectedValue } = this.state;
return (
<div className="field">
<Autocomplete
label={fieldLabel}
loadSuggestions={loadSuggestions}
valueSelected={this.handleAddEntryChange}
helpText={helpText}
value={selectedValue}
placeholder={placeholder}
loadingMessage={loadingMessage}
noOptionsMessage={noOptionsMessage}
creatable={true}
/>
<AddButton
label={buttonLabel}
action={this.addButtonClicked}
disabled={disabled}
/>
</div>
);
}
addButtonClicked = (event: Event) => {
event.preventDefault();
this.appendEntry();
};
appendEntry = () => {
const { selectedValue } = this.state;
if (!selectedValue) {
return;
}
// $FlowFixMe null is needed to clear the selection; undefined does not work
this.setState({ ...this.state, selectedValue: null }, () =>
this.props.addEntry(selectedValue)
);
};
handleAddEntryChange = (selection: SelectValue) => {
this.setState({
...this.state,
selectedValue: selection
});
};
}
export default AutocompleteAddEntryToTableField;

View File

@@ -0,0 +1,50 @@
//@flow
import React from "react";
import { Help } from "../index";
type Props = {
label?: string,
name?: string,
checked: boolean,
onChange?: (value: boolean, name?: string) => void,
disabled?: boolean,
helpText?: string
};
class Checkbox extends React.Component<Props> {
onCheckboxChange = (event: SyntheticInputEvent<HTMLInputElement>) => {
if (this.props.onChange) {
this.props.onChange(event.target.checked, this.props.name);
}
};
renderHelp = () => {
const helpText = this.props.helpText;
if (helpText) {
return <Help message={helpText} />;
}
};
render() {
return (
<div className="field is-grouped">
<div className="control">
<label className="checkbox" disabled={this.props.disabled}>
<input
type="checkbox"
checked={this.props.checked}
onChange={this.onCheckboxChange}
disabled={this.props.disabled}
/>
{" "}
{this.props.label}
{this.renderHelp()}
</label>
</div>
</div>
);
}
}
export default Checkbox;

View File

@@ -0,0 +1,43 @@
// @flow
import React from "react";
import classNames from "classnames";
type Props = {
options: string[],
optionValues?: string[],
optionSelected: string => void,
preselectedOption?: string,
className: any,
disabled?: boolean
};
class DropDown extends React.Component<Props> {
render() {
const { options, optionValues, preselectedOption, className, disabled } = this.props;
return (
<div className={classNames(className, "select", disabled ? "disabled": "")}>
<select
value={preselectedOption ? preselectedOption : ""}
onChange={this.change}
disabled={disabled}
>
<option key="" />
{options.map((option, index) => {
return (
<option key={option} value={optionValues && optionValues[index] ? optionValues[index] : option}>
{option}
</option>
);
})}
</select>
</div>
);
}
change = (event: SyntheticInputEvent<HTMLSelectElement>) => {
this.props.optionSelected(event.target.value);
};
}
export default DropDown;

View File

@@ -0,0 +1,69 @@
//@flow
import React from "react";
import injectSheet from "react-jss";
import classNames from "classnames";
import { translate } from "react-i18next";
type Props = {
filter: string => void,
value?: string,
// context props
classes: Object,
t: string => string
};
type State = {
value: string
};
const styles = {
inputField: {
float: "right",
marginTop: "1.25rem"
},
inputHeight: {
height: "2.5rem"
}
};
class FilterInput extends React.Component<Props, State> {
constructor(props) {
super(props);
this.state = { value: this.props.value ? this.props.value : "" };
}
handleChange = event => {
this.setState({ value: event.target.value });
};
handleSubmit = event => {
this.props.filter(this.state.value);
event.preventDefault();
};
render() {
const { classes, t } = this.props;
return (
<form
className={classNames(classes.inputField, "input-field")}
onSubmit={this.handleSubmit}
>
<div className="control has-icons-left">
<input
className={classNames(classes.inputHeight, "input")}
type="search"
placeholder={t("filterEntries")}
value={this.state.value}
onChange={this.handleChange}
/>
<span className="icon is-small is-left">
<i className="fas fa-search" />
</span>
</div>
</form>
);
}
}
export default injectSheet(styles)(translate("commons")(FilterInput));

View File

@@ -0,0 +1,90 @@
//@flow
import React from "react";
import classNames from "classnames";
import LabelWithHelpIcon from "./LabelWithHelpIcon";
type Props = {
label?: string,
name?: string,
placeholder?: string,
value?: string,
type?: string,
autofocus?: boolean,
onChange: (value: string, name?: string) => void,
onReturnPressed?: () => void,
validationError: boolean,
errorMessage: string,
disabled?: boolean,
helpText?: string
};
class InputField extends React.Component<Props> {
static defaultProps = {
type: "text",
placeholder: ""
};
field: ?HTMLInputElement;
componentDidMount() {
if (this.props.autofocus && this.field) {
this.field.focus();
}
}
handleInput = (event: SyntheticInputEvent<HTMLInputElement>) => {
this.props.onChange(event.target.value, this.props.name);
};
handleKeyPress = (event: SyntheticKeyboardEvent<HTMLInputElement>) => {
const onReturnPressed = this.props.onReturnPressed;
if (!onReturnPressed) {
return;
}
if (event.key === "Enter") {
event.preventDefault();
onReturnPressed();
}
};
render() {
const {
type,
placeholder,
value,
validationError,
errorMessage,
disabled,
label,
helpText
} = this.props;
const errorView = validationError ? "is-danger" : "";
const helper = validationError ? (
<p className="help is-danger">{errorMessage}</p>
) : (
""
);
return (
<div className="field">
<LabelWithHelpIcon label={label} helpText={helpText} />
<div className="control">
<input
ref={input => {
this.field = input;
}}
className={classNames("input", errorView)}
type={type}
placeholder={placeholder}
value={value}
onChange={this.handleInput}
onKeyPress={this.handleKeyPress}
disabled={disabled}
/>
</div>
{helper}
</div>
);
}
}
export default InputField;

View File

@@ -0,0 +1,37 @@
//@flow
import React from "react";
import Help from "../Help.js";
type Props = {
label?: string,
helpText?: string
};
class LabelWithHelpIcon extends React.Component<Props> {
renderHelp() {
const { helpText } = this.props;
if (helpText) {
return (
<Help message={helpText} />
);
}
}
render() {
const {label } = this.props;
if (label) {
const help = this.renderHelp();
return (
<label className="label">
{label} { help }
</label>
);
}
return "";
}
}
export default LabelWithHelpIcon;

View File

@@ -0,0 +1,37 @@
//@flow
import React from "react";
import { translate } from "react-i18next";
import type { DisplayedUser } from "@scm-manager/ui-types";
import TagGroup from "./TagGroup";
type Props = {
members: string[],
memberListChanged: (string[]) => void,
t: string => string
};
class MemberNameTagGroup extends React.Component<Props> {
render() {
const { members, t } = this.props;
const membersExtended = members.map(id => {
return { id, displayName: id, mail: "" };
});
return (
<TagGroup
items={membersExtended}
label={t("group.members")}
helpText={t("groupForm.help.memberHelpText")}
onRemove={this.removeEntry}
/>
);
}
removeEntry = (membersExtended: DisplayedUser[]) => {
const members = membersExtended.map(function(item) {
return item["id"];
});
this.props.memberListChanged(members);
};
}
export default translate("groups")(MemberNameTagGroup);

View File

@@ -0,0 +1,112 @@
// @flow
import React from "react";
import { translate } from "react-i18next";
import InputField from "./InputField";
type State = {
password: string,
confirmedPassword: string,
passwordValid: boolean,
passwordConfirmationFailed: boolean
};
type Props = {
passwordChanged: (string, boolean) => void,
passwordValidator?: string => boolean,
// Context props
t: string => string
};
class PasswordConfirmation extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
password: "",
confirmedPassword: "",
passwordValid: true,
passwordConfirmationFailed: false
};
}
componentDidMount() {
this.setState({
password: "",
confirmedPassword: "",
passwordValid: true,
passwordConfirmationFailed: false
});
}
render() {
const { t } = this.props;
return (
<div className="columns is-multiline">
<div className="column is-half">
<InputField
label={t("password.newPassword")}
type="password"
onChange={this.handlePasswordChange}
value={this.state.password ? this.state.password : ""}
validationError={!this.state.passwordValid}
errorMessage={t("password.passwordInvalid")}
/>
</div>
<div className="column is-half">
<InputField
label={t("password.confirmPassword")}
type="password"
onChange={this.handlePasswordValidationChange}
value={this.state ? this.state.confirmedPassword : ""}
validationError={this.state.passwordConfirmationFailed}
errorMessage={t("password.passwordConfirmFailed")}
/>
</div>
</div>
);
}
validatePassword = password => {
const { passwordValidator } = this.props;
if (passwordValidator) {
return passwordValidator(password);
}
return password.length >= 6 && password.length < 32;
};
handlePasswordValidationChange = (confirmedPassword: string) => {
const passwordConfirmed = this.state.password === confirmedPassword;
this.setState(
{
confirmedPassword,
passwordConfirmationFailed: !passwordConfirmed
},
this.propagateChange
);
};
handlePasswordChange = (password: string) => {
const passwordConfirmationFailed =
password !== this.state.confirmedPassword;
this.setState(
{
passwordValid: this.validatePassword(password),
passwordConfirmationFailed,
password: password
},
this.propagateChange
);
};
isValid = () => {
return this.state.passwordValid && !this.state.passwordConfirmationFailed;
};
propagateChange = () => {
this.props.passwordChanged(this.state.password, this.isValid());
};
}
export default translate("commons")(PasswordConfirmation);

View File

@@ -0,0 +1,41 @@
//@flow
import React from "react";
import { Help } from "../index";
type Props = {
label?: string,
name?: string,
value?: string,
checked: boolean,
onChange?: (value: boolean, name?: string) => void,
disabled?: boolean,
helpText?: string
};
class Radio extends React.Component<Props> {
renderHelp = () => {
const helpText = this.props.helpText;
if (helpText) {
return <Help message={helpText} />;
}
};
render() {
return (
<label className="radio" disabled={this.props.disabled}>
<input
type="radio"
name={this.props.name}
value={this.props.value}
checked={this.props.checked}
onChange={this.props.onChange}
disabled={this.props.disabled}
/>{" "}
{this.props.label}
{this.renderHelp()}
</label>
);
}
}
export default Radio;

View File

@@ -0,0 +1,71 @@
//@flow
import React from "react";
import classNames from "classnames";
import LabelWithHelpIcon from "./LabelWithHelpIcon";
export type SelectItem = {
value: string,
label: string
};
type Props = {
name?: string,
label?: string,
options: SelectItem[],
value?: string,
onChange: (value: string, name?: string) => void,
loading?: boolean,
helpText?: string,
disabled?: boolean
};
class Select extends React.Component<Props> {
field: ?HTMLSelectElement;
componentDidMount() {
// trigger change after render, if value is null to set it to the first value
// of the given options.
if (!this.props.value && this.field && this.field.value) {
this.props.onChange(this.field.value);
}
}
handleInput = (event: SyntheticInputEvent<HTMLSelectElement>) => {
this.props.onChange(event.target.value, this.props.name);
};
render() {
const { options, value, label, helpText, loading, disabled } = this.props;
const loadingClass = loading ? "is-loading" : "";
return (
<div className="field">
<LabelWithHelpIcon label={label} helpText={helpText} />
<div className={classNames(
"control select",
loadingClass
)}>
<select
ref={input => {
this.field = input;
}}
value={value}
onChange={this.handleInput}
disabled={disabled}
>
{options.map(opt => {
return (
<option value={opt.value} key={"KEY_" + opt.value}>
{opt.label}
</option>
);
})}
</select>
</div>
</div>
);
}
}
export default Select;

View File

@@ -0,0 +1,66 @@
//@flow
import * as React from "react";
import injectSheet from "react-jss";
import type { DisplayedUser } from "@scm-manager/ui-types";
import { Help, Tag } from "../index";
type Props = {
items: DisplayedUser[],
label: string,
helpText?: string,
onRemove: (DisplayedUser[]) => void,
// context props
classes: Object
};
const styles = {
help: {
position: "relative"
}
};
class TagGroup extends React.Component<Props> {
render() {
const { items, label, helpText, classes } = this.props;
let help = null;
if (helpText) {
help = <Help className={classes.help} message={helpText} />;
}
return (
<div className="field is-grouped is-grouped-multiline">
{label && items ? (
<div className="control">
<strong>
{label}
{help}
{items.length > 0 ? ":" : ""}
</strong>
</div>
) : (
""
)}
{items.map((item, key) => {
return (
<div className="control" key={key}>
<div className="tags has-addons">
<Tag
color="info is-outlined"
label={item.displayName}
onRemove={() => this.removeEntry(item)}
/>
</div>
</div>
);
})}
</div>
);
}
removeEntry = item => {
const newItems = this.props.items.filter(name => name !== item);
this.props.onRemove(newItems);
};
}
export default injectSheet(styles)(TagGroup);

View File

@@ -0,0 +1,57 @@
//@flow
import React from "react";
import LabelWithHelpIcon from "./LabelWithHelpIcon";
export type SelectItem = {
value: string,
label: string
};
type Props = {
name?: string,
label?: string,
placeholder?: SelectItem[],
value?: string,
autofocus?: boolean,
onChange: (value: string, name?: string) => void,
helpText?: string,
disabled?: boolean
};
class Textarea extends React.Component<Props> {
field: ?HTMLTextAreaElement;
componentDidMount() {
if (this.props.autofocus && this.field) {
this.field.focus();
}
}
handleInput = (event: SyntheticInputEvent<HTMLTextAreaElement>) => {
this.props.onChange(event.target.value, this.props.name);
};
render() {
const { placeholder, value, label, helpText, disabled } = this.props;
return (
<div className="field">
<LabelWithHelpIcon label={label} helpText={helpText} />
<div className="control">
<textarea
className="textarea"
ref={input => {
this.field = input;
}}
placeholder={placeholder}
onChange={this.handleInput}
value={value}
disabled={!!disabled}
/>
</div>
</div>
);
}
}
export default Textarea;

View File

@@ -0,0 +1,16 @@
// @create-index
export { default as AddEntryToTableField } from "./AddEntryToTableField.js";
export { default as AutocompleteAddEntryToTableField } from "./AutocompleteAddEntryToTableField.js";
export { default as TagGroup } from "./TagGroup.js";
export { default as MemberNameTagGroup } from "./MemberNameTagGroup.js";
export { default as Checkbox } from "./Checkbox.js";
export { default as Radio } from "./Radio.js";
export { default as FilterInput } from "./FilterInput.js";
export { default as InputField } from "./InputField.js";
export { default as Select } from "./Select.js";
export { default as Textarea } from "./Textarea.js";
export { default as PasswordConfirmation } from "./PasswordConfirmation.js";
export { default as LabelWithHelpIcon } from "./LabelWithHelpIcon.js";
export { default as DropDown } from "./DropDown.js";