@khanacademy/perseus-editor 0.0.1 → 0.1.2

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.
Files changed (127) hide show
  1. package/.babelrc.js +1 -1
  2. package/CHANGELOG.md +26 -0
  3. package/dist/es/index.css +1020 -0
  4. package/dist/es/index.js +9 -1
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/index.css +1020 -0
  7. package/dist/index.js +9 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/strings.js +6 -1
  10. package/package.json +16 -4
  11. package/src/README.md +7 -0
  12. package/src/__stories__/editor.stories.js +33 -0
  13. package/src/__stories__/section-control-button.stories.jsx +27 -0
  14. package/src/__testdata__/input-number_testdata.js +28 -0
  15. package/src/__tests__/editor_test.jsx +123 -0
  16. package/src/__tests__/i18n_test.jsx +585 -0
  17. package/src/__tests__/traversal_test.jsx +378 -0
  18. package/src/all-editors.js +85 -0
  19. package/src/all-editors_test.js +17 -0
  20. package/src/all-widgets.js +10 -0
  21. package/src/article-editor.jsx +437 -0
  22. package/src/components/__stories__/blur-input.stories.jsx +24 -0
  23. package/src/components/blur-input.jsx +62 -0
  24. package/src/components/checkbox.jsx +194 -0
  25. package/src/components/device-framer.jsx +91 -0
  26. package/src/components/drag-target.jsx +88 -0
  27. package/src/components/dropdown-option.jsx +318 -0
  28. package/src/components/form-wrapped-text-field.jsx +248 -0
  29. package/src/components/graph-settings.jsx +631 -0
  30. package/src/components/hover-behavior.jsx +178 -0
  31. package/src/components/json-editor.jsx +121 -0
  32. package/src/components/link.jsx +173 -0
  33. package/src/components/simple-button.jsx +86 -0
  34. package/src/components/sortable.jsx +186 -0
  35. package/src/components/util.js +39 -0
  36. package/src/components/viewport-resizer.jsx +73 -0
  37. package/src/diffs/__stories__/perseus-diff-wrapper.jsx +8 -0
  38. package/src/diffs/__stories__/structured-item-diff.stories.jsx +855 -0
  39. package/src/diffs/__stories__/tags-diff.stories.jsx +38 -0
  40. package/src/diffs/__stories__/text-diff.stories.jsx +37 -0
  41. package/src/diffs/__tests__/split-diff_test.js +34 -0
  42. package/src/diffs/__tests__/string-array-diff_test.js +74 -0
  43. package/src/diffs/__tests__/structured-item-diff_test.js +432 -0
  44. package/src/diffs/__tests__/widget-diff-performer_test.js +44 -0
  45. package/src/diffs/article-diff.jsx +70 -0
  46. package/src/diffs/item-diff.jsx +77 -0
  47. package/src/diffs/renderer-diff.jsx +143 -0
  48. package/src/diffs/split-diff.jsx +30 -0
  49. package/src/diffs/string-array-diff.jsx +75 -0
  50. package/src/diffs/structured-item-diff.jsx +269 -0
  51. package/src/diffs/tags-diff.jsx +50 -0
  52. package/src/diffs/text-diff.jsx +194 -0
  53. package/src/diffs/widget-diff-performer.jsx +74 -0
  54. package/src/diffs/widget-diff.jsx +291 -0
  55. package/src/editor-page.jsx +351 -0
  56. package/src/editor.jsx +1364 -0
  57. package/src/hint-editor.jsx +447 -0
  58. package/src/i18n.jsx +139 -0
  59. package/src/iframe-content-renderer.jsx +201 -0
  60. package/src/index.js +27 -8
  61. package/src/item-editor.jsx +159 -0
  62. package/src/item-extras-editor.jsx +150 -0
  63. package/src/katex-error-view.jsx +87 -0
  64. package/src/multirenderer-editor.jsx +1074 -0
  65. package/src/section-control-button.jsx +53 -0
  66. package/src/stateful-article-editor.jsx +176 -0
  67. package/src/stateful-editor-page.jsx +90 -0
  68. package/src/styles/perseus-editor.less +1339 -0
  69. package/src/testing-widgets.js +12 -0
  70. package/src/util/register-all-widgets-and-editors-for-testing.js +15 -0
  71. package/src/util/register-all-widgets-and-editors-for-testing_test.js +18 -0
  72. package/src/widgets/__stories__/label-image-editor.stories.jsx +97 -0
  73. package/src/widgets/categorizer-editor.jsx +103 -0
  74. package/src/widgets/cs-program-editor.jsx +258 -0
  75. package/src/widgets/definition-editor.jsx +75 -0
  76. package/src/widgets/dropdown-editor.jsx +201 -0
  77. package/src/widgets/example-graphie-widget-editor.jsx +70 -0
  78. package/src/widgets/example-graphie-widget.jsx +175 -0
  79. package/src/widgets/example-widget-editor.jsx +60 -0
  80. package/src/widgets/example-widget.jsx +153 -0
  81. package/src/widgets/explanation-editor.jsx +89 -0
  82. package/src/widgets/expression-editor.jsx +560 -0
  83. package/src/widgets/graded-group-editor.jsx +162 -0
  84. package/src/widgets/graded-group-set-editor.jsx +101 -0
  85. package/src/widgets/grapher-editor.jsx +154 -0
  86. package/src/widgets/group-editor.jsx +83 -0
  87. package/src/widgets/iframe-editor.jsx +200 -0
  88. package/src/widgets/image-editor.jsx +326 -0
  89. package/src/widgets/input-number-editor.jsx +269 -0
  90. package/src/widgets/interaction/constraint-editor.jsx +147 -0
  91. package/src/widgets/interaction-editor.jsx +1389 -0
  92. package/src/widgets/interactive-graph-editor.jsx +322 -0
  93. package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +81 -0
  94. package/src/widgets/label-image/__stories__/behavior.stories.jsx +32 -0
  95. package/src/widgets/label-image/__stories__/marker.stories.jsx +76 -0
  96. package/src/widgets/label-image/__stories__/question-markers.stories.jsx +106 -0
  97. package/src/widgets/label-image/__stories__/select-image.stories.jsx +60 -0
  98. package/src/widgets/label-image/answer-choices.jsx +231 -0
  99. package/src/widgets/label-image/behavior.jsx +92 -0
  100. package/src/widgets/label-image/marker.jsx +292 -0
  101. package/src/widgets/label-image/question-markers.jsx +181 -0
  102. package/src/widgets/label-image/select-image.jsx +86 -0
  103. package/src/widgets/label-image-editor.jsx +257 -0
  104. package/src/widgets/lights-puzzle-editor.jsx +267 -0
  105. package/src/widgets/matcher-editor.jsx +147 -0
  106. package/src/widgets/matrix-editor.jsx +155 -0
  107. package/src/widgets/measurer-editor.jsx +245 -0
  108. package/src/widgets/molecule-editor.jsx +63 -0
  109. package/src/widgets/number-line-editor.jsx +498 -0
  110. package/src/widgets/numeric-input-editor.jsx +554 -0
  111. package/src/widgets/orderer-editor.jsx +166 -0
  112. package/src/widgets/passage-editor.jsx +115 -0
  113. package/src/widgets/passage-ref-editor.jsx +77 -0
  114. package/src/widgets/passage-ref-target-editor.jsx +47 -0
  115. package/src/widgets/plotter-editor.jsx +537 -0
  116. package/src/widgets/radio/editor.jsx +398 -0
  117. package/src/widgets/reaction-diagram-editor.jsx +127 -0
  118. package/src/widgets/sequence-editor.jsx +205 -0
  119. package/src/widgets/simple-markdown-tester-editor.jsx +84 -0
  120. package/src/widgets/simple-markdown-tester.jsx +91 -0
  121. package/src/widgets/simulator-editor.jsx +144 -0
  122. package/src/widgets/sorter-editor.jsx +100 -0
  123. package/src/widgets/table-editor.jsx +164 -0
  124. package/src/widgets/transformer-editor.jsx +1006 -0
  125. package/src/widgets/unit-editor.jsx +293 -0
  126. package/src/widgets/video-editor.jsx +74 -0
  127. package/src/__tests__/index.test.js +0 -15
@@ -0,0 +1,178 @@
1
+ /* eslint-disable react/sort-comp */
2
+ // @flow
3
+ /**
4
+ * Add hover behavior to another component.
5
+ *
6
+ * This component provides hover and focus status updates in a consistent way
7
+ * across desktop and mobile.
8
+ *
9
+ * For advice on when to use this component instead of Button, see README.md.
10
+ *
11
+ * Many mobile browsers treat :hover as sticky which results in buttons looking
12
+ * pressed even when the user has lifted their finger. This component ensures
13
+ * that when user lifts their finger the hover state is removed.
14
+ *
15
+ * We also chnage how the focus state works. Browsers immediately set the
16
+ * focus state as soon as you click on a button. This component only sets the
17
+ * focus state when a user starts navigating between elements via keybaord.
18
+ *
19
+ * HoverBehavior accepts a function as 'children' which is passed state and
20
+ * an object containing event handlers. The 'children' function should return
21
+ * an React Element of some sort.
22
+ *
23
+ * Example:
24
+ *
25
+ * class MyButtonLikeComponent extends React.Component {
26
+ * render(): React.Node {
27
+ * return <HoverBehavior
28
+ * disabled={this.props.disabled}
29
+ * onClick={this.handleClick}
30
+ * >
31
+ * {({hovered}, handlers) =>
32
+ * <RoundRect
33
+ * textcolor='white'
34
+ * backgroundColor={hovered ? 'red' : 'blue'}/>}
35
+ * {...handlers}
36
+ * >
37
+ * {this.props.children}
38
+ * </RoundRect>
39
+ * }
40
+ * </HoverBehavior>
41
+ * }
42
+ * }
43
+ *
44
+ * This follows a pattern call 'Function as Child Components':
45
+ * https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9#.sw0jiio8b
46
+ */
47
+ import * as React from "react";
48
+
49
+ type Handlers = {|
50
+ onBlur: () => mixed,
51
+ onClick: (e: SyntheticMouseEvent<>) => mixed,
52
+ onFocus: () => mixed,
53
+ onMouseDown: () => mixed,
54
+ onMouseEnter: () => mixed,
55
+ onMouseLeave: () => mixed,
56
+ onTouchStart: () => mixed,
57
+ onTouchEnd: () => mixed,
58
+ |};
59
+
60
+ type State = {|
61
+ focused: boolean,
62
+ hovered: boolean,
63
+ |};
64
+
65
+ type Props = {|
66
+ children?: (state: State, handlers: Handlers) => React.Node,
67
+ disabled?: boolean,
68
+ onClick?: (e: SyntheticMouseEvent<>) => mixed,
69
+ startHovered: boolean,
70
+ shouldUpdate: () => boolean,
71
+ |};
72
+
73
+ export default class HoverBehavior extends React.Component<Props, State> {
74
+ static defaultProps: {|
75
+ shouldUpdate: () => boolean,
76
+ startHovered: boolean,
77
+ |} = {
78
+ startHovered: false,
79
+ shouldUpdate: () => true,
80
+ };
81
+
82
+ constructor(props: Props) {
83
+ super(props);
84
+
85
+ this.state = {
86
+ focused: false,
87
+ hovered: props.startHovered,
88
+ };
89
+ }
90
+
91
+ focusFlag: boolean;
92
+ waitingForClick: boolean;
93
+
94
+ handleClick: (e: SyntheticMouseEvent<>) => void = (
95
+ e: SyntheticMouseEvent<>,
96
+ ) => {
97
+ if (!this.props.disabled) {
98
+ if (this.props.shouldUpdate()) {
99
+ this.waitingForClick = false;
100
+ }
101
+ if (this.props.onClick && !this.props.disabled) {
102
+ this.props.onClick(e);
103
+ }
104
+ }
105
+ };
106
+
107
+ handleMouseEnter: () => void = () => {
108
+ if (
109
+ !this.props.disabled &&
110
+ this.props.shouldUpdate() &&
111
+ !this.waitingForClick
112
+ ) {
113
+ this.setState({hovered: true});
114
+ }
115
+ };
116
+
117
+ handleMouseLeave: () => void = () => {
118
+ if (
119
+ !this.props.disabled &&
120
+ this.props.shouldUpdate() &&
121
+ !this.waitingForClick
122
+ ) {
123
+ this.setState({hovered: false});
124
+ }
125
+ };
126
+
127
+ handleTouchStart: () => void = () => {
128
+ if (!this.props.disabled && this.props.shouldUpdate()) {
129
+ this.setState({hovered: true});
130
+ }
131
+ };
132
+
133
+ handleTouchEnd: () => void = () => {
134
+ if (!this.props.disabled && this.props.shouldUpdate()) {
135
+ this.setState({hovered: false});
136
+ this.waitingForClick = true;
137
+ }
138
+ };
139
+
140
+ handleMouseDown: () => void = () => {
141
+ if (!this.props.disabled && this.props.shouldUpdate()) {
142
+ this.setState({focused: false});
143
+ this.focusFlag = true;
144
+ }
145
+ };
146
+
147
+ handleBlur: () => void = () => {
148
+ if (!this.props.disabled && this.props.shouldUpdate()) {
149
+ this.setState({focused: false});
150
+ }
151
+ };
152
+
153
+ handleFocus: () => void = () => {
154
+ if (!this.props.disabled && this.props.shouldUpdate()) {
155
+ if (this.focusFlag) {
156
+ this.focusFlag = false;
157
+ } else {
158
+ this.setState({focused: true});
159
+ }
160
+ }
161
+ };
162
+
163
+ render(): React.Node {
164
+ const handlers = {
165
+ onBlur: this.handleBlur,
166
+ onClick: this.handleClick,
167
+ onFocus: this.handleFocus,
168
+ onMouseDown: this.handleMouseDown,
169
+ onMouseEnter: this.handleMouseEnter,
170
+ onMouseLeave: this.handleMouseLeave,
171
+ onTouchStart: this.handleTouchStart,
172
+ onTouchEnd: this.handleTouchEnd,
173
+ };
174
+
175
+ const {children} = this.props;
176
+ return children?.(this.state, handlers) || null;
177
+ }
178
+ }
@@ -0,0 +1,121 @@
1
+ /* eslint-disable react/prop-types */
2
+ /* eslint-disable @babel/no-invalid-this */
3
+ /* eslint-disable react/no-unsafe */
4
+ /* eslint-disable react/sort-comp */
5
+ // @flow
6
+ import createReactClass from "create-react-class";
7
+ import * as React from "react";
8
+ import _ from "underscore";
9
+
10
+ const JsonEditor: any = createReactClass({
11
+ displayName: "JsonEditor",
12
+
13
+ getInitialState: function () {
14
+ return {
15
+ currentValue: JSON.stringify(this.props.value, null, 4),
16
+ valid: true,
17
+ };
18
+ },
19
+
20
+ UNSAFE_componentWillReceiveProps: function (nextProps) {
21
+ const shouldReplaceContent =
22
+ !this.state.valid ||
23
+ !_.isEqual(nextProps.value, JSON.parse(this.state.currentValue));
24
+
25
+ if (shouldReplaceContent) {
26
+ this.setState(this.getInitialState());
27
+ }
28
+ },
29
+
30
+ render: function () {
31
+ const classes =
32
+ "perseus-json-editor " + (this.state.valid ? "valid" : "invalid");
33
+
34
+ return (
35
+ <textarea
36
+ className={classes}
37
+ value={this.state.currentValue}
38
+ onChange={this.handleChange}
39
+ onKeyDown={this.handleKeyDown}
40
+ onBlur={this.handleBlur}
41
+ />
42
+ );
43
+ },
44
+
45
+ handleKeyDown: function (e) {
46
+ // This handler allows the tab character to be entered by pressing
47
+ // tab, instead of jumping to the next (non-existant) field
48
+ if (e.key === "Tab") {
49
+ const cursorPos = e.target.selectionStart;
50
+ const v = e.target.value;
51
+ const textBefore = v.substring(0, cursorPos);
52
+ const textAfter = v.substring(cursorPos, v.length);
53
+ e.target.value = textBefore + " " + textAfter;
54
+ e.target.selectionStart = textBefore.length + 4;
55
+ e.target.selectionEnd = textBefore.length + 4;
56
+
57
+ e.preventDefault();
58
+ this.handleChange(e);
59
+ }
60
+ },
61
+
62
+ handleChange: function (e) {
63
+ const nextString = e.target.value;
64
+ try {
65
+ let json = JSON.parse(nextString);
66
+ // Some extra handling to allow copy-pasting from /api/vi
67
+ if (_.isString(json)) {
68
+ json = JSON.parse(json);
69
+ }
70
+ // This callback unfortunately causes multiple renders,
71
+ // but seems to be necessary to avoid componentWillReceiveProps
72
+ // being called before setState has gone through
73
+ this.setState(
74
+ {
75
+ currentValue: nextString,
76
+ valid: true,
77
+ },
78
+ function () {
79
+ this.props.onChange(json);
80
+ },
81
+ );
82
+ } catch (ex) {
83
+ this.setState({
84
+ currentValue: nextString,
85
+ valid: false,
86
+ });
87
+ }
88
+ },
89
+
90
+ // You can type whatever you want as you're typing, but if it's not valid
91
+ // when you blur, it will revert to the last valid value.
92
+ handleBlur: function (e) {
93
+ const nextString = e.target.value;
94
+ try {
95
+ let json = JSON.parse(nextString);
96
+ // Some extra handling to allow copy-pasting from /api/vi
97
+ if (_.isString(json)) {
98
+ json = JSON.parse(json);
99
+ }
100
+ // This callback unfortunately causes multiple renders,
101
+ // but seems to be necessary to avoid componentWillReceiveProps
102
+ // being called before setState has gone through
103
+ this.setState(
104
+ {
105
+ currentValue: JSON.stringify(json, null, 4),
106
+ valid: true,
107
+ },
108
+ function () {
109
+ this.props.onChange(json);
110
+ },
111
+ );
112
+ } catch (ex) {
113
+ this.setState({
114
+ currentValue: JSON.stringify(this.props.value, null, 4),
115
+ valid: true,
116
+ });
117
+ }
118
+ },
119
+ });
120
+
121
+ export default JsonEditor;
@@ -0,0 +1,173 @@
1
+ // @flow
2
+ /**
3
+ * A generic link that removes common link styles.
4
+ *
5
+ * This is useful for wrapping many child components in a link and making sure
6
+ * that "link" styles don't get applied to all you content.
7
+ */
8
+ import {StyleSheet, css} from "aphrodite";
9
+ import * as React from "react";
10
+
11
+ // TODO(diedra): This is terrible! If we don't have a href for a link, it should
12
+ // be a button instead. We just need to make sure we override the default browser
13
+ // button styling (backgroundColor, border, and padding).
14
+ const DEFAULT_HREF = "javascript:void(0)";
15
+
16
+ type AphroditeStyle = any;
17
+
18
+ type DefaultProps = {|
19
+ element: string | React.AbstractComponent<any>,
20
+ // Whether the link should appear in a 'highlighted' state. In
21
+ // practice, this will apply the same styles that are applied on-hover.
22
+ highlighted: boolean,
23
+ href: string,
24
+ // Pass through styles but also add our own to normalize
25
+ style: AphroditeStyle | Array<AphroditeStyle>,
26
+ |};
27
+
28
+ type LinkProps = {|
29
+ ...DefaultProps,
30
+ children?: React.Node,
31
+ // An additional class name to add. This is supported so-as to allow
32
+ // Perseus to add class-based styles to links when using this
33
+ // component as its base link class.
34
+ className?: string,
35
+ // Additional direct styles to add.
36
+ inlineStyles?: {[key: string]: any, ...},
37
+ // Optional referrer query parameter to add to the end of the URL, used
38
+ // for analytics purposes.
39
+ referrer?: string,
40
+ // A target window to open a link in
41
+ target?: string,
42
+ // The relationship between the linked page and the current page.
43
+ // Mostly used to prevent abuse from linked sites.
44
+ rel?: string,
45
+ // The data-test-id attribute
46
+ testId?: string,
47
+ // Specify either an HTML tag (like "a") or a React component (like
48
+ // React Router's `Link`) to be used as the underlying element.
49
+
50
+ onClick?: (e: SyntheticMouseEvent<>) => mixed,
51
+ onMouseOver?: (e: SyntheticMouseEvent<>) => mixed,
52
+ onMouseLeave?: (e: SyntheticMouseEvent<>) => mixed,
53
+ onMouseEnter?: (e: SyntheticMouseEvent<>) => mixed,
54
+ onBlur?: (e: SyntheticEvent<>) => mixed,
55
+ onFocus?: (e: SyntheticEvent<>) => mixed,
56
+ onKeyDown?: (e: SyntheticKeyboardEvent<>) => mixed,
57
+ "aria-label"?: string,
58
+ "aria-selected"?: boolean,
59
+ "aria-current"?: boolean,
60
+ role?: "tab",
61
+ title?: string,
62
+ id?: string,
63
+ tabIndex?: number,
64
+ |};
65
+
66
+ /**
67
+ * A wrapper that creates an anchor tag with normalized styles
68
+ */
69
+ class Link extends React.Component<LinkProps> {
70
+ static defaultProps: DefaultProps = {
71
+ highlighted: false,
72
+ href: DEFAULT_HREF,
73
+ style: ([]: Array<AphroditeStyle>),
74
+ element: "a",
75
+ };
76
+
77
+ render(): React.Element<string> | React.Node {
78
+ const {
79
+ children,
80
+ className,
81
+ highlighted,
82
+ href,
83
+ inlineStyles,
84
+ referrer,
85
+ style,
86
+ target,
87
+ testId,
88
+ element,
89
+ ...otherProps
90
+ } = this.props;
91
+
92
+ // We need to make sure the hash goes at the end of the <a>'s href, so
93
+ // we'll split for now and re-add after potentially adding ?ref=
94
+ // Even though we have defined a default prop for href, if href is
95
+ // set to null, we will still get the null, so we need to handle that.
96
+ const [hrefWithoutHash, hash] = href ? href.split("#") : [DEFAULT_HREF];
97
+ let url = hrefWithoutHash;
98
+
99
+ if (referrer) {
100
+ if (url.indexOf("?") > -1) {
101
+ url += "&ref=" + referrer;
102
+ } else {
103
+ url += "?ref=" + referrer;
104
+ }
105
+ }
106
+
107
+ if (hash) {
108
+ url += "#" + hash;
109
+ }
110
+
111
+ const delegatedStyles = [
112
+ styles.link,
113
+ highlighted && styles.highlighted,
114
+ ];
115
+
116
+ if (Array.isArray(style)) {
117
+ delegatedStyles.push(...style);
118
+ } else {
119
+ delegatedStyles.push(style);
120
+ }
121
+
122
+ const delegatedClassName = className ? " " + className : "";
123
+
124
+ // When passing an anchor tag, we want to use the native `href`.
125
+ // ReactRouter links, though, need a special `to` prop instead.
126
+ const urlProp = element === "a" ? {href: url} : {to: url};
127
+
128
+ // When the link is set to open in a new window, we want to append
129
+ // some `rel` attributes. This is to ensure that the links we're
130
+ // sending folks to can't hijack the existing page.
131
+ // More info: https://www.jitbit.com/alexblog/256-targetblank---the-most-underestimated-vulnerability-ever/
132
+ //
133
+ // If `rel` is already defined, we assume the caller knows what they're
134
+ // doing. In the future, we may wish to check what keywords are passed,
135
+ // and add in `noopener` and `noreferrer`.
136
+ let rel = otherProps.rel;
137
+ if (target === "_blank" && !rel) {
138
+ rel = "noopener noreferrer";
139
+ }
140
+
141
+ return React.createElement(
142
+ element,
143
+ {
144
+ "data-test-id": testId,
145
+ ...otherProps,
146
+ ...urlProp,
147
+ className: css(...delegatedStyles) + delegatedClassName,
148
+ style: inlineStyles,
149
+ target,
150
+ rel,
151
+ },
152
+ children,
153
+ );
154
+ }
155
+ }
156
+
157
+ const styles = StyleSheet.create({
158
+ link: {
159
+ // Remove the gray background color from active links in IE 10
160
+ // https://necolas.github.io/normalize.css/3.0.2/normalize.css
161
+ backgroundColor: "transparent",
162
+ color: "inherit",
163
+ textDecoration: "none",
164
+ ":hover": {
165
+ textDecoration: "underline",
166
+ },
167
+ },
168
+ highlighted: {
169
+ textDecoration: "underline",
170
+ },
171
+ });
172
+
173
+ export default Link;
@@ -0,0 +1,86 @@
1
+ // @flow
2
+ /**
3
+ * Provides a simple styled button
4
+ *
5
+ */
6
+
7
+ import {StyleSheet, css} from "aphrodite";
8
+ import PropTypes from "prop-types";
9
+ import * as React from "react";
10
+
11
+ class SimpleButton extends React.Component<$FlowFixMe> {
12
+ static propTypes = {
13
+ children: PropTypes.node,
14
+ color: PropTypes.oneOf(["orange", "green"]),
15
+ onClick: PropTypes.func,
16
+ };
17
+
18
+ static defaultProps: $FlowFixMe = {
19
+ children: null,
20
+ color: "green",
21
+ onClick: () => {},
22
+ };
23
+
24
+ render(): React.Node {
25
+ const {children, color, onClick, ...otherProps} = this.props;
26
+ return (
27
+ <div
28
+ className={css(
29
+ styles.baseButton,
30
+ color === "green" && styles.green,
31
+ color === "orange" && styles.orange,
32
+ )}
33
+ onClick={onClick}
34
+ {...otherProps}
35
+ >
36
+ {children}
37
+ </div>
38
+ );
39
+ }
40
+ }
41
+
42
+ const styles = StyleSheet.create({
43
+ baseButton: {
44
+ top: "0",
45
+ fontSize: "11px",
46
+ padding: "3px 10px",
47
+ backgroundRepeat: "repeat-x",
48
+ borderColor: `
49
+ rgba(0, 0, 0, 0.1)
50
+ rgba(0, 0, 0, 0.1)
51
+ rgba(0, 0, 0, 0.25)
52
+ `,
53
+ color: "#ffffff",
54
+ borderRadius: "3px",
55
+ lineHeight: "15px",
56
+ cursor: "pointer",
57
+ transition: "box-shadow ease-in-out 0.15s",
58
+ appearance: "none",
59
+ textDecoration: "none",
60
+ textAlign: "center",
61
+ ":hover": {
62
+ boxShadow: `0 1px 1px rgba(0, 0, 0, 0.35),
63
+ inset 0 0 50px 5px rgba(255, 255, 255, 0.2)`,
64
+ },
65
+ },
66
+ green: {
67
+ border: "1px solid #7fab07",
68
+ backgroundColor: "#80ac07",
69
+ backgroundImage: "linear-gradient(to bottom, #8aba08, #719807)",
70
+ ":hover": {
71
+ borderBottomColor: "#547105",
72
+ backgroundColor: "#719807",
73
+ },
74
+ },
75
+ orange: {
76
+ border: "1px solid #d45704",
77
+ backgroundColor: "#d55704",
78
+ backgroundImage: "linear-gradient(to bottom, #e35d04, #c04f03)",
79
+ ":hover": {
80
+ borderBottomColor: "#983e03",
81
+ backgroundColor: "#c04f03",
82
+ },
83
+ },
84
+ });
85
+
86
+ export default SimpleButton;