Skip to content

Commit

Permalink
Bootstrap widget kickoff (#537)
Browse files Browse the repository at this point in the history
* bootstrap widget - initial configs and structure

* Implementing button for bootstrap widget

* removing unnecessary code

* minor fix to bootstrap index.js

* removing use confirm from bootstrap widget

* removing use confirm from index.js

* moving bootstrap and fontawesome dependencies to peer dependencies and dev dependencies

* adding reactstrap button group, correcting size of button and removing rangeslider from bootstrap feature
  • Loading branch information
PedroAMCoelho authored and ukrbublik committed Jan 18, 2022
1 parent e586a01 commit 5a6fa3f
Show file tree
Hide file tree
Showing 25 changed files with 553 additions and 2 deletions.
6 changes: 6 additions & 0 deletions CONFIG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@ See https://github.com/ukrbublik/react-awesome-query-builder/tree/master/example
import {Widgets} from 'react-awesome-query-builder';
import AntdWidgets from 'react-awesome-query-builder/lib/components/widgets/antd';
import MaterialWidgets from 'react-awesome-query-builder/lib/components/widgets/material';
import BootstrapWidgets from 'react-awesome-query-builder/lib/components/widgets/bootstrap';
const {
TextWidget,
NumberWidget,
Expand All @@ -542,6 +543,11 @@ const {
MaterialNumberWidget,
...
} = MaterialWidgets;
const {
BootstrapTextWidget,
BootstrapNumberWidget,
...
} = BootstrapWidgets;
----

[source,javascript]
Expand Down
2 changes: 2 additions & 0 deletions examples/demo/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ruRU } from "@material-ui/core/locale";
import AntdConfig from "react-awesome-query-builder/config/antd";
import AntdWidgets from "react-awesome-query-builder/components/widgets/antd";
import MaterialConfig from "react-awesome-query-builder/config/material";
import BootstrapConfig from "react-awesome-query-builder/config/bootstrap";
const {
FieldSelect,
FieldDropdown,
Expand All @@ -25,6 +26,7 @@ const skinToConfig: Record<string, Config> = {
vanilla: BasicConfig,
antd: AntdConfig,
material: MaterialConfig,
bootstrap: BootstrapConfig
};

export default (skin: string) => {
Expand Down
1 change: 1 addition & 0 deletions examples/demo/demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export default class DemoQueryBuilder extends Component<{}, DemoQueryBuilderStat
<option key="vanilla">vanilla</option>
<option key="antd">antd</option>
<option key="material">material</option>
<option key="bootstrap">bootstrap</option>
</select>
<button onClick={this.resetValue}>reset</button>
<button onClick={this.clearValue}>clear</button>
Expand Down
33 changes: 33 additions & 0 deletions modules/components/widgets/bootstrap/core/BootstrapButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from "react";
import { Button } from "reactstrap";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPlus, faTrashAlt } from "@fortawesome/free-solid-svg-icons";

export default ({ type, label, onClick, config }) => {
const typeToOnlyIcon = {
delGroup: faTrashAlt,
delRuleGroup: faTrashAlt,
delRule: faTrashAlt,
addRuleGroup: faPlus,
addRuleGroupExt: faPlus,
};
const typeToIcon = {
addRule: faPlus,
addGroup: faPlus,
};
const typeToColor = {
addRule: "primary",
addGroup: "primary",
delGroup: "danger",
delRuleGroup: "danger",
delRule: "danger",
};

let isOnlyIcon = typeToOnlyIcon[type];

return (
<Button size="sm" color={typeToColor[type]} onClick={onClick}>
<FontAwesomeIcon icon={isOnlyIcon ? typeToOnlyIcon[type] : typeToIcon[type]} /> {!isOnlyIcon && label}
</Button>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from "react";
import { ButtonGroup } from "reactstrap";

export default ({children, config}) => {
return <ButtonGroup>{children}</ButtonGroup>;
};
10 changes: 10 additions & 0 deletions modules/components/widgets/bootstrap/core/BootstrapConfirm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default ({ onOk, okText, cancelText, title, confirmFn }) => {
confirmFn({
description: title || "Are you sure?",
title: null,
confirmationText: okText || "Ok",
cancellationText: cancelText || "Cancel",
})
.then(onOk)
.catch(() => {});
};
39 changes: 39 additions & 0 deletions modules/components/widgets/bootstrap/core/BootstrapConjs.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from "react";

export default ({id, not, setNot, conjunctionOptions, setConjunction, disabled, readonly, config, showNot, notLabel}) => {
const conjsCount = Object.keys(conjunctionOptions).length;
const lessThenTwo = disabled;
const {forceShowConj} = config.settings;
const showConj = forceShowConj || conjsCount > 1 && !lessThenTwo;

const renderOptions = () =>
Object.keys(conjunctionOptions).map(key => {
const {id, name, label, checked} = conjunctionOptions[key];
let postfix = setConjunction.isDummyFn ? "__dummy" : "";
if ((readonly || disabled) && !checked)
return null;
return [
<input key={id+postfix} type="radio" id={id+postfix} name={name+postfix} checked={checked} disabled={readonly || disabled} value={key} onChange={onChange} />
,
<label key={id+postfix+"label"} htmlFor={id+postfix}>{label}</label>
];
});

const renderNot = () => {
return [
<input key={id} type="checkbox" id={id + "__not"} checked={not} disabled={readonly} onChange={onNotChange} />
,
<label key={id+"label"} htmlFor={id + "__not"}>{notLabel || "NOT"}</label>
];
};

const onChange = e => setConjunction(e.target.value);

const onNotChange = e => setNot(e.target.checked);

return [
showNot && renderNot(),
showConj && renderOptions()
];

};
29 changes: 29 additions & 0 deletions modules/components/widgets/bootstrap/core/BootstrapFieldSelect.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from "react";

export default ({items, setField, selectedKey, readonly}) => {
const renderOptions = (fields) => (
Object.keys(fields).map(fieldKey => {
const field = fields[fieldKey];
const {items, path, label, disabled} = field;
if (items) {
return <optgroup disabled={disabled} key={path} label={label}>{renderOptions(items)}</optgroup>;
} else {
return <option disabled={disabled} key={path} value={path}>{label}</option>;
}
})
);

const onChange = e => setField(e.target.value);

const hasValue = selectedKey != null;
return (
<select
onChange={onChange}
value={hasValue ? selectedKey : ""}
disabled={readonly}
>
{!hasValue && <option disabled value={""}></option>}
{renderOptions(items)}
</select>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from "react";

export default ({config, valueSources, valueSrc, title, setValueSrc, readonly}) => {
const renderOptions = (valueSources) => (
valueSources.map(([srcKey, info]) => (
<option key={srcKey} value={srcKey}>{info.label}</option>
))
);

const onChange = e => setValueSrc(e.target.value);

return (
<select
onChange={onChange}
value={valueSrc}
disabled={readonly}
>
{renderOptions(valueSources)}
</select>
);
};
3 changes: 3 additions & 0 deletions modules/components/widgets/bootstrap/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {BootstrapWidgets} from "../../..";
declare const BootstrapWidgets: BootstrapWidgets;
export default BootstrapWidgets;
50 changes: 50 additions & 0 deletions modules/components/widgets/bootstrap/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//bootstrap css
import "bootstrap/dist/css/bootstrap.min.css";

// value widgets
import BootstrapTextWidget from "./value/BootstrapText";
import BootstrapTextAreaWidget from "./value/BootstrapTextArea";
import BootstrapDateWidget from "./value/BootstrapDate";
import BootstrapDateTimeWidget from "./value/BootstrapDateTime";
import BootstrapTimeWidget from "./value/BootstrapTime";
import BootstrapSelectWidget from "./value/BootstrapSelect";
import BootstrapNumberWidget from "./value/BootstrapNumber";
import BootstrapSliderWidget from "./value/BootstrapSlider";
import BootstrapBooleanWidget from "./value/BootstrapBoolean";
import BootstrapMultiSelectWidget from "./value/BootstrapMultiSelect";

// field select widgets
import BootstrapFieldSelect from "./core/BootstrapFieldSelect";

// core components
import BootstrapButton from "./core/BootstrapButton";
import BootstrapButtonGroup from "./core/BootstrapButtonGroup";
import BootstrapConjs from "./core/BootstrapConjs";
import BootstrapValueSources from "./core/BootstrapValueSources";
import BootstrapConfirm from "./core/BootstrapConfirm";

// provider
const BootstrapProvider = ({config, children}) => children;

export default {
BootstrapTextWidget,
BootstrapTextAreaWidget,
BootstrapDateWidget,
BootstrapDateTimeWidget,
BootstrapTimeWidget,
BootstrapSelectWidget,
BootstrapNumberWidget,
BootstrapSliderWidget,
BootstrapBooleanWidget,
BootstrapMultiSelectWidget,

BootstrapFieldSelect,

BootstrapButton,
BootstrapButtonGroup,
BootstrapConjs,
BootstrapValueSources,
BootstrapConfirm,

BootstrapProvider,
};
22 changes: 22 additions & 0 deletions modules/components/widgets/bootstrap/value/BootstrapBoolean.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from "react";
import uuid from "../../../../utils/uuid";

export default (props) => {
const {value, setValue, config, labelYes, labelNo, readonly} = props;
const onCheckboxChange = e => setValue(e.target.checked);
const onRadioChange = e => setValue(e.target.value == "true");
const id = uuid(), id2 = uuid();

// return <>
// <input key={id} type="checkbox" id={id} checked={!!value} disabled={readonly} onChange={onCheckboxChange} />
// <label style={{display: "inline"}} key={id+"label"} htmlFor={id}>{value ? labelYes : labelNo}</label>
// </>;

return <>
<input key={id} type="radio" id={id} value={true} checked={!!value} disabled={readonly} onChange={onRadioChange} />
<label style={{display: "inline"}} key={id+"label"} htmlFor={id}>{labelYes}</label>
<input key={id2} type="radio" id={id2} value={false} checked={!value} disabled={readonly} onChange={onRadioChange} />
<label style={{display: "inline"}} key={id2+"label"} htmlFor={id2}>{labelNo}</label>
</>;

};
16 changes: 16 additions & 0 deletions modules/components/widgets/bootstrap/value/BootstrapDate.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from "react";

export default (props) => {
const {value, setValue, config, valueFormat, readonly} = props;

const onChange = e => {
let value = e.target.value;
if (value == "")
value = undefined;
setValue(value);
};

return (
<input type="date" value={value || ""} disabled={readonly} onChange={onChange} />
);
};
25 changes: 25 additions & 0 deletions modules/components/widgets/bootstrap/value/BootstrapDateTime.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from "react";
import moment from "moment";

export default (props) => {
const {value, setValue, config, valueFormat, use12Hours, readonly} = props;

const onChange = e => {
let value = e.target.value;
if (value == "")
value = undefined;
else
value = moment(new Date(value)).format(valueFormat);
setValue(value);
};

let dtValue = value;
if (!value)
dtValue = "";
else
dtValue = moment(value).format("YYYY-MM-DDTHH:mm");

return (
<input type="datetime-local" value={dtValue} disabled={readonly} onChange={onChange} />
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";
import {mapListValues} from "../../../../utils/stuff";

export default ({listValues, value, setValue, allowCustomValues, readonly}) => {
const renderOptions = () =>
mapListValues(listValues, ({title, value}) => {
return <option key={value} value={value}>{title}</option>;
});

const getMultiSelectValues = (multiselect) => {
let values = [];
const options = multiselect.options;
for (let i = 0 ; i < options.length ; i++) {
const opt = options[i];
if (opt.selected) {
values.push(opt.value);
}
}
if (!values.length)
values = undefined; //not allow []
return values;
};

const onChange = e => setValue(getMultiSelectValues(e.target));

return (
<select multiple
onChange={onChange}
value={value}
disabled={readonly}
>
{renderOptions()}
</select>
);
};
17 changes: 17 additions & 0 deletions modules/components/widgets/bootstrap/value/BootstrapNumber.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from "react";

export default (props) => {
const {value, setValue, config, readonly, min, max, step, placeholder} = props;
const onChange = e => {
let val = e.target.value;
if (val === "" || val === null)
val = undefined;
else
val = Number(val);
setValue(val);
};
const numberValue = value == undefined ? "" : value;
return (
<input type="number" value={numberValue} placeholder={placeholder} disabled={readonly} min={min} max={max} step={step} onChange={onChange} />
);
};
23 changes: 23 additions & 0 deletions modules/components/widgets/bootstrap/value/BootstrapSelect.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from "react";
import {mapListValues} from "../../../../utils/stuff";

export default ({listValues, value, setValue, allowCustomValues, readonly}) => {
const renderOptions = () =>
mapListValues(listValues, ({title, value}) => {
return <option key={value} value={value}>{title}</option>;
});

const onChange = e => setValue(e.target.value);

const hasValue = value != null;
return (
<select
onChange={onChange}
value={hasValue ? value : ""}
disabled={readonly}
>
{!hasValue && <option disabled value={""}></option>}
{renderOptions()}
</select>
);
};

0 comments on commit 5a6fa3f

Please sign in to comment.