1
0
mirror of https://gitlab.nic.cz/turris/reforis/foris-js.git synced 2024-12-25 00:11:36 +01:00

Release 1.1.0

This commit is contained in:
Maciej Lenartowicz 2019-10-22 08:24:10 +00:00
parent dccc9e5769
commit f2aa28f172
19 changed files with 222 additions and 123 deletions

View File

@ -6,7 +6,7 @@
Each commit to `dev` branch will result in publishing a new version of library
tagged `beta`. Versions names are based on commit SHA, e.g.
`foris@0.1.0-d9073aa4.0`.
`foris@0.1.0-beta.d9073aa4`.
### Preparing a release

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "foris",
"version": "1.0.0",
"version": "1.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "foris",
"version": "1.0.0",
"version": "1.1.0",
"description": "Set of components and utils for Foris and its plugins.",
"author": "CZ.NIC, z.s.p.o.",
"repository": {

View File

@ -6,11 +6,12 @@ then
exit 1
else
# Need to replace "_" with "_" as GitLab CI won't accept secret vars with "-"
echo "//registry.npmjs.org/:_authToken=$(echo $NPM_TOKEN | tr _ -)" > .npmrc
echo "//registry.npmjs.org/:_authToken=$(echo "$NPM_TOKEN" | tr _ -)" > .npmrc
echo "unsafe-perm = true" >> ~/.npmrc
if test "$1" = "beta"
then
npm version prerelease --preid=$CI_COMMIT_SHORT_SHA --git-tag-version false
BETA_VERSION=$(npx -c 'echo "$npm_package_version"')-beta.$CI_COMMIT_SHORT_SHA
npm version "$BETA_VERSION" --git-tag-version false
npm publish --tag beta
elif test "$1" = "latest"
then

View File

@ -12,7 +12,7 @@ import {
API_ACTIONS, TIMEOUT, HEADERS, APIReducer, getErrorMessage,
} from "./utils";
export function useAPIPost(url) {
export function useAPIPost(url, contentType) {
const [state, dispatch] = useReducer(APIReducer, {
isSending: false,
isError: false,
@ -20,12 +20,17 @@ export function useAPIPost(url) {
data: null,
});
const headers = { ...HEADERS };
if (contentType) {
headers["Content-Type"] = contentType;
}
const post = async (data) => {
dispatch({ type: API_ACTIONS.INIT });
try {
const result = await axios.post(url, data, {
timeout: TIMEOUT,
headers: HEADERS,
headers,
});
dispatch({ type: API_ACTIONS.SUCCESS, payload: result.data });
} catch (error) {

View File

@ -7,7 +7,7 @@
import React from "react";
import PropTypes from "prop-types";
import { useUID } from "react-uid/dist/es5/index";
import { useUID } from "react-uid";
import { formFieldsSize } from "./constants";

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from "react";
import PropTypes from "prop-types";
DownloadButton.propTypes = {
href: PropTypes.string.isRequired,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]),
};
export function DownloadButton({ href, children }) {
return <a href={href} className="btn btn-primary" download>{children}</a>;
}

View File

@ -0,0 +1,7 @@
Hyperlink with apperance of a button.
It has `download` attribute, which prevents closing WebSocket connection on Firefox. See [related issue](https://bugzilla.mozilla.org/show_bug.cgi?id=858538) for more details.
```js
<DownloadButton href="example.zip">Download</DownloadButton>
```

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from "react";
import PropTypes from "prop-types";
import { Input } from "./Input";
FileInput.propTypes = {
/** Field label. */
label: PropTypes.string.isRequired,
/** Error message. */
error: PropTypes.string,
/** Help text message. */
helpText: PropTypes.string,
/** Email value. */
value: PropTypes.string,
};
export function FileInput({ ...props }) {
return (
<Input
type="file"
className="custom-file-input"
labelClassName="custom-file-label"
groupClassName="custom-file"
{...props}
/>
);
}

View File

@ -0,0 +1,15 @@
Bootstrap component for file input. Includes label and has predefined sizes and structure for using in foris forms.
All additional `props` are passed to the `<input type="file">` HTML component.
```js
import {useState} from 'react';
const [files, setFiles] = useState([]);
<FileInput
files={files}
label="Some file"
helpText="Will be uploaded"
onChange={event =>setFiles(event.target.files)}
/>
```

View File

@ -6,7 +6,7 @@
*/
import React from "react";
import { useUID } from "react-uid/dist/es5/index";
import { useUID } from "react-uid";
import PropTypes from "prop-types";
import { formFieldsSize } from "./constants";
@ -21,31 +21,31 @@ Input.propTypes = {
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]),
labelClassName: PropTypes.string,
groupClassName: PropTypes.string,
};
/** Base bootstrap input component. */
export function Input({
type, label, helpText, error, className, children, ...props
type, label, helpText, error, className, children, labelClassName, groupClassName, ...props
}) {
const uid = useUID();
const inputClassName = `form-control ${className || ""} ${(error ? "is-invalid" : "")}`.trim();
return (
<div className={formFieldsSize}>
<div className="form-group">
<label htmlFor={uid}>{label}</label>
<div className="input-group">
<input
className={inputClassName}
type={type}
id={uid}
<div className={`form-group ${formFieldsSize}`}>
<label className={labelClassName} htmlFor={uid}>{label}</label>
<div className={`input-group ${groupClassName || ""}`.trim()}>
<input
className={inputClassName}
type={type}
id={uid}
{...props}
/>
{children}
</div>
{error ? <div className="invalid-feedback">{error}</div> : null}
{helpText ? <small className="form-text text-muted">{helpText}</small> : null}
{...props}
/>
{children}
</div>
{error ? <div className="invalid-feedback">{error}</div> : null}
{helpText ? <small className="form-text text-muted">{helpText}</small> : null}
</div>
);
}

View File

@ -7,7 +7,7 @@
import React from "react";
import PropTypes from "prop-types";
import { useUID } from "react-uid/dist/es5/index";
import { useUID } from "react-uid";
import { formFieldsSize } from "./constants";

View File

@ -7,7 +7,7 @@
import React from "react";
import PropTypes from "prop-types";
import { useUID } from "react-uid/dist/es5/index";
import { useUID } from "react-uid";
Select.propTypes = {

View File

@ -0,0 +1,19 @@
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from "react";
import { render } from "customTestRender";
import { DownloadButton } from "../DownloadButton";
describe("<DownloadButton />", () => {
it("should have download attribute", () => {
const { container } = render(<DownloadButton href="http://example.com">Test Button</DownloadButton>);
expect(container.firstChild.getAttribute("download")).not.toBeNull();
});
});

View File

@ -2,53 +2,49 @@
exports[`<NumberInput/> Render number input 1`] = `
<div
class="col-sm-12 offset-lg-1 col-lg-10"
class="form-group col-sm-12 offset-lg-1 col-lg-10"
>
<div
class="form-group"
<label
for="1"
>
<label
for="1"
>
Test label
</label>
Test label
</label>
<div
class="input-group"
>
<input
class="form-control"
id="1"
type="number"
value="1"
/>
<div
class="input-group"
class="input-group-append"
>
<input
class="form-control"
id="1"
type="number"
value="1"
/>
<div
class="input-group-append"
<button
aria-label="Increase"
class="btn btn-outline-secondary"
type="button"
>
<button
aria-label="Increase"
class="btn btn-outline-secondary"
type="button"
>
<i
class="fas fa-plus"
/>
</button>
<button
aria-label="Decrease"
class="btn btn-outline-secondary"
type="button"
>
<i
class="fas fa-minus"
/>
</button>
</div>
<i
class="fas fa-plus"
/>
</button>
<button
aria-label="Decrease"
class="btn btn-outline-secondary"
type="button"
>
<i
class="fas fa-minus"
/>
</button>
</div>
<small
class="form-text text-muted"
>
Some help text
</small>
</div>
<small
class="form-text text-muted"
>
Some help text
</small>
</div>
`;

View File

@ -2,32 +2,28 @@
exports[`<PasswordInput/> Render password input 1`] = `
<div
class="col-sm-12 offset-lg-1 col-lg-10"
class="form-group col-sm-12 offset-lg-1 col-lg-10"
>
<div
class="form-group"
<label
for="1"
>
<label
for="1"
>
Test label
</label>
<div
class="input-group"
>
<input
autocomplete="new-password"
class="form-control"
id="1"
type="password"
value="Some password"
/>
</div>
<small
class="form-text text-muted"
>
Some help text
</small>
Test label
</label>
<div
class="input-group"
>
<input
autocomplete="new-password"
class="form-control"
id="1"
type="password"
value="Some password"
/>
</div>
<small
class="form-text text-muted"
>
Some help text
</small>
</div>
`;

View File

@ -2,31 +2,27 @@
exports[`<TextInput/> Render text input 1`] = `
<div
class="col-sm-12 offset-lg-1 col-lg-10"
class="form-group col-sm-12 offset-lg-1 col-lg-10"
>
<div
class="form-group"
<label
for="1"
>
<label
for="1"
>
Test label
</label>
<div
class="input-group"
>
<input
class="form-control"
id="1"
type="text"
value="Some text"
/>
</div>
<small
class="form-text text-muted"
>
Some help text
</small>
Test label
</label>
<div
class="input-group"
>
<input
class="form-control"
id="1"
type="text"
value="Some text"
/>
</div>
<small
class="form-text text-muted"
>
Some help text
</small>
</div>
`;

View File

@ -17,7 +17,7 @@ const FORM_ACTIONS = {
resetData: 2,
};
export function useForm(validator, prepData) {
export function useForm(validator, dataPreprocessor) {
const [state, dispatch] = useReducer(formReducer, {
data: null,
initialData: null,
@ -28,10 +28,10 @@ export function useForm(validator, prepData) {
dispatch({
type: FORM_ACTIONS.resetData,
data,
prepData,
dataPreprocessor,
validator,
});
}, [prepData, validator]);
}, [dataPreprocessor, validator]);
const onFormChangeHandler = useCallback((updateRule) => (event) => {
dispatch({
@ -41,6 +41,7 @@ export function useForm(validator, prepData) {
validator,
});
}, [validator]);
return [
state,
onFormChangeHandler,
@ -61,12 +62,15 @@ function formReducer(state, action) {
};
}
case FORM_ACTIONS.resetData: {
if (!action.data) return { ...state, initialData: state.data };
const prepData = action.prepData ? action.prepData(action.data) : action.data;
if (!action.data) {
return { ...state, initialData: state.data };
}
const data = action.dataPreprocessor ? action.dataPreprocessor(action.data) : action.data;
return {
data: prepData,
initialData: prepData,
errors: action.data ? action.validator(prepData) : undefined,
data,
initialData: data,
errors: action.data ? action.validator(data) : undefined,
};
}
default: {
@ -82,6 +86,9 @@ function getChangedValue(target) {
} else if (target.type === "number") {
const parsedValue = parseInt(value);
value = Number.isNaN(parsedValue) ? value : parsedValue;
} else if (target.type === "file") {
// Return first file (we don't need multiple yet)
[value] = target.files;
}
return value;
}

View File

@ -15,15 +15,17 @@ export { useAPIPatch } from "api/patch";
export { Alert } from "bootstrap/Alert";
export { Button } from "bootstrap/Button";
export { CheckBox } from "bootstrap/CheckBox";
export { formFieldsSize } from "bootstrap/constants";
export { DownloadButton } from "bootstrap/DownloadButton";
export { DataTimeInput } from "bootstrap/DataTimeInput";
export { EmailInput } from "bootstrap/EmailInput";
export { FileInput } from "bootstrap/FileInput";
export { Input } from "bootstrap/Input";
export { NumberInput } from "bootstrap/NumberInput";
export { PasswordInput } from "bootstrap/PasswordInput";
export { RadioSet } from "bootstrap/RadioSet";
export { Select } from "bootstrap/Select";
export { TextInput } from "bootstrap/TextInput";
export { formFieldsSize } from "bootstrap/constants";
export {
Spinner,