disco_app 0.8.9 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/images/disco_app/icons.svg +0 -0
- data/app/assets/javascripts/disco_app/components/{filterable_shop_list.js.jsx → custom/filterable_shop_list.js.jsx} +1 -1
- data/app/assets/javascripts/disco_app/components/custom/inline-radio-options.es6.jsx +59 -0
- data/app/assets/javascripts/disco_app/components/custom/rules-editor.es6.jsx +360 -0
- data/app/assets/javascripts/disco_app/components/{shop_list.js.jsx → custom/shop_list.js.jsx} +9 -11
- data/app/assets/javascripts/disco_app/components/custom/shop_row.js.jsx +43 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/cards/card-section.es6.jsx +30 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/cards/card.es6.jsx +9 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/cards/cart-section-title.es6.jsx +7 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/forms/base_form.es6.jsx +72 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/forms/base_input.es6.jsx +20 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/forms/button.es6.jsx +13 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-radio.es6.jsx +30 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-select.es6.jsx +39 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-text.es6.jsx +59 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-textarea.es6.jsx +48 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/icons/icon-chevron.es6.jsx +33 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/icons/next-icon.es6.jsx +18 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/input_select.es6.jsx +21 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-annotated-section.es6.jsx +29 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-empty-state.es6.jsx +35 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-footer-help.es6.jsx +13 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-page-actions.es6.jsx +39 -0
- data/app/assets/javascripts/disco_app/components.js +1 -4
- data/app/assets/javascripts/disco_app/disco_app.js +3 -0
- data/app/assets/javascripts/disco_app/ui-kit.js +1 -0
- data/app/assets/stylesheets/disco_app/admin/_header.scss +66 -0
- data/app/assets/stylesheets/disco_app/admin/_layout.scss +40 -0
- data/app/assets/stylesheets/disco_app/admin/_nav.scss +172 -0
- data/app/assets/stylesheets/disco_app/admin.scss +11 -0
- data/app/assets/stylesheets/disco_app/disco_app.scss +12 -12
- data/app/assets/stylesheets/disco_app/{disco/mixins → mixins}/_flexbox.scss +6 -0
- data/app/assets/stylesheets/disco_app/ui-kit/_ui-empty-state.scss +94 -0
- data/app/assets/stylesheets/disco_app/ui-kit/_ui-footer-help.scss +25 -0
- data/app/assets/stylesheets/disco_app/ui-kit/_ui-icons.scss +28 -0
- data/app/assets/stylesheets/disco_app/ui-kit/_ui-kit.scss +5086 -0
- data/app/assets/stylesheets/disco_app/ui-kit/_ui-layout.scss +10 -0
- data/app/assets/stylesheets/disco_app/ui-kit/_ui-page-actions.scss +21 -0
- data/app/assets/stylesheets/disco_app/{disco/_tabs.scss → ui-kit/_ui-tabs.scss} +3 -1
- data/app/assets/stylesheets/disco_app/ui-kit/_ui-type.scss +13 -0
- data/app/controllers/disco_app/admin/concerns/plans_controller.rb +8 -5
- data/app/controllers/disco_app/admin/concerns/subscriptions_controller.rb +32 -0
- data/app/controllers/disco_app/admin/subscriptions_controller.rb +3 -0
- data/app/helpers/disco_app/application_helper.rb +22 -0
- data/app/models/disco_app/concerns/plan.rb +2 -2
- data/app/models/disco_app/concerns/shop.rb +0 -4
- data/app/models/disco_app/concerns/subscription.rb +12 -0
- data/app/resources/disco_app/admin/resources/concerns/shop_resource.rb +40 -3
- data/app/views/disco_app/admin/plans/_form.html.erb +18 -21
- data/app/views/disco_app/admin/plans/_plan_code_fields.html.erb +15 -0
- data/app/views/disco_app/admin/plans/index.html.erb +2 -0
- data/app/views/disco_app/admin/shops/index.html.erb +2 -1
- data/app/views/disco_app/admin/subscriptions/edit.html.erb +33 -0
- data/app/views/disco_app/shared/_icons.html.erb +3 -0
- data/app/views/layouts/admin/_nav_items.erb +20 -0
- data/app/views/layouts/admin.html.erb +53 -9
- data/app/views/layouts/embedded_app.html.erb +2 -0
- data/app/views/layouts/embedded_app_modal.html.erb +11 -0
- data/config/routes.rb +4 -2
- data/lib/disco_app/engine.rb +2 -1
- data/lib/disco_app/version.rb +1 -1
- data/lib/generators/disco_app/disco_app_generator.rb +35 -2
- data/lib/generators/disco_app/templates/assets/javascripts/components.js +3 -0
- data/lib/generators/disco_app/templates/config/database.yml.tt +20 -0
- data/lib/tasks/database.rake +8 -0
- data/test/controllers/disco_app/charges_controller_test.rb +9 -2
- data/test/fixtures/api/widget_store/charges/create_recurring_application_charge_request.json +1 -1
- data/test/fixtures/api/widget_store/charges/create_second_recurring_application_charge_request.json +1 -1
- data/test/fixtures/disco_app/subscriptions.yml +1 -0
- data/test/models/disco_app/subscription_test.rb +19 -0
- data/test/services/disco_app/charges_service_test.rb +9 -2
- data/test/test_helper.rb +3 -0
- metadata +80 -21
- data/app/assets/javascripts/disco_app/components/shop_row.js.jsx +0 -27
- data/app/assets/stylesheets/disco_app/bootstrap/_custom.scss +0 -54
- data/app/assets/stylesheets/disco_app/bootstrap/_variables.scss +0 -872
- data/app/assets/stylesheets/disco_app/disco/_buttons.scss +0 -31
- data/app/assets/stylesheets/disco_app/disco/_cards.scss +0 -52
- data/app/assets/stylesheets/disco_app/disco/_forms.scss +0 -23
- data/app/assets/stylesheets/disco_app/disco/_grid.scss +0 -58
- data/app/assets/stylesheets/disco_app/disco/_sections.scss +0 -61
- data/app/assets/stylesheets/disco_app/disco/_tables.scss +0 -57
- data/app/assets/stylesheets/disco_app/disco/_type.scss +0 -39
- data/app/views/layouts/admin/_navbar.html.erb +0 -25
- data/lib/generators/disco_app/adminify/adminify_generator.rb +0 -35
- data/lib/generators/disco_app/reactify/reactify_generator.rb +0 -45
- /data/app/assets/javascripts/disco_app/components/{shop_filter_tab.js.jsx → custom/shop_filter_tab.js.jsx} +0 -0
- /data/app/assets/javascripts/disco_app/components/{shop_filter_tabs.js.jsx → custom/shop_filter_tabs.js.jsx} +0 -0
- /data/app/assets/javascripts/disco_app/components/{shopify_admin_link.js.jsx → custom/shopify_admin_link.js.jsx} +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class for form components that provides helper methods
|
|
3
|
+
* for rendering errors associated with the form
|
|
4
|
+
**/
|
|
5
|
+
class BaseForm extends React.Component {
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* check if the field parameter has errors associated.
|
|
9
|
+
* if no parameters are given, it checks if there's at least an error at all
|
|
10
|
+
**/
|
|
11
|
+
hasErrors(field) {
|
|
12
|
+
if ((arguments.length === 0 && this.errorKeys().length > 0) ||
|
|
13
|
+
(this.props.errors && this.props.errors.errors.indexOf(field) > -1)) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* returns a list of fields that contain errors
|
|
21
|
+
**/
|
|
22
|
+
errorKeys() {
|
|
23
|
+
return this.props.errors.errors;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* returns the type of resource associate with this error
|
|
28
|
+
**/
|
|
29
|
+
errorType() {
|
|
30
|
+
return this.props.errors.type;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* returns the error messages
|
|
35
|
+
**/
|
|
36
|
+
errorMessages() {
|
|
37
|
+
return this.props.errors.messages;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* renders basic form errors
|
|
42
|
+
**/
|
|
43
|
+
renderErrors() {
|
|
44
|
+
if (!this.hasErrors()) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
var title = "There " + (this.errorMessages().length == 1 ? "is" : "are") + " " + this.errorMessages().length + " error" + (this.errorMessages().length > 1 ? "s" : "") + " for this " + this.errorType() + ":";
|
|
49
|
+
var errorMessages = this.errorMessages().map(function(message) {
|
|
50
|
+
return <li>{message}</li>;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className="ui-banner ui-banner--status-error ui-banner--default-vertical-spacing ui-banner--default-horizontal-spacing">
|
|
55
|
+
<div className="ui-banner__ribbon">
|
|
56
|
+
<svg className="next-icon next-icon--24" viewBox="0 0 24 24">
|
|
57
|
+
<path d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.6 0 12 0zm0 4c1.4 0 2.7.4 3.9 1L12 8.8 8.8 12 5 15.9c-.6-1.1-1-2.5-1-3.9 0-4.4 3.6-8 8-8zm0 16c-1.4 0-2.7-.4-3.9-1l3.9-3.9 3.2-3.2L19 8.1c.6 1.1 1 2.5 1 3.9 0 4.4-3.6 8-8 8z"></path>
|
|
58
|
+
</svg>
|
|
59
|
+
</div>
|
|
60
|
+
<div className="ui-banner__content">
|
|
61
|
+
<h2 className="ui-banner__title">
|
|
62
|
+
{title}
|
|
63
|
+
</h2>
|
|
64
|
+
<ul>
|
|
65
|
+
{errorMessages}
|
|
66
|
+
</ul>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class providing helper method for deciding if an input contains errors.
|
|
3
|
+
* Expects subclasses to have `name` and `errors` props.
|
|
4
|
+
**/
|
|
5
|
+
class BaseInput extends React.Component {
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Checks if the component has associated errors or not
|
|
9
|
+
**/
|
|
10
|
+
hasError() {
|
|
11
|
+
if (!(this.props && this.props.name)) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
var fieldName = this.props.name.substring(this.props.name.lastIndexOf("[") + 1, this.props.name.lastIndexOf("]"));
|
|
15
|
+
return (fieldName &&
|
|
16
|
+
this.props.errors &&
|
|
17
|
+
this.props.errors.errors &&
|
|
18
|
+
this.props.errors.errors.indexOf(fieldName) > -1);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const InputRadio = ({ label, name, value, checked, inline, isLast, onChange }) => {
|
|
2
|
+
|
|
3
|
+
const id = `${name}[${value}]`;
|
|
4
|
+
|
|
5
|
+
const wrapperClassName = classNames({
|
|
6
|
+
'next-input-wrapper': true,
|
|
7
|
+
'inline': inline,
|
|
8
|
+
'sr': !isLast
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const labelClassName = classNames({
|
|
12
|
+
'next-label': true,
|
|
13
|
+
'next-label--switch': true,
|
|
14
|
+
'inline': inline,
|
|
15
|
+
'fw-normal': inline
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const handleChange = (e) => {
|
|
19
|
+
onChange(e.target.value);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return(
|
|
23
|
+
<div className={wrapperClassName}>
|
|
24
|
+
<label htmlFor={id} className={labelClassName}>{label}</label>
|
|
25
|
+
<input id={id} className="next-radio" type="radio" value={value} name={name} checked={checked} onChange={handleChange} />
|
|
26
|
+
<span className="next-radio--styled" />
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const InputSelect = ({ id, label, labelHidden, name, options, value, onChange }) => {
|
|
2
|
+
|
|
3
|
+
const optionElements = options.map((option) => {
|
|
4
|
+
return <option key={option.value} value={option.value}>{option.label}</option>;
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
const labelClassName = classNames({
|
|
8
|
+
'next-label': true,
|
|
9
|
+
'helper--visually-hidden': labelHidden
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const handleChange = (e) => {
|
|
13
|
+
onChange(e.target.value);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return(
|
|
17
|
+
<div className="next-input-wrapper">
|
|
18
|
+
<label className={labelClassName} htmlFor={id}>{label}</label>
|
|
19
|
+
<div className="next-select__wrapper next-input--has-content">
|
|
20
|
+
<select className="next-select rule-field" id={id} name={name} value={value} onChange={handleChange}>
|
|
21
|
+
{optionElements}
|
|
22
|
+
</select>
|
|
23
|
+
<NextIcon name="next-chevron-down" size={12} />
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
InputSelect.propTypes = {
|
|
31
|
+
label: React.PropTypes.string.isRequired,
|
|
32
|
+
name: React.PropTypes.string,
|
|
33
|
+
options: React.PropTypes.arrayOf(
|
|
34
|
+
React.PropTypes.shape({
|
|
35
|
+
label: React.PropTypes.string.isRequired,
|
|
36
|
+
value: React.PropTypes.string.isRequired
|
|
37
|
+
})
|
|
38
|
+
).isRequired
|
|
39
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
class InputText extends BaseInput {
|
|
2
|
+
|
|
3
|
+
render() {
|
|
4
|
+
const { errors, name, value, defaultValue, disabled, helpMessage, label, labelHidden, onChange, placeholder } = this.props;
|
|
5
|
+
|
|
6
|
+
const wrapperClassName = classNames({
|
|
7
|
+
'next-input-wrapper': true,
|
|
8
|
+
'next-input-wrapper--is-error': this.hasError()
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const labelClassName = classNames({
|
|
12
|
+
'next-label': true,
|
|
13
|
+
'helper--visually-hidden': labelHidden
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const handleChange = (e) => {
|
|
17
|
+
onChange(e.target.value);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
let helpElement = null;
|
|
21
|
+
if(helpMessage) {
|
|
22
|
+
helpElement = <p className="next-input__help-text">{helpMessage}</p>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className={wrapperClassName}>
|
|
27
|
+
<label className={labelClassName} htmlFor={name}>{label}</label>
|
|
28
|
+
<input
|
|
29
|
+
id={name}
|
|
30
|
+
className="next-input"
|
|
31
|
+
disabled={disabled}
|
|
32
|
+
value={value}
|
|
33
|
+
defaultValue={defaultValue}
|
|
34
|
+
name={name}
|
|
35
|
+
onChange={handleChange}
|
|
36
|
+
placeholder={placeholder}
|
|
37
|
+
type="text"
|
|
38
|
+
/>
|
|
39
|
+
{helpElement}
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
InputText.propTypes = {
|
|
46
|
+
errors: React.PropTypes.object,
|
|
47
|
+
label: React.PropTypes.string.isRequired,
|
|
48
|
+
name: React.PropTypes.string,
|
|
49
|
+
onChange: React.PropTypes.func,
|
|
50
|
+
placeholder: React.PropTypes.string,
|
|
51
|
+
helpMessage: React.PropTypes.string,
|
|
52
|
+
error: React.PropTypes.bool,
|
|
53
|
+
disabled: React.PropTypes.bool
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
InputText.defaultProps = {
|
|
57
|
+
errors: {},
|
|
58
|
+
disabled: false
|
|
59
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
class InputTextArea extends BaseInput {
|
|
2
|
+
|
|
3
|
+
render() {
|
|
4
|
+
const { errors, defaultValue, label, name, onChange, placeholder, helpMessage } = this.props;
|
|
5
|
+
|
|
6
|
+
const wrapperClassName = classNames({
|
|
7
|
+
'next-input-wrapper': true,
|
|
8
|
+
'next-input-wrapper--is-error': this.hasError()
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const handleChange = (e) => {
|
|
12
|
+
onChange(e.target.value);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
let helpElement = null;
|
|
16
|
+
if(helpMessage) {
|
|
17
|
+
helpElement = <p className="next-input__help-text">{helpMessage}</p>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className={wrapperClassName}>
|
|
22
|
+
<label className="next-label" htmlFor={name}>{label}</label>
|
|
23
|
+
<textarea
|
|
24
|
+
className="next-textarea"
|
|
25
|
+
defaultValue={defaultValue}
|
|
26
|
+
name={name}
|
|
27
|
+
onChange={handleChange}
|
|
28
|
+
placeholder={placeholder}
|
|
29
|
+
/>
|
|
30
|
+
{helpElement}
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
InputTextArea.propTypes = {
|
|
37
|
+
errors: React.PropTypes.object,
|
|
38
|
+
defaultValue: React.PropTypes.string,
|
|
39
|
+
label: React.PropTypes.string.isRequired,
|
|
40
|
+
name: React.PropTypes.string.isRequired,
|
|
41
|
+
onChange: React.PropTypes.func.isRequired,
|
|
42
|
+
placeholder: React.PropTypes.string,
|
|
43
|
+
helpMessage: React.PropTypes.string
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
InputTextArea.defaultProps = {
|
|
47
|
+
errors: {},
|
|
48
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const IconChevron = ({ size, direction, disabled }) => {
|
|
2
|
+
|
|
3
|
+
const iconClasses = classNames({
|
|
4
|
+
"next-icon": true,
|
|
5
|
+
"next-icon--blue": disabled === false,
|
|
6
|
+
"next-icon--sky-darker": disabled,
|
|
7
|
+
"next-icon--rotate-180": direction === 'previous',
|
|
8
|
+
['next-icon--' + size]: true
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<svg
|
|
13
|
+
aria-labelledby="next-chevron"
|
|
14
|
+
role="img"
|
|
15
|
+
className={iconClasses}
|
|
16
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
17
|
+
viewBox="0 0 28 28">
|
|
18
|
+
|
|
19
|
+
<path d="M7,3.8l10.5,10.5L7,24.7l2.7,2.7L23,14.3L9.7,1C9.7,1,7,3.8,7,3.8z" />
|
|
20
|
+
</svg>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
IconChevron.propTypes = {
|
|
25
|
+
direction: React.PropTypes.oneOf(['next', 'previous']).isRequired,
|
|
26
|
+
disabled: React.PropTypes.bool,
|
|
27
|
+
size: React.PropTypes.number
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
IconChevron.defaultProps = {
|
|
31
|
+
disabled: false,
|
|
32
|
+
size: 10
|
|
33
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const NextIcon = ({ size, name }) => {
|
|
2
|
+
|
|
3
|
+
const className = classNames({
|
|
4
|
+
"next-icon": true,
|
|
5
|
+
['next-icon--size-' + size]: true
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<svg className={className}>
|
|
10
|
+
<use xlinkHref={'#' + name} />
|
|
11
|
+
</svg>
|
|
12
|
+
);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
NextIcon.propTypes = {
|
|
16
|
+
size: React.PropTypes.number,
|
|
17
|
+
name: React.PropTypes.string
|
|
18
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const InputSelectOld = ({ label, name, options, value, onChange }) => {
|
|
2
|
+
|
|
3
|
+
const id = name;
|
|
4
|
+
|
|
5
|
+
const optionElements = options.map((option) => {
|
|
6
|
+
return <option key={option.value} value={option.value}>{option.label}</option>;
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const handleChange = (e) => {
|
|
10
|
+
onChange(e.target.value);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
return(
|
|
14
|
+
<div className="next-input-wrapper next-input-wrapper--halved">
|
|
15
|
+
<select id={id} name={name} value={value} onChange={handleChange}>
|
|
16
|
+
{optionElements}
|
|
17
|
+
</select>
|
|
18
|
+
</div>
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
};
|
data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-annotated-section.es6.jsx
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const UIAnnotatedSection = ({ title, description, children }) => {
|
|
2
|
+
|
|
3
|
+
return (
|
|
4
|
+
<section className="ui-annotated-section">
|
|
5
|
+
|
|
6
|
+
<div className="ui-annotated-section__annotation">
|
|
7
|
+
<div className="ui-annotated-section__title">
|
|
8
|
+
<h2 className="next-heading next-heading--no-margin">{title}</h2>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div className="ui-annotated-section__description">
|
|
12
|
+
<p>{description}</p>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div className="ui-annotated-section__content">
|
|
17
|
+
{children}
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
</section>
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
UIAnnotatedSection.propTypes = {
|
|
26
|
+
title: React.PropTypes.string.isRequired,
|
|
27
|
+
description: React.PropTypes.string,
|
|
28
|
+
children: React.PropTypes.node
|
|
29
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const UIEmptyState = ({ title, subtitle, image, href, label }) => {
|
|
2
|
+
|
|
3
|
+
let imageSubsection = null;
|
|
4
|
+
if(image) {
|
|
5
|
+
imageSubsection = (
|
|
6
|
+
<div className="ui-empty-state__subsection">
|
|
7
|
+
<div className="ui-empty-state__items ui-empty-state__items--odd-queries ui-empty-state__items--quantity-queries">
|
|
8
|
+
<div className="ui-empty-state__item">
|
|
9
|
+
<div className="ui-empty-state__subitems">
|
|
10
|
+
<div className="ui-empty-state__subitem">
|
|
11
|
+
<img src={image} alt={title} />
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return(
|
|
21
|
+
<div className="ui-empty-state">
|
|
22
|
+
<div className="ui-empty-state__section">
|
|
23
|
+
<div className="ui-empty-state__subsection">
|
|
24
|
+
<h1 className="next-heading next-heading--xl">{title}</h1>
|
|
25
|
+
<h2 className="next-heading next-heading--subdued">{subtitle}</h2>
|
|
26
|
+
</div>
|
|
27
|
+
{imageSubsection}
|
|
28
|
+
<div className="ui-empty-state__subsection">
|
|
29
|
+
<a className="btn btn-large btn-primary" href={href}>{label}</a>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const UIPageActions = ({ label, disabled, secondaryHref, secondaryLabel }) => {
|
|
2
|
+
|
|
3
|
+
const buttonClassName = classNames({
|
|
4
|
+
'btn': true,
|
|
5
|
+
'btn-primary': !disabled,
|
|
6
|
+
'disabled': disabled
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
let secondaryElement = null;
|
|
10
|
+
if(secondaryHref) {
|
|
11
|
+
secondaryElement = (
|
|
12
|
+
<div className="ui-page-actions__secondary">
|
|
13
|
+
<div className="button-group">
|
|
14
|
+
<a className="btn" href={secondaryHref}>{secondaryLabel}</a>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="ui-page-actions">
|
|
22
|
+
{secondaryElement}
|
|
23
|
+
<div className="ui-page-actions__primary">
|
|
24
|
+
<div className="button-group button-group--right-aligned">
|
|
25
|
+
<button name="button" type="submit" className={buttonClassName} disabled={disabled}>
|
|
26
|
+
{label}
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
UIPageActions.propTypes = {
|
|
35
|
+
label: React.PropTypes.string.isRequired,
|
|
36
|
+
disabled: React.PropTypes.bool,
|
|
37
|
+
secondaryHref: React.PropTypes.string,
|
|
38
|
+
secondaryLabel: React.PropTypes.string
|
|
39
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(){var t,e,n,s,a,i=function(t,e){return function(){return t.apply(e,arguments)}};t={STYLIZED:"next-input--stylized",SELECT_WRAPPER:"next-select__wrapper",TEXTAREA:"next-textarea",INPUT:"next-input",SELECT:"next-select",LABEL:"next-label"},n={LABEL:{FOCUSED:t.LABEL+"--is-focused"},INPUT:{FOCUSED:t.INPUT+"--is-focused"}},e=[t.INPUT,t.TEXTAREA,t.SELECT],s=function(){function s(){this.handleFocusChange=i(this.handleFocusChange,this),document.addEventListener("focus",this.handleFocusChange,!0),document.addEventListener("blur",this.handleFocusChange,!0)}return s.prototype.handleFocusChange=function(t){var s,a,i,o,r,c,l,u,d;for(u=t.target,a=0,o=e.length;o>a;a++)l=e[a],d=u.classList.contains(l);return d?(c=this.findLabelAndParent(u),i=c[0],r=c[1],s="focus"===t.type,i.classList.toggle(n.LABEL.FOCUSED,s),r.classList.toggle(n.INPUT.FOCUSED,s)):void 0},s.prototype.findLabelAndParent=function(e){var n,s;return n=document.querySelectorAll("label[for='"+e.getAttribute("id")+"']")[0],s=this._findParentNodeByClass(t.SELECT_WRAPPER,e),s||(s=this._findParentNodeByClass(t.STYLIZED,e)),[n,s]},s.prototype._findParentNodeByClass=function(t,e){var n;for(n=e.parentNode;!n.classList.contains(t);)n=n.parentNode;return n.classList.contains(t)?n:void 0},s}(),a=new s}).call(this),function(){}.call(this);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
//
|
|
2
|
+
// header.scss
|
|
3
|
+
// Styles for admin header.
|
|
4
|
+
// --------------------------------------------------
|
|
5
|
+
|
|
6
|
+
.header-row {
|
|
7
|
+
display: block;
|
|
8
|
+
box-sizing: border-box;
|
|
9
|
+
position: fixed;
|
|
10
|
+
top: 0;
|
|
11
|
+
z-index: 198;
|
|
12
|
+
left: 230px;
|
|
13
|
+
right: 0;
|
|
14
|
+
height: 56px;
|
|
15
|
+
max-width: 100vw;
|
|
16
|
+
box-shadow: 0 1px 0 rgba(0,0,0,0.07);
|
|
17
|
+
background: #ffffff;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.header {
|
|
21
|
+
position: relative;
|
|
22
|
+
height: 56px;
|
|
23
|
+
padding: 0 20px;
|
|
24
|
+
display: -webkit-box;
|
|
25
|
+
display: -webkit-flex;
|
|
26
|
+
display: -ms-flexbox;
|
|
27
|
+
display: flex;
|
|
28
|
+
-webkit-box-orient: horizontal;
|
|
29
|
+
-webkit-box-direction: normal;
|
|
30
|
+
-webkit-flex-direction: row;
|
|
31
|
+
-ms-flex-direction: row;
|
|
32
|
+
flex-direction: row;
|
|
33
|
+
-webkit-box-align: center;
|
|
34
|
+
-webkit-align-items: center;
|
|
35
|
+
-ms-flex-align: center;
|
|
36
|
+
align-items: center;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.header__main {
|
|
40
|
+
-webkit-box-flex: 1;
|
|
41
|
+
-webkit-flex: 1 0 0%;
|
|
42
|
+
-ms-flex: 1 0 0%;
|
|
43
|
+
flex: 1 0 0%;
|
|
44
|
+
-webkit-box-ordinal-group: 2;
|
|
45
|
+
-webkit-order: 1;
|
|
46
|
+
-ms-flex-order: 1;
|
|
47
|
+
order: 1;
|
|
48
|
+
font-size: 24px;
|
|
49
|
+
text-align: left;
|
|
50
|
+
margin: 0;
|
|
51
|
+
padding: 10px 0;
|
|
52
|
+
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
53
|
+
font-weight: 300;
|
|
54
|
+
white-space: nowrap;
|
|
55
|
+
text-overflow: ellipsis;
|
|
56
|
+
overflow: hidden;
|
|
57
|
+
z-index: 2;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.breadcrumb {
|
|
61
|
+
color: #798c9c;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.breadcrumb a, .breadcrumb a:hover {
|
|
65
|
+
color: #798c9c;
|
|
66
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//
|
|
2
|
+
// layout.scss
|
|
3
|
+
// Styles for admin layout.
|
|
4
|
+
// --------------------------------------------------
|
|
5
|
+
|
|
6
|
+
body {
|
|
7
|
+
line-height: 18px;
|
|
8
|
+
font-size: 13px;
|
|
9
|
+
color: #31373d;
|
|
10
|
+
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
11
|
+
text-rendering: optimizeLegibility;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
html, body {
|
|
15
|
+
height: 100%;
|
|
16
|
+
margin: 0;
|
|
17
|
+
padding: 0;
|
|
18
|
+
border: 0;
|
|
19
|
+
background: #ebeef0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#wrapper {
|
|
23
|
+
min-height: 100%;
|
|
24
|
+
background: #ebeef0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#content {
|
|
28
|
+
padding: 56px 0 0 230px;
|
|
29
|
+
width: 100%;
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
outline: 0;
|
|
32
|
+
display: block;
|
|
33
|
+
max-width: 3000px;
|
|
34
|
+
margin: 0 auto;
|
|
35
|
+
position: relative;
|
|
36
|
+
-webkit-transition: -webkit-transform 200ms ease;
|
|
37
|
+
transition: -webkit-transform 200ms ease;
|
|
38
|
+
transition: transform 200ms ease;
|
|
39
|
+
transition: transform 200ms ease, -webkit-transform 200ms ease;
|
|
40
|
+
}
|