learningtribes-studio-frontend 1.4.2-alpha.1 → 1.4.2-alpha.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "learningtribes-studio-frontend",
3
- "version": "1.4.2-alpha.1",
3
+ "version": "1.4.2-alpha.2",
4
4
  "description": "The frontend for the Open edX platform",
5
5
  "repository": "Learningtribes/studio-frontend",
6
6
  "scripts": {
@@ -1,47 +1,33 @@
1
1
  @import 'edx-bootstrap';
2
2
 
3
+ // Matches the shared upload widget used by the Uploads page and PDF editor.
4
+ $upload-border: #ebebf1;
5
+ $upload-brand: #5959eb;
6
+ $upload-prompt-primary: #3535cd;
7
+ $upload-prompt-secondary: #1f1f59;
8
+ $upload-icon: #4c4c8e;
9
+
3
10
  .center-text {
4
11
  text-align: center !important;
5
12
  }
6
13
 
7
14
  .drop-zone-compact {
8
- border: 1px dashed #e0e0e0;
15
+ border: 1px dashed $upload-border;
9
16
  border-radius: 8px;
10
17
  padding: 20px;
18
+ display: flex;
19
+ flex-direction: column;
20
+ align-items: center;
21
+ justify-content: center;
22
+ text-align: center;
23
+ cursor: pointer;
11
24
  background-color: #fff;
12
25
  transition: all 0.3s ease;
13
- cursor: pointer;
14
26
 
15
27
  &:hover {
16
- border-color: #4a90d9;
28
+ border-color: $upload-brand;
17
29
  background-color: #fff;
18
30
  }
19
-
20
- [data-identifier="asset-drop-zone-icon"] {
21
- color: #9e9e9e;
22
- margin-bottom: 8px;
23
-
24
- span {
25
- font-size: 32px;
26
- }
27
- }
28
-
29
- h2 {
30
- display: none;
31
- }
32
-
33
- .label-or {
34
- display: none;
35
- }
36
-
37
- button.btn {
38
- display: none;
39
- }
40
-
41
- p {
42
- font-size: 12px;
43
- margin-bottom: 0;
44
- }
45
31
  }
46
32
 
47
33
  .drop-zone {
@@ -50,49 +36,55 @@
50
36
 
51
37
  .drop-active {
52
38
  outline: none;
53
- border: 2px dashed #4a90d9;
54
- background-color: rgba(74, 144, 217, 0.05);
39
+ border-color: $upload-brand;
40
+ background-color: #fff;
41
+ }
42
+
43
+ .upload-icon {
44
+ font-size: 32px;
45
+ width: 32px;
46
+ height: 32px;
47
+ line-height: 32px;
48
+ color: $upload-icon;
49
+ margin-bottom: 8px;
55
50
  }
56
51
 
57
52
  .upload-prompt {
58
- text-align: center;
59
- margin-bottom: 4px;
60
53
  font-size: 16px;
61
54
  font-weight: 600;
55
+ margin-bottom: 4px;
62
56
  }
63
57
 
64
- .upload-link {
58
+ .upload-prompt-primary {
65
59
  background: none;
66
60
  border: none;
67
- color: #4a90d9;
61
+ color: $upload-prompt-primary;
68
62
  font-size: 16px;
69
63
  font-weight: 600;
70
64
  cursor: pointer;
71
65
  padding: 0;
72
- text-decoration: none;
73
66
 
74
- &:hover {
75
- color: #4a90d9;
67
+ &:hover,
68
+ &:focus {
69
+ color: $upload-prompt-primary;
76
70
  text-decoration: none;
77
71
  }
78
72
 
79
73
  &:focus {
80
- outline: 2px solid rgba(74, 144, 217, 0.5);
74
+ outline: 2px solid rgba(89, 89, 235, 0.5);
81
75
  outline-offset: 2px;
82
76
  }
83
77
  }
84
78
 
85
- .upload-text {
86
- color: #666;
79
+ .upload-prompt-secondary {
80
+ color: $upload-prompt-secondary;
87
81
  font-size: 16px;
88
- font-weight: 400;
82
+ font-weight: 600;
89
83
  }
90
84
 
91
- .size-limit {
92
- text-align: center;
93
- color: #999;
85
+ .upload-info {
94
86
  font-size: 14px;
95
- margin-bottom: 0;
87
+ color: $upload-prompt-secondary;
96
88
  }
97
89
 
98
90
  .supported-files-types {
@@ -27,7 +27,7 @@ describe('<AssetsDropZone />', () => {
27
27
 
28
28
  describe('renders', () => {
29
29
  it('contains file browser button', () => {
30
- expect(wrapper.find('button').text()).toEqual('Browse your computer');
30
+ expect(wrapper.find('button').text()).toEqual('Click to upload');
31
31
  });
32
32
  it('hides the icon & upload file size from screen readers', () => {
33
33
  expect(wrapper.find('[aria-hidden=true]').length).toBe(2);
@@ -3,8 +3,6 @@ import classNames from 'classnames';
3
3
  import Dropzone from 'react-dropzone';
4
4
  import PropTypes from 'prop-types';
5
5
 
6
- import { Button } from '@edx/paragon';
7
- import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';
8
6
  import WrappedMessage from '../../utils/i18n/formattedMessageWrapper';
9
7
  import messages from './displayMessages';
10
8
  import styles from './AssetsDropZone.scss';
@@ -57,31 +55,29 @@ export default class AssetsDropZone extends React.Component {
57
55
  activeClassName={styles['drop-active']}
58
56
  className={classNames([
59
57
  this.props.compactStyle ? styles['drop-zone-compact'] : styles['drop-zone'],
60
- styles['center-text'],
61
58
  ])}
62
59
  data-identifier="asset-drop-zone"
63
- disableClick
64
60
  maxSize={this.dropZoneMaxFileSizeBytes}
65
61
  onDrop={this.onDrop}
66
62
  ref={this.setDropZoneRef}
67
63
  >
68
- <div className={styles['center-text']} data-identifier="asset-drop-zone-icon">
69
- <span
70
- aria-hidden
71
- className={'far fa-images '+classNames(
72
- styles['center-text'],
73
- FontAwesomeStyles['fa-3x'])}
74
- />
75
- </div>
64
+ <i
65
+ aria-hidden="true"
66
+ className={classNames('far', 'fa-images', styles['upload-icon'])}
67
+ data-identifier="asset-drop-zone-icon"
68
+ />
76
69
  <div className={styles['upload-prompt']}>
77
70
  <WrappedMessage message={messages.assetsDropZoneBrowseLabel} >
78
71
  { displayText => (
79
72
  <button
80
73
  type="button"
81
74
  aria-describedby="asset-drop-zone-max-file-size-label"
82
- className={styles['upload-link']}
75
+ className={styles['upload-prompt-primary']}
83
76
  data-identifier="asset-drop-zone-browse-button"
84
- onClick={this.handleClick}
77
+ onClick={(event) => {
78
+ event.stopPropagation();
79
+ this.handleClick();
80
+ }}
85
81
  ref={this.setUploadButtonRef}
86
82
  >
87
83
  {displayText}
@@ -90,7 +86,7 @@ export default class AssetsDropZone extends React.Component {
90
86
  </WrappedMessage>
91
87
  <WrappedMessage message={messages.assetsDropZoneHeader}>
92
88
  { displayText => (
93
- <span className={styles['upload-text']} data-identifier="asset-drop-zone-header"> {displayText}</span>
89
+ <span className={styles['upload-prompt-secondary']} data-identifier="asset-drop-zone-header"> {displayText}</span>
94
90
  )}
95
91
  </WrappedMessage>
96
92
  </div>
@@ -101,14 +97,14 @@ export default class AssetsDropZone extends React.Component {
101
97
  }}
102
98
  >
103
99
  { displayText => (
104
- <p
105
- aria-hidden
106
- className={styles['size-limit']}
100
+ <div
101
+ aria-hidden="true"
102
+ className={styles['upload-info']}
107
103
  data-identifier="asset-drop-zone-max-file-size-label"
108
104
  id="asset-drop-zone-max-file-size-label"
109
105
  >
110
106
  {displayText}
111
- </p>
107
+ </div>
112
108
  )}
113
109
  </WrappedMessage>
114
110
  {this.props.supportedFileTypes !== undefined &&
@@ -1,34 +1,41 @@
1
1
  @import 'edx-bootstrap';
2
2
  @import 'utils/scss/focus';
3
3
 
4
- // Horizontal scrollable grid
4
+ // Horizontal strip. Card chrome matches the Uploads thumbnails;
5
+ // selection and scrolling match the PDF editor file strip.
6
+ $brand-primary: #5959eb;
7
+ $neutral-fg: #09092d;
8
+ $neutral-alt: #1f1f59;
9
+ $preview-bg: #fafafa;
10
+ $badge-bg: #ccccff;
11
+ $badge-fg: #1717ab;
12
+
5
13
  .assets-grid {
6
14
  display: flex;
7
- gap: 10px;
15
+ gap: 12px;
8
16
  overflow-x: auto;
9
17
  padding: 8px 0;
10
- margin-bottom: 10px;
18
+ margin-bottom: 8px;
11
19
 
12
20
  &:focus {
13
21
  @include focus();
14
22
  }
15
23
 
16
- // Custom scrollbar
17
24
  &::-webkit-scrollbar {
18
25
  height: 6px;
19
26
  }
20
27
 
21
28
  &::-webkit-scrollbar-track {
22
- background: #f0f0f0;
29
+ background: #ebebf1;
23
30
  border-radius: 3px;
24
31
  }
25
32
 
26
33
  &::-webkit-scrollbar-thumb {
27
- background: #c0c0c0;
34
+ background: #c4c4d8;
28
35
  border-radius: 3px;
29
36
 
30
37
  &:hover {
31
- background: #a0a0a0;
38
+ background: #b0b0cd;
32
39
  }
33
40
  }
34
41
  }
@@ -41,20 +48,18 @@
41
48
 
42
49
  &:hover {
43
50
  .asset-preview {
44
- border-color: #c0c0c0;
51
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
45
52
  }
46
53
  }
47
54
 
48
55
  &.selected {
49
56
  .asset-preview {
50
- border-color: #4a90d9;
51
- border-width: 2px;
57
+ border-color: $brand-primary;
52
58
  }
53
59
 
54
60
  .asset-name {
55
- color: #4a90d9;
56
- background-color: rgba(74, 144, 217, 0.1);
57
- padding: 2px 4px;
61
+ color: #fff;
62
+ background-color: $brand-primary;
58
63
  border-radius: 4px;
59
64
  }
60
65
  }
@@ -63,29 +68,30 @@
63
68
  .asset-preview {
64
69
  width: 100px;
65
70
  height: 100px;
66
- border: 1px solid #e0e0e0;
67
- border-radius: 8px;
68
- background-color: #fff;
71
+ border: 4px solid transparent;
72
+ border-radius: 4px;
73
+ background-color: $preview-bg;
69
74
  display: flex;
70
75
  flex-direction: column;
71
76
  align-items: center;
72
77
  justify-content: center;
73
78
  position: relative;
74
- margin-bottom: 8px;
75
- transition: border-color 0.2s ease;
76
79
  overflow: hidden;
80
+ box-sizing: content-box;
81
+ transition: box-shadow 0.2s ease, border-color 0.2s ease;
77
82
  }
78
83
 
79
84
  .file-type-badge {
80
85
  position: absolute;
81
- top: 6px;
82
- left: 6px;
83
- background-color: #e8f4fd;
84
- color: #1976d2;
85
- font-size: 10px;
86
- font-weight: 700;
87
- padding: 2px 6px;
88
- border-radius: 4px;
86
+ top: 4px;
87
+ left: 4px;
88
+ background-color: $badge-bg;
89
+ color: $badge-fg;
90
+ font-size: 11px;
91
+ font-weight: 600;
92
+ line-height: 16px;
93
+ padding: 4px;
94
+ border-radius: 2px;
89
95
  text-transform: lowercase;
90
96
  z-index: 1;
91
97
  }
@@ -94,11 +100,13 @@
94
100
  width: 100%;
95
101
  height: 100%;
96
102
  object-fit: cover;
103
+ border-radius: 4px;
97
104
  }
98
105
 
99
106
  .asset-icon {
100
- font-size: 36px;
101
- color: #9e9e9e;
107
+ font-size: 35px;
108
+ color: $neutral-alt;
109
+ margin-top: 6px;
102
110
 
103
111
  i {
104
112
  display: block;
@@ -106,30 +114,29 @@
106
114
  }
107
115
 
108
116
  .asset-info {
109
- text-align: center;
117
+ width: 100px;
118
+ margin-top: 8px;
110
119
  }
111
120
 
112
121
  .asset-name {
122
+ display: inline-block;
123
+ max-width: 100%;
124
+ box-sizing: border-box;
125
+ vertical-align: top;
113
126
  font-size: 12px;
114
127
  font-weight: 500;
115
- color: #333;
128
+ color: $neutral-fg;
116
129
  white-space: nowrap;
117
130
  overflow: hidden;
118
131
  text-overflow: ellipsis;
119
- max-width: 100px;
120
- margin-bottom: 2px;
121
-
122
- &.selected {
123
- color: #4a90d9;
124
- background-color: rgba(74, 144, 217, 0.1);
125
- padding: 2px 4px;
126
- border-radius: 4px;
127
- }
132
+ padding: 2px 4px;
133
+ border-radius: 4px;
128
134
  }
129
135
 
130
136
  .asset-size {
131
- font-size: 11px;
132
- color: #666;
137
+ font-size: 12px;
138
+ font-weight: 400;
139
+ color: $neutral-alt;
133
140
  }
134
141
 
135
142
  // Old styles
@@ -94,7 +94,11 @@ export default class AssetsList extends React.Component {
94
94
  const isSelected = this.props.selectedAsset.id === asset.id;
95
95
  const baseUrl = this.props.courseDetails.base_url || '';
96
96
  const fileExt = this.getFileExtension(asset.display_name);
97
- const fileSize = this.formatFileSize(asset.file_size);
97
+ const fileSize = this.formatFileSize(asset.file_size || asset.size);
98
+ const previewPath = asset.thumbnail || asset.url;
99
+ const previewSrc = previewPath
100
+ ? (previewPath.indexOf('http') === 0 ? previewPath : `${baseUrl}${previewPath}`)
101
+ : '';
98
102
 
99
103
  return (
100
104
  <div
@@ -102,16 +106,17 @@ export default class AssetsList extends React.Component {
102
106
  className={classNames(styles['asset-grid-item'], { [styles.selected]: isSelected })}
103
107
  id={`asset-list-option-${index}`}
104
108
  aria-selected={isSelected}
109
+ aria-label={asset.display_name}
105
110
  role="option"
106
111
  key={asset.id || asset.display_name}
107
112
  tabIndex="-1"
108
113
  >
109
114
  <div className={styles['asset-preview']}>
110
115
  <span className={styles['file-type-badge']}>{fileExt}</span>
111
- {asset.thumbnail ? (
116
+ {previewSrc ? (
112
117
  <img
113
118
  className={styles['asset-thumbnail']}
114
- src={`${baseUrl}${asset.thumbnail}`}
119
+ src={previewSrc}
115
120
  alt=""
116
121
  />
117
122
  ) : (
@@ -121,7 +126,7 @@ export default class AssetsList extends React.Component {
121
126
  )}
122
127
  </div>
123
128
  <div className={styles['asset-info']}>
124
- <div className={classNames(styles['asset-name'], { [styles.selected]: isSelected })} title={asset.display_name}>
129
+ <div className={styles['asset-name']} title={asset.display_name}>
125
130
  {asset.display_name}
126
131
  </div>
127
132
  {fileSize && (
@@ -85,71 +85,88 @@
85
85
  }
86
86
 
87
87
  .modal-body {
88
- // Form fields
89
- form {
88
+ form.image-fields {
89
+ fieldset {
90
+ border: 0;
91
+ padding: 0;
92
+ margin: 0;
93
+ }
94
+
95
+ fieldset legend {
96
+ position: absolute;
97
+ width: 1px;
98
+ height: 1px;
99
+ padding: 0;
100
+ margin: -1px;
101
+ overflow: hidden;
102
+ clip: rect(0, 0, 0, 0);
103
+ border: 0;
104
+ }
105
+
90
106
  .paragon-fieldset {
91
107
  margin-bottom: 1.5rem;
92
-
93
- fieldset {
94
- legend {
95
- font-size: 14px;
96
- font-weight: 500;
97
- color: #333;
98
- margin-bottom: 0.5rem;
99
- }
100
- }
101
108
  }
102
109
 
103
110
  .form-group {
104
- margin-bottom: 1rem;
111
+ margin-bottom: 0;
105
112
 
106
113
  label {
114
+ display: block;
107
115
  font-size: 14px;
108
116
  font-weight: 500;
109
- color: #333;
117
+ color: #09092d;
118
+ margin-bottom: 8px;
110
119
  }
111
120
 
112
121
  .form-control {
113
- border-radius: 8px;
114
- border: 1px solid #e0e0e0;
115
- padding: 10px 14px;
122
+ border-radius: 4px;
123
+ border: 1px solid #d7d7e4;
124
+ padding: 12px 16px;
116
125
  font-size: 14px;
126
+ color: #09092d;
127
+ width: 100%;
117
128
 
118
129
  &:focus {
119
- border-color: #e0e0e0;
120
- box-shadow: none;
130
+ border-color: #5959eb;
131
+ box-shadow: 0 0 0 2px #fff, 0 0 0 4px #5959eb;
121
132
  }
122
133
  }
123
134
 
124
- .form-text {
135
+ .form-text,
136
+ small {
137
+ display: block;
125
138
  font-size: 12px;
126
- color: #666;
139
+ color: #1f1f59;
127
140
  margin-top: 4px;
128
141
  }
129
142
  }
130
143
  }
131
144
 
132
- // Dimensions row
133
- .form-row {
134
- .col {
135
- .form-group {
136
- label {
137
- font-size: 14px;
138
- font-weight: 500;
139
- }
140
- }
141
- }
145
+ .dimensions-row {
146
+ display: flex;
147
+ align-items: flex-end;
148
+ gap: 12px;
149
+ margin-top: 20px;
150
+ }
151
+
152
+ .dimension-field {
153
+ flex: 1;
154
+ min-width: 0;
155
+ }
156
+
157
+ .dimensions-separator {
158
+ flex-shrink: 0;
159
+ padding-bottom: 10px;
160
+ color: #1f1f59;
161
+ font-size: 14px;
142
162
  }
143
163
  }
144
164
 
145
- // Checkbox
146
165
  .form-check {
147
- .form-check-input {
148
- margin-top: 0.25rem;
149
- }
166
+ margin-top: 16px;
150
167
 
151
168
  .form-check-label {
152
169
  font-size: 14px;
153
- color: #333;
170
+ color: #09092d;
154
171
  }
155
172
  }
@@ -109,31 +109,31 @@ describe('EditImageModal', () => {
109
109
  });
110
110
  });
111
111
 
112
- it('a Next page button', () => {
112
+ it('a Save button', () => {
113
113
  expect(getNextPageButton(editImageModal)).toHaveLength(1);
114
114
  });
115
115
 
116
- it('a Next page button with correct label', () => {
117
- const nextPageButton = getNextPageButton(editImageModal);
116
+ it('a Save button with correct label', () => {
117
+ const saveButton = getNextPageButton(editImageModal);
118
118
 
119
- const nextPageButtonText = shallowWithIntl(nextPageButton.prop('label'));
120
- expect(nextPageButtonText.prop('message')).toEqual(messages.editImageModalNextPageButton);
119
+ const saveButtonText = shallowWithIntl(saveButton.prop('label'));
120
+ expect(saveButtonText.prop('message')).toEqual(messages.editImageModalInsertImageButton);
121
121
  });
122
122
 
123
- it('a disabled Next page button', () => {
124
- const nextPageButton = getNextPageButton(editImageModal);
123
+ it('a disabled Save button', () => {
124
+ const saveButton = getNextPageButton(editImageModal);
125
125
 
126
- expect(nextPageButton.prop('disabled')).toEqual(true);
126
+ expect(saveButton.prop('disabled')).toEqual(true);
127
127
  });
128
128
 
129
- it('a not disabled Next page button when asset selected', () => {
129
+ it('a not disabled Save button when asset selected', () => {
130
130
  editImageModal.setProps({
131
131
  selectedAsset: testAssetsList[0],
132
132
  });
133
133
 
134
- const nextPageButton = getNextPageButton(editImageModal);
134
+ const saveButton = getNextPageButton(editImageModal);
135
135
 
136
- expect(nextPageButton.prop('disabled')).toEqual(false);
136
+ expect(saveButton.prop('disabled')).toEqual(false);
137
137
  });
138
138
  });
139
139
  });
@@ -320,9 +320,11 @@ describe('EditImageModal', () => {
320
320
  expect(header.prop('message')).toEqual(messages.editImageModalInsertHeader);
321
321
  });
322
322
 
323
- it('a no assets message', () => {
324
- expect(getModalBody(editImageModal).find(WrappedMessage).filterWhere(message => message.prop('tagName') === 'h3').at(1)
325
- .prop('message')).toEqual(messages.editImageModalAssetsListNoAssetsMessage);
323
+ it('no empty-state message', () => {
324
+ const emptyMessage = getModalBody(editImageModal).find(WrappedMessage).filterWhere(
325
+ message => message.prop('message') === messages.editImageModalAssetsListNoAssetsMessage,
326
+ );
327
+ expect(emptyMessage).toHaveLength(0);
326
328
  });
327
329
 
328
330
  it('no AssetsList component', () => {
@@ -506,8 +508,8 @@ describe('EditImageModal', () => {
506
508
  });
507
509
  });
508
510
 
509
- it('a form-row', () => {
510
- expect(getImageDimensionsFieldset(editImageModal).find('div.form-row')).toHaveLength(1);
511
+ it('a dimensions row', () => {
512
+ expect(getImageDimensionsFieldset(editImageModal).find('div.dimensions-row')).toHaveLength(1);
511
513
  });
512
514
 
513
515
  describe('an imageDimensions width InputText with', () => {
@@ -48,9 +48,14 @@ const messages = defineMessages({
48
48
  },
49
49
  editImageModalImageDescriptionLabel: {
50
50
  id: 'editImageModalImageDescriptionLabel',
51
- defaultMessage: 'Image Description (Alt Text)',
51
+ defaultMessage: 'Image Description',
52
52
  description: 'Label for the text input box for an image\'s description',
53
53
  },
54
+ editImageModalImageDecorativeDescription: {
55
+ id: 'editImageModalImageDecorativeDescription',
56
+ defaultMessage: 'If this image is purely decorative, it does not require alternative text.',
57
+ description: 'Help text shown under the image description when alternative text is optional',
58
+ },
54
59
  editImageModalImageDescriptionLegend: {
55
60
  id: 'editImageModalImageDescriptionLegend',
56
61
  defaultMessage: 'Image Description',