Skip to content

Commit

Permalink
feat(FileDrop): Add support for validation messages
Browse files Browse the repository at this point in the history
Enables FileDrop to accept the same `messages` prop that
our other form input components accept.

Fixes: INSTUI-862

Test plan:

- The second FileDrop example should now appear with
  an `Invalid filetype` message and red/error border

- Change the message type to `success` and confirm the
  text turns green

- Test the input on a screenreader and confirm that the
  message is read when the input is focused via
  aria-describedby (does not work in NVDA due to
  screenreader/Firefox bug
  nvaccess/nvda#5326)

- Confirm that :focus, :hover, and error states can
  now be seen simultaneously

Change-Id: Iec9d8e98c0ae9bbd99f0800689fd4dccfae9d522
Reviewed-on: https://gerrit.instructure.com/134365
Tested-by: Jenkins
Reviewed-by: Jennifer Stern <jstern@instructure.com>
QA-Review: Dan Sasaki <dsasaki@instructure.com>
Product-Review: Chris Hart <chart@instructure.com>
  • Loading branch information
Chris Hart committed Dec 7, 2017
1 parent ed26f7c commit 879862c
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 20 deletions.
9 changes: 7 additions & 2 deletions packages/ui-core/src/components/FileDrop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,25 @@ example: true
### Accept

The `accept` prop dictates what type of files are allowed. It can be an array or comma-separated string of
[MIME type formats](https://en.wikipedia.org/wiki/Media_type#Common_examples) and/or file extensions
[MIME type formats](https://en.wikipedia.org/wiki/Media_type#Common_examples) and/or file extensions.

FileDrop accepts the same `messages` prop as the other Instructure UI input components for providing
form validation feedback. If there are `error` messages in the `messages` array, FileDrop's border
will turn the theme's `rejectedColor`.

```js
---
example: true
---
<Grid startAt="medium" vAlign="middle">
<Grid startAt="medium">
<GridRow>
<GridCol>

<FileDrop
accept=".jpg"
onDropAccepted={([file]) => { console.log(`File accepted ${file.name}`) }}
onDropRejected={([file]) => { console.log(`File rejected ${file.name}`) }}
messages={[{ text: 'Invalid file type', type: 'error' }]}
label={
<Billboard
size="small"
Expand Down
37 changes: 34 additions & 3 deletions packages/ui-core/src/components/FileDrop/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import PropTypes from 'prop-types'
import classnames from 'classnames'

import themeable from '@instructure/ui-themeable'
import CustomPropTypes from '@instructure/ui-utils/lib/react/CustomPropTypes'
import { omitProps } from '@instructure/ui-utils/lib/react/passthroughProps'
import uid from '@instructure/ui-utils/lib/uid'

import Container from '../Container'
import FormFieldMessages from '../FormField/FormFieldMessages'

import styles from './styles.css'
import theme from './theme'

Expand Down Expand Up @@ -92,6 +96,13 @@ export default class FileDrop extends Component {
PropTypes.arrayOf(PropTypes.string)
]),
/**
* object with shape: `{
* text: PropTypes.string,
* type: PropTypes.oneOf(['error', 'hint', 'success', 'screenreader-only'])
* }`
*/
messages: PropTypes.arrayOf(CustomPropTypes.message),
/**
* callback called when dropping files or when the file dialog window exits successfully
*/
onDrop: PropTypes.func,
Expand Down Expand Up @@ -146,13 +157,15 @@ export default class FileDrop extends Component {
enablePreview: false,
allowMultiple: false,
maxSize: Infinity,
minSize: 0
minSize: 0,
messages: []
}

constructor (props) {
super(props)

this.defaultId = `FileDrop__${uid()}`
this.messagesId = `FileDrop__messages-${uid()}`
}

state = {
Expand All @@ -165,6 +178,16 @@ export default class FileDrop extends Component {
fileInputEl = null
defaultId = null

get hasMessages () {
return this.props.messages && (this.props.messages.length > 0)
}

get invalid () {
return this.hasMessages && this.props.messages.findIndex((message) => {
return message.type === 'error'
}) >= 0
}

getDataTransferItems (event, enablePreview) {
let list = Array.prototype.slice.call(getEventFiles(event, this.fileInputEl))
this.fileInputEl.value = null
Expand Down Expand Up @@ -325,7 +348,7 @@ export default class FileDrop extends Component {
const id = this.props.id || this.defaultId
const classes = {
[styles.root]: true,
[styles.dragRejected]: this.state.isDragRejected,
[styles.dragRejected]: this.state.isDragRejected || this.invalid,
[styles.dragAccepted]: this.state.isDragAccepted,
[styles.focused]: this.state.focused
}
Expand All @@ -342,7 +365,9 @@ export default class FileDrop extends Component {
onDrop={this.handleDrop}
>
<span className={styles.label}>
{this.renderLabel()}
<span className={styles.layout}>
{this.renderLabel()}
</span>
</span>
</label>
<input
Expand All @@ -357,7 +382,13 @@ export default class FileDrop extends Component {
multiple={allowMultiple}
accept={this.acceptStr()}
onChange={this.handleDrop}
aria-describedby={this.hasMessages ? this.messagesId : null}
/>
{(this.hasMessages) ?
<Container display="block" margin="small 0 0">
<FormFieldMessages id={this.messagesId} messages={this.props.messages} />
</Container>
: null}
</div>
)
}
Expand Down
55 changes: 41 additions & 14 deletions packages/ui-core/src/components/FileDrop/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,63 @@
box-sizing: border-box;
position: relative;
z-index: 1;
overflow: hidden;
text-align: center;
border-radius: var(--borderRadius);
border: var(--borderWidth) solid transparent;
border: var(--borderWidth) var(--borderStyle) transparent;
cursor: pointer;

&:hover {
border-color: var(--hoverBorderColor);
border-style: var(--hoverBorderStyle);
}

&::before {
content: "";
position: absolute;
top: -0.25rem;
left: -0.25rem;
right: -0.25rem;
bottom: -0.25rem;
border: var(--focusBorderWidth) var(--focusBorderStyle) var(--focusBorderColor);
border-radius: var(--borderRadius);
opacity: 0;
transform: scale(0.01);
transition: all 0.2s;
}
}

.layout {
display: block;
overflow: hidden;
border-radius: var(--borderRadius);
}

.focused {
.label {
border-color: var(--hoverBorderColor);
border-style: solid;
.label::before {
opacity: 1;
transform: scale(1);
}
}

.dragAccepted,
.dragRejected {
.dragAccepted {
.label {
border-style: var(--focusBorderStyle);
border-color: var(--acceptedColor);
}
}

.dragAccepted .label {
border-color: var(--acceptedColor);
&.focused {
.label::before {
border-color: var(--acceptedColor);
}
}
}

.dragRejected .label {
border-color: var(--rejectedColor);
.dragRejected {
.label {
border-color: var(--rejectedColor);
}

&.focused {
.label::before {
border-color: var(--rejectedColor);
}
}
}
4 changes: 3 additions & 1 deletion packages/ui-core/src/components/FileDrop/theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ export default function generator ({ borders, colors }) {
backgroundColor: colors.white,
borderRadius: borders.radiusLarge,
borderWidth: borders.widthMedium,
borderStyle: 'dashed',
hoverBorderColor: colors.brand,
hoverBorderStyle: 'dashed',
focusBorderWidth: borders.widthSmall,
focusBorderStyle: 'solid',
focusBorderColor: colors.brand,
acceptedColor: colors.brand,
rejectedColor: colors.crimson
}
Expand Down

0 comments on commit 879862c

Please sign in to comment.