fontcustom_canvas 0.1.0 → 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.
@@ -0,0 +1,131 @@
1
+ /*
2
+ * Copyright (C) 2015 - present Instructure, Inc.
3
+ *
4
+ * This file is part of Canvas.
5
+ *
6
+ * Canvas is free software: you can redistribute it and/or modify it under
7
+ * the terms of the GNU Affero General Public License as published by the Free
8
+ * Software Foundation, version 3 of the License.
9
+ *
10
+ * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
11
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
+ * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13
+ * details.
14
+ *
15
+ * You should have received a copy of the GNU Affero General Public License along
16
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+ import React from 'react'
20
+ import PropTypes from 'prop-types'
21
+ import I18n from 'i18nObj'
22
+ import {Text} from '@instructure/ui-elements'
23
+ import ThemeEditorColorRow from './ThemeEditorColorRow'
24
+ import ThemeEditorImageRow from './ThemeEditorImageRow'
25
+ import ThemeEditorVariableGroup from './ThemeEditorVariableGroup'
26
+ import RangeInput from './RangeInput'
27
+ import customTypes from './PropTypes'
28
+ import ThemeEditorFontFamilyRow from './ThemeEditorFontFamilyRow'
29
+ import ThemeEditorFontWeightRow from './ThemeEditorFontWeightRow'
30
+ import ThemeEditorFontSizeRow from './ThemeEditorFontSizeRow'
31
+
32
+ const activeIndexKey = 'Theme__editor-accordion-index'
33
+
34
+ export default class ThemeEditorAccordion extends React.Component {
35
+ static propTypes = {
36
+ variableSchema: customTypes.variableSchema,
37
+ brandConfigVariables: PropTypes.object.isRequired,
38
+ changedValues: PropTypes.object.isRequired,
39
+ changeSomething: PropTypes.func.isRequired,
40
+ getDisplayValue: PropTypes.func.isRequired,
41
+ themeState: PropTypes.object,
42
+ handleThemeStateChange: PropTypes.func
43
+ }
44
+
45
+ state = {
46
+ expandedIndex: Number(window.sessionStorage.getItem(activeIndexKey))
47
+ }
48
+
49
+ setStoredAccordionIndex(index) {
50
+ window.sessionStorage.setItem(activeIndexKey, index)
51
+ }
52
+
53
+ handleToggle = (event, expanded, index) => {
54
+ this.setState(
55
+ {
56
+ expandedIndex: index
57
+ },
58
+ () => {
59
+ this.setStoredAccordionIndex(index)
60
+ }
61
+ )
62
+ }
63
+
64
+ renderRow = varDef => {
65
+ const props = {
66
+ key: varDef.variable_name,
67
+ currentValue: this.props.brandConfigVariables[varDef.variable_name],
68
+ userInput: this.props.changedValues[varDef.variable_name],
69
+ onChange: this.props.changeSomething.bind(null, varDef.variable_name),
70
+ placeholder: this.props.getDisplayValue(varDef.variable_name),
71
+ themeState: this.props.themeState,
72
+ handleThemeStateChange: this.props.handleThemeStateChange,
73
+ varDef
74
+ }
75
+
76
+ switch (varDef.type) {
77
+ case 'color':
78
+ return <ThemeEditorColorRow {...props} />
79
+ case 'image':
80
+ return <ThemeEditorImageRow {...props} />
81
+ case 'percentage':
82
+ const defaultValue = props.currentValue || props.placeholder
83
+ return (
84
+ <RangeInput
85
+ key={varDef.variable_name}
86
+ labelText={varDef.human_name}
87
+ min={0}
88
+ max={1}
89
+ step={0.1}
90
+ defaultValue={defaultValue ? parseFloat(defaultValue) : 0.5}
91
+ name={`brand_config[variables][${varDef.variable_name}]`}
92
+ variableKey={varDef.variable_name}
93
+ onChange={value => props.onChange(value)}
94
+ themeState={props.themeState}
95
+ handleThemeStateChange={props.handleThemeStateChange}
96
+ formatValue={value => I18n.toPercentage(value * 100, {precision: 0})}
97
+ />
98
+ )
99
+ case 'fontFamily':
100
+ return <ThemeEditorFontFamilyRow {...props} />
101
+ case 'fontSize':
102
+ return <ThemeEditorFontSizeRow {...props} />
103
+ case 'fontWeight':
104
+ return <ThemeEditorFontWeightRow {...props} />
105
+ default:
106
+ return null
107
+ }
108
+ }
109
+
110
+ render() {
111
+ return (
112
+ <div>
113
+ {this.props.variableSchema.map((variableGroup, index) => (
114
+ <ThemeEditorVariableGroup
115
+ key={variableGroup.group_name}
116
+ summary={
117
+ <Text as="h2" weight="bold">
118
+ {variableGroup.group_name}
119
+ </Text>
120
+ }
121
+ index={index}
122
+ expanded={index === this.state.expandedIndex}
123
+ onToggle={this.handleToggle}
124
+ >
125
+ {variableGroup.variables.map(this.renderRow)}
126
+ </ThemeEditorVariableGroup>
127
+ ))}
128
+ </div>
129
+ )
130
+ }
131
+ }
@@ -0,0 +1,147 @@
1
+ import React, {Component} from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import customTypes from './PropTypes'
4
+ import I18n from 'i18n!theme_editor'
5
+ import classnames from 'classnames'
6
+ import fonts from './Fonts'
7
+
8
+ export class ThemeEditorFontFamilyRow extends Component {
9
+ static propTypes = {
10
+ varDef: customTypes.fontFamily,
11
+ onChange: PropTypes.func.isRequired,
12
+ userInput: customTypes.userVariableInput,
13
+ placeholder: PropTypes.string.isRequired,
14
+ themeState: PropTypes.object,
15
+ handleThemeStateChange: PropTypes.func
16
+ }
17
+
18
+ static defaultProps = {
19
+ userInput: {},
20
+ themeState: {},
21
+ handleThemeStateChange() {}
22
+ }
23
+
24
+ state = {}
25
+
26
+ showWarning = () => this.props.userInput.invalid && this.inputNotFocused()
27
+
28
+ warningLabel = () => {
29
+ if (this.showWarning()) {
30
+ return (
31
+ <span role="alert">
32
+ <div className="ic-Form-message ic-Form-message--error" tabIndex="0">
33
+ <div className="ic-Form-message__Layout">
34
+ <i className="icon-warning" role="presentation" />
35
+ {I18n.t("'%{chosenFontFamily}' is not a valid fontFamily.", {
36
+ chosenFontFamily: this.props.userInput.val
37
+ })}
38
+ </div>
39
+ </div>
40
+ </span>
41
+ )
42
+ } else {
43
+ // must return empty alert span so screenreaders
44
+ // read the error when it is inserted
45
+ return <span role="alert" />
46
+ }
47
+ }
48
+
49
+ changedFontFamily(value) {
50
+ // fail fast for no value
51
+ if (!value) return null
52
+
53
+ // set and read fontFamily values from a dom node to get only valid values
54
+ const tempNode = document.createElement('span')
55
+ tempNode.style.fontFamily = value
56
+ const fontFamily = tempNode.style.fontFamily
57
+
58
+ // reject invalid values
59
+ if (!fontFamily) return null
60
+
61
+ return fontFamily
62
+ }
63
+
64
+ inputChange = value => {
65
+ const invalidFontFamily = !!value && (!this.changedFontFamily(value))
66
+ this.props.onChange(value, invalidFontFamily)
67
+ if (!invalidFontFamily) {
68
+ this.props.handleThemeStateChange(this.props.varDef.variable_name, value)
69
+ }
70
+ }
71
+
72
+ inputNotFocused = () => this.textInput && this.textInput !== document.activeElement
73
+
74
+ updateIfMounted = () => {
75
+ this.forceUpdate()
76
+ }
77
+
78
+ textFontFamilyInput = () => (
79
+ <select
80
+ ref={c => (this.textInput = c)}
81
+ id={`brand_config[variables][${this.props.varDef.variable_name}]`}
82
+ className={classnames({
83
+ 'Theme__editor-fontFamily-block_input-text': true,
84
+ 'Theme__editor-fontFamily-block_input': true,
85
+ 'Theme__editor-fontFamily-block_input--has-error': this.props.userInput.invalid,
86
+ 'custom-select': true
87
+ })}
88
+ style={{fontFamily: this.props.placeholder}}
89
+ aria-invalid={this.showWarning()}
90
+ onChange={event => this.inputChange(event.target.value)}
91
+ onBlur={this.updateIfMounted} >
92
+
93
+ { fonts.map((font, key) => {
94
+ if(font == this.props.placeholder){
95
+ return (<option
96
+ key={key}
97
+ value={font}
98
+ style={{fontFamily: font}}
99
+ selected
100
+ >
101
+ {font}
102
+ </option>)
103
+ }else{
104
+ return (<option
105
+ key={key}
106
+ value={font}
107
+ style={{fontFamily: font}}
108
+ >
109
+ {font}
110
+ </option>)
111
+ }
112
+ })}
113
+
114
+ </select>
115
+ )
116
+
117
+ render() {
118
+ const fontFamilyInputValue = this.props.placeholder !== 'none' ? this.props.placeholder : null
119
+ return (
120
+ <section className="Theme__editor-accordion_element Theme__editor-fontFamily ic-Form-control border border-dark mt-1 mb-1 p-1 rounded">
121
+ <div className="Theme__editor-form--fontFamily">
122
+ <label
123
+ htmlFor={`brand_config[variables][${this.props.varDef.variable_name}]`}
124
+ className="Theme__editor-fontFamily_title h6"
125
+ >
126
+ {this.props.varDef.human_name}
127
+ </label>
128
+ <hr />
129
+ <div className="Theme__editor-fontFamily-block border p-2">
130
+ {this.textFontFamilyInput()}
131
+ {this.warningLabel()}
132
+ <p
133
+ className="Theme__editor-fontFamily-label Theme__editor-fontFamily-block_label-sample border text-center shadow"
134
+ style={{fontFamily: this.props.placeholder}}
135
+ /* this <p> and <input type=fontFamily> are here so if you click the 'sample',
136
+ it will pop up a fontFamily picker on browsers that support it */
137
+ >
138
+ Text Example
139
+ </p>
140
+ </div>
141
+ </div>
142
+ </section>
143
+ )
144
+ }
145
+ }
146
+
147
+ export default ThemeEditorFontFamilyRow
@@ -0,0 +1,152 @@
1
+ import React, {Component} from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import customTypes from './PropTypes'
4
+ import I18n from 'i18n!theme_editor'
5
+ import classnames from 'classnames'
6
+
7
+ export class ThemeEditorFontSizeRow extends Component {
8
+ static propTypes = {
9
+ varDef: customTypes.fontSize,
10
+ onChange: PropTypes.func.isRequired,
11
+ userInput: customTypes.userVariableInput,
12
+ placeholder: PropTypes.string.isRequired,
13
+ themeState: PropTypes.object,
14
+ handleThemeStateChange: PropTypes.func
15
+ }
16
+
17
+ static defaultProps = {
18
+ userInput: {},
19
+ themeState: {},
20
+ handleThemeStateChange() {}
21
+ }
22
+
23
+ state = {}
24
+
25
+ showWarning = () => this.props.userInput.invalid && this.inputNotFocused()
26
+
27
+ warningLabel = () => {
28
+ if (this.showWarning()) {
29
+ return (
30
+ <span role="alert">
31
+ <div className="ic-Form-message ic-Form-message--error" tabIndex="0">
32
+ <div className="ic-Form-message__Layout">
33
+ <i className="icon-warning" role="presentation" />
34
+ {I18n.t("'%{chosenFontSize}' is not a valid fontSize.", {
35
+ chosenFontSize: this.props.userInput.val
36
+ })}
37
+ </div>
38
+ </div>
39
+ </span>
40
+ )
41
+ } else {
42
+ // must return empty alert span so screenreaders
43
+ // read the error when it is inserted
44
+ return <span role="alert" />
45
+ }
46
+ }
47
+
48
+ changedFontSize(value) {
49
+ let val = value.slice(0, (value.length - 2))
50
+
51
+ // fail fast for no value
52
+ if (!value) return null
53
+
54
+ // set and read fontSize values from a dom node to get only valid values
55
+ const tempNode = document.createElement('span')
56
+ tempNode.style.fontSize = value
57
+ const fontSize = tempNode.style.fontSize
58
+
59
+ // reject invalid values
60
+ if (!fontSize) return null
61
+
62
+ return fontSize
63
+ }
64
+
65
+ invalidNumber(sizeValue) {
66
+ console.log(sizeValue);
67
+ let val = sizeValue.slice(0, (sizeValue.length - 2))
68
+ console.log(val);
69
+ console.log(+val != NaN);
70
+ console.log((+val).toString().length == val.length);
71
+ console.log((+val > 4 && +val < 73));
72
+ console.log((+val).toString().length == val.length && +val > 4 && +val < 73);
73
+
74
+ return (+val == NaN) ? false : ((+val).toString().length != val.length && +val < 5 && +val > 72)
75
+ }
76
+
77
+ inputChange = value => {
78
+ let val;
79
+ if (value.includes("px")) {
80
+ val = value
81
+ }else{
82
+ val = `${value}px`;
83
+ }
84
+
85
+ const invalidFontSize = !!val && (!this.changedFontSize(val)) || this.invalidNumber(val)
86
+ this.props.onChange(val, invalidFontSize)
87
+ if (!invalidFontSize) {
88
+ this.props.handleThemeStateChange(this.props.varDef.variable_name, val)
89
+ }
90
+ }
91
+
92
+ inputNotFocused = () => this.textInput && this.textInput !== document.activeElement
93
+
94
+ updateIfMounted = () => {
95
+ this.forceUpdate()
96
+ }
97
+
98
+ textFontSizeInput = () => (
99
+ <span>
100
+ <input
101
+ ref={c => (this.textInput = c)}
102
+ type="text"
103
+ name="fontSize"
104
+ id={`brand_config[variables][${this.props.varDef.variable_name}]`}
105
+ className={classnames({
106
+ 'Theme__editor-fontSize-block_input-text': true,
107
+ 'Theme__editor-fontSize-block_input': true,
108
+ 'Theme__editor-fontSize-block_input--has-error': this.props.userInput.invalid,
109
+ 'form-control': true
110
+ })}
111
+ placeholder={this.props.placeholder}
112
+ value={this.props.themeState[this.props.varDef.variable_name]}
113
+ aria-invalid={this.showWarning()}
114
+ onChange={event => this.inputChange(event.target.value)}
115
+ onBlur={this.updateIfMounted}
116
+ />
117
+ </span>
118
+ )
119
+
120
+ render() {
121
+ const fontSizeInputValue = this.props.placeholder !== 'none' ? this.props.placeholder : null
122
+ return (
123
+ <section className="Theme__editor-accordion_element Theme__editor-fontFamily ic-Form-control border border-dark mt-1 mb-1 p-1 rounded">
124
+ <div className="Theme__editor-form--fontSize">
125
+ <label
126
+ htmlFor={`brand_config[variables][${this.props.varDef.variable_name}]`}
127
+ className="Theme__editor-fontSize_title h6"
128
+ >
129
+ {this.props.varDef.human_name}
130
+ </label>
131
+ <hr />
132
+ <div className="Theme__editor-fontSize-block border p-2">
133
+ {this.textFontSizeInput()}
134
+ {this.warningLabel()}\
135
+ <br />
136
+ <small>Hint: Valid from 5 till 72 size!</small>
137
+ <p
138
+ className="Theme__editor-fontSize-label Theme__editor-fontSize-block_label-sample border text-center shadow"
139
+ style={{fontSize: this.props.placeholder}}
140
+ /* this <p> and <input type=fontSize> are here so if you click the 'sample',
141
+ it will pop up a fontSize picker on browsers that support it */
142
+ >
143
+ Text Example
144
+ </p>
145
+ </div>
146
+ </div>
147
+ </section>
148
+ )
149
+ }
150
+ }
151
+
152
+ export default ThemeEditorFontSizeRow
@@ -0,0 +1,176 @@
1
+ import React, {Component} from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import customTypes from './PropTypes'
4
+ import I18n from 'i18n!theme_editor'
5
+ import classnames from 'classnames'
6
+
7
+ export class ThemeEditorFontWeightRow extends Component {
8
+ static propTypes = {
9
+ varDef: customTypes.fontWeight,
10
+ onChange: PropTypes.func.isRequired,
11
+ userInput: customTypes.userVariableInput,
12
+ placeholder: PropTypes.string.isRequired,
13
+ themeState: PropTypes.object,
14
+ handleThemeStateChange: PropTypes.func
15
+ }
16
+
17
+ static defaultProps = {
18
+ userInput: {},
19
+ themeState: {},
20
+ handleThemeStateChange() {}
21
+ }
22
+
23
+ state = {}
24
+
25
+ showWarning = () => this.props.userInput.invalid && this.inputNotFocused()
26
+
27
+ warningLabel = () => {
28
+ if (this.showWarning()) {
29
+ return (
30
+ <span role="alert">
31
+ <div className="ic-Form-message ic-Form-message--error" tabIndex="0">
32
+ <div className="ic-Form-message__Layout">
33
+ <i className="icon-warning" role="presentation" />
34
+ {I18n.t("'%{chosenFontWeight}' is not a valid fontWeight.", {
35
+ chosenFontWeight: this.props.userInput.val
36
+ })}
37
+ </div>
38
+ </div>
39
+ </span>
40
+ )
41
+ } else {
42
+ // must return empty alert span so screenreaders
43
+ // read the error when it is inserted
44
+ return <span role="alert" />
45
+ }
46
+ }
47
+
48
+ changedFontWeight(value) {
49
+ // fail fast for no value
50
+ if (!value) return null
51
+
52
+ // set and read fontWeight values from a dom node to get only valid values
53
+ const tempNode = document.createElement('span')
54
+ tempNode.style.fontWeight = value
55
+ const fontWeight = tempNode.style.fontWeight
56
+
57
+ // reject invalid values
58
+ if (!fontWeight) return null
59
+
60
+ return fontWeight
61
+ }
62
+
63
+ inputChange = value => {
64
+ const invalidFontWeight = !!value && (!this.changedFontWeight(value))
65
+ this.props.onChange(value, invalidFontWeight)
66
+ if (!invalidFontWeight) {
67
+ this.props.handleThemeStateChange(this.props.varDef.variable_name, value)
68
+ }
69
+ }
70
+
71
+ inputNotFocused = () => this.textInput && this.textInput !== document.activeElement
72
+
73
+ updateIfMounted = () => {
74
+ this.forceUpdate()
75
+ }
76
+
77
+ textFontWeightInput = () => (
78
+ <span>
79
+ <input
80
+ ref={c => (this.textInput = c)}
81
+ type="radio"
82
+ name="fontWeight"
83
+ id={`brand_config[variables][${this.props.varDef.variable_name}]`}
84
+ className={classnames({
85
+ 'Theme__editor-fontWeight-block_input-text': true,
86
+ 'Theme__editor-fontWeight-block_input': true,
87
+ 'Theme__editor-fontWeight-block_input--has-error': this.props.userInput.invalid
88
+ })}
89
+ placeholder={this.props.placeholder}
90
+ value="lighter"
91
+ aria-invalid={this.showWarning()}
92
+ onChange={event => this.inputChange(event.target.value)}
93
+ onBlur={this.updateIfMounted}
94
+ />Lighter<br />
95
+ <input
96
+ ref={c => (this.textInput = c)}
97
+ type="radio"
98
+ name="fontWeight"
99
+ id={`brand_config[variables][${this.props.varDef.variable_name}]`}
100
+ className={classnames({
101
+ 'Theme__editor-fontWeight-block_input-text': true,
102
+ 'Theme__editor-fontWeight-block_input': true,
103
+ 'Theme__editor-fontWeight-block_input--has-error': this.props.userInput.invalid
104
+ })}
105
+ placeholder={this.props.placeholder}
106
+ value="normal"
107
+ aria-invalid={this.showWarning()}
108
+ onChange={event => this.inputChange(event.target.value)}
109
+ onBlur={this.updateIfMounted}
110
+ />Normal<br />
111
+ <input
112
+ ref={c => (this.textInput = c)}
113
+ type="radio"
114
+ name="fontWeight"
115
+ id={`brand_config[variables][${this.props.varDef.variable_name}]`}
116
+ className={classnames({
117
+ 'Theme__editor-fontWeight-block_input-text': true,
118
+ 'Theme__editor-fontWeight-block_input': true,
119
+ 'Theme__editor-fontWeight-block_input--has-error': this.props.userInput.invalid
120
+ })}
121
+ placeholder={this.props.placeholder}
122
+ value="bold"
123
+ aria-invalid={this.showWarning()}
124
+ onChange={event => this.inputChange(event.target.value)}
125
+ onBlur={this.updateIfMounted}
126
+ />Bold<br />
127
+ <input
128
+ ref={c => (this.textInput = c)}
129
+ type="radio"
130
+ name="fontWeight"
131
+ id={`brand_config[variables][${this.props.varDef.variable_name}]`}
132
+ className={classnames({
133
+ 'Theme__editor-fontWeight-block_input-text': true,
134
+ 'Theme__editor-fontWeight-block_input': true,
135
+ 'Theme__editor-fontWeight-block_input--has-error': this.props.userInput.invalid
136
+ })}
137
+ placeholder={this.props.placeholder}
138
+ value="bolder"
139
+ aria-invalid={this.showWarning()}
140
+ onChange={event => this.inputChange(event.target.value)}
141
+ onBlur={this.updateIfMounted}
142
+ />Bolder
143
+ </span>
144
+ )
145
+
146
+ render() {
147
+ const fontWeightInputValue = this.props.placeholder !== 'none' ? this.props.placeholder : null
148
+ return (
149
+ <section className="Theme__editor-accordion_element Theme__editor-fontWeight ic-Form-control border border-dark mt-1 mb-1 p-1 rounded">
150
+ <div className="Theme__editor-form--fontWeight">
151
+ <label
152
+ htmlFor={`brand_config[variables][${this.props.varDef.variable_name}]`}
153
+ className="Theme__editor-fontWeight_title h6"
154
+ >
155
+ {this.props.varDef.human_name}
156
+ </label>
157
+ <hr />
158
+ <div className="Theme__editor-fontWeight-block border p-2">
159
+ {this.textFontWeightInput()}
160
+ {this.warningLabel()}
161
+ <p
162
+ className="Theme__editor-fontWeight-label Theme__editor-fontWeight-block_label-sample border text-center shadow"
163
+ style={{fontWeight: this.props.placeholder}}
164
+ /* this <p> and <input type=fontWeight> are here so if you click the 'sample',
165
+ it will pop up a fontWeight picker on browsers that support it */
166
+ >
167
+ Text Example
168
+ </p>
169
+ </div>
170
+ </div>
171
+ </section>
172
+ )
173
+ }
174
+ }
175
+
176
+ export default ThemeEditorFontWeightRow