Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .scss-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ exclude:
linters:
ColorVariable:
enabled: false
SelectorFormat:
convention: hyphenated_BEM
7 changes: 5 additions & 2 deletions app/assets/javascripts/i18n-strings.js.erb
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,13 @@ window.LoginGov = window.LoginGov || {};
'doc_auth.forms.selected_file',
'doc_auth.forms.change_file',
'doc_auth.forms.choose_file_html',
'doc_auth.headings.back',
'doc_auth.headings.document_capture',
'doc_auth.headings.upload_front',
'doc_auth.headings.upload_back',
'doc_auth.headings.document_capture_front',
'doc_auth.headings.document_capture_back',
'doc_auth.headings.front',
'doc_auth.tips.document_capture_header_text',
'doc_auth.tips.document_capture_hint',
'doc_auth.tips.document_capture_id_text1',
'doc_auth.tips.document_capture_id_text2',
'doc_auth.tips.document_capture_id_text3',
Expand Down
1 change: 1 addition & 0 deletions app/assets/stylesheets/application.css.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@
// up work should upgrade to use a newer USWDS, at which time this should be removed altogether.
#document-capture-form { // scss-lint:disable IdSelector
@import 'uswds/dist/scss/elements/form-controls/file-input';
@import 'components/file-input';
}
73 changes: 73 additions & 0 deletions app/assets/stylesheets/components/_file-input.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
.id-card-file-input .usa-file-input {
max-width: 375px;

&:not(.usa-file-input--has-value) {
@include u-margin-top(1);
position: relative;

.usa-file-input__target {
align-items: center;
bottom: 0;
display: flex;
flex-direction: column;
height: 100%;
justify-content: center;
left: 0;
margin-top: 0;
position: absolute;
right: 0;
top: 0;
}

&::after {
content: '';
display: block;
// 2.125" x 3.375" are common standard ID dimensions
padding-bottom: ((2.125 / 3.375) * 100) + unquote('%');
}
}
}

//================================================
// Pending upstream Login Design System revisions:
//================================================

.usa-file-input__input {
outline-offset: 2px;
}

.usa-file-input:not(.usa-file-input--has-value) {
.usa-file-input__target {
border-color: color('primary');
border-width: 2px;

&:hover {
border-color: color('primary-darker');
}
}

.usa-file-input__input:focus {
outline-offset: 3px;
}
}

.usa-file-input__banner-text {
@include u-font('sans', 'xl');
@include u-margin-bottom(1);
color: color('primary');
display: block;
text-transform: uppercase;
}

.usa-file-input.usa-file-input--single-value {
.usa-file-input__preview {
padding: 0;
}

.usa-file-input__preview__image {
height: auto;
margin-left: auto;
margin-right: 0;
width: 100%;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,22 @@ function DocumentsStep({ value, onChange }) {
{!isMobile && <li>{t('doc_auth.tips.document_capture_id_text4')}</li>}
</ul>
{DOCUMENT_SIDES.map((side) => {
const label = t(`doc_auth.headings.upload_${side}`);
const inputKey = `${side}_image`;

return (
<FileInput
key={side}
label={label}
/* i18n-tasks-use t('doc_auth.headings.document_capture_back') */
/* i18n-tasks-use t('doc_auth.headings.document_capture_front') */
label={t(`doc_auth.headings.document_capture_${side}`)}
hint={t('doc_auth.tips.document_capture_hint')}
/* i18n-tasks-use t('doc_auth.headings.back') */
/* i18n-tasks-use t('doc_auth.headings.front') */
bannerText={t(`doc_auth.headings.${side}`)}
accept={['image/*']}
value={value[inputKey]}
onChange={(nextValue) => onChange({ [inputKey]: nextValue })}
className="id-card-file-input"
/>
);
})}
Expand Down
49 changes: 33 additions & 16 deletions app/javascript/app/document-capture/components/file-input.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import FileImage from './file-image';
import DeviceContext from '../context/device';
import useInstanceId from '../hooks/use-instance-id';
import useI18n from '../hooks/use-i18n';

Expand All @@ -15,14 +16,15 @@ export function isImageFile(file) {
return /^image\//.test(file.type);
}

function FileInput({ label, hint, accept, value, onChange }) {
function FileInput({ label, hint, bannerText, accept, value, onChange, className }) {
const { t, formatHTML } = useI18n();
const instanceId = useInstanceId();
const { isMobile } = useContext(DeviceContext);
const inputId = `file-input-${instanceId}`;
const hintId = `${inputId}-hint`;

return (
<>
<div className={className}>
{/*
* Disable reason: The Airbnb configuration of the `jsx-a11y` rule is strict in that it
* requires _both_ the `for` attribute and nesting, to maximize support for assistive
Expand All @@ -42,11 +44,19 @@ function FileInput({ label, hint, accept, value, onChange }) {
{hint}
</span>
)}
<div className="usa-file-input">
<div
className={[
'usa-file-input usa-file-input--single-value',
value && 'usa-file-input--has-value',
]
.filter(Boolean)
.join(' ')}
>
<div className="usa-file-input__target">
{value && (
{value && !isMobile && (
<div className="usa-file-input__preview-heading">
{t('doc_auth.forms.selected_file')}{' '}
<span className="usa-sr-only">{t('doc_auth.forms.selected_file')}: </span>
{value.name}{' '}
<span className="usa-file-input__choose">{t('doc_auth.forms.change_file')}</span>
</div>
)}
Expand All @@ -57,14 +67,17 @@ function FileInput({ label, hint, accept, value, onChange }) {
)}
{!value && (
<div className="usa-file-input__instructions" aria-hidden="true">
<span className="usa-file-input__drag-text">
{formatHTML(t('doc_auth.forms.choose_file_html'), {
// eslint-disable-next-line react/prop-types
'lg-underline': ({ children }) => (
<span className="usa-file-input__choose">{children}</span>
),
})}
</span>
{bannerText && <strong className="usa-file-input__banner-text">{bannerText}</strong>}
{isMobile && bannerText ? null : (
<span className="usa-file-input__drag-text">
{formatHTML(t('doc_auth.forms.choose_file_html'), {
// eslint-disable-next-line react/prop-types
'lg-underline': ({ children }) => (
<span className="usa-file-input__choose">{children}</span>
),
})}
</span>
)}
</div>
)}
<div className="usa-file-input__box" />
Expand All @@ -80,23 +93,27 @@ function FileInput({ label, hint, accept, value, onChange }) {
/>
</div>
</div>
</>
</div>
);
}

FileInput.propTypes = {
label: PropTypes.string.isRequired,
hint: PropTypes.string,
bannerText: PropTypes.string,
accept: PropTypes.arrayOf(PropTypes.string),
value: PropTypes.instanceOf(window.File),
onChange: PropTypes.func,
className: PropTypes.string,
};

FileInput.defaultProps = {
accept: [],
hint: null,
bannerText: null,
accept: [],
value: undefined,
onChange: () => {},
className: null,
};

export default FileInput;
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,12 @@ function FormSteps({ steps, onComplete }) {
value={values}
onChange={(nextValuesPatch) => setValues({ ...values, ...nextValuesPatch })}
/>
<Button isPrimary onClick={toNextStep} isDisabled={!isStepValid(effectiveStep, values)}>
<Button
isPrimary
onClick={toNextStep}
isDisabled={!isStepValid(effectiveStep, values)}
className="margin-y-5"
>
{t(isLastStep ? 'forms.buttons.submit.default' : 'forms.buttons.continue')}
</Button>
</>
Expand Down
2 changes: 2 additions & 0 deletions config/locales/doc_auth/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ en:
zip_code: Zip Code
headings:
address: Mailing Address
back: Back
capture_complete: We have verified your state issued ID
document_capture: Add your state-issued ID
document_capture_back: Back of your ID
Expand All @@ -32,6 +33,7 @@ en:
document_capture_heading_with_selfie_html: Add your state&#8209;issued&nbsp;ID
and selfie
document_capture_selfie: Your photo
front: Front
selfie: Take a selfie.
ssn: Please enter your social security number.
take_pic_back: Take a photo of the back of your ID
Expand Down
2 changes: 2 additions & 0 deletions config/locales/doc_auth/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ es:
zip_code: Código postal
headings:
address: Dirección de envio
back: Parte posterior
capture_complete: Hemos verificado la identificación emitida por su estado
document_capture: Agregue su identificación emitida por el estado
document_capture_back: Detrás de su identificación
Expand All @@ -33,6 +34,7 @@ es:
document_capture_heading_with_selfie_html: Cargue su identificación emitida
por el estado y una foto suya
document_capture_selfie: Tu foto
front: Frente
selfie: Toma una selfie.
ssn: Por favor ingrese su número de seguro social.
take_pic_back: Toma una foto de la parte posterior de tu identificación
Expand Down
2 changes: 2 additions & 0 deletions config/locales/doc_auth/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ fr:
zip_code: Code postal
headings:
address: Adresse mail
back: Verso
capture_complete: Nous avons vérifié votre ID émis par l'état
document_capture: Ajoutez votre pièce d'identité émise par l'État
document_capture_back: Dos de votre pièce d'identité
Expand All @@ -35,6 +36,7 @@ fr:
document_capture_heading_with_selfie_html: Téléchargez votre pièce d'identité
officielle et une photo de vous
document_capture_selfie: Ta photo
front: De face
selfie: Prendre un selfie.
ssn: S'il vous plaît entrez votre numéro de sécurité sociale.
take_pic_back: Prenez une photo au verso de votre identifiant
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ describe('document-capture/components/document-capture', () => {
it('renders the form steps', () => {
const { getByText } = render(<DocumentCapture />);

const step = getByText('doc_auth.headings.upload_front');
const step = getByText('doc_auth.headings.document_capture_front');

expect(step).to.be.ok();
});
Expand All @@ -16,11 +16,11 @@ describe('document-capture/components/document-capture', () => {
const { getByLabelText, getByText, findByText } = render(<DocumentCapture />);

userEvent.upload(
getByLabelText('doc_auth.headings.upload_front'),
getByLabelText('doc_auth.headings.document_capture_front'),
new window.File([''], 'upload.png', { type: 'image/png' }),
);
userEvent.upload(
getByLabelText('doc_auth.headings.upload_back'),
getByLabelText('doc_auth.headings.document_capture_back'),
new window.File([''], 'upload.png', { type: 'image/png' }),
);
userEvent.click(getByText('forms.buttons.continue'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ describe('document-capture/components/documents-step', () => {
it('renders with front and back inputs', () => {
const { getByLabelText } = render(<DocumentsStep />);

const front = getByLabelText('doc_auth.headings.upload_front');
const back = getByLabelText('doc_auth.headings.upload_back');
const front = getByLabelText('doc_auth.headings.document_capture_front');
const back = getByLabelText('doc_auth.headings.document_capture_back');

expect(front).to.be.ok();
expect(back).to.be.ok();
Expand All @@ -21,7 +21,7 @@ describe('document-capture/components/documents-step', () => {
const { getByLabelText } = render(<DocumentsStep onChange={onChange} />);
const file = new window.File([''], 'upload.png', { type: 'image/png' });

userEvent.upload(getByLabelText('doc_auth.headings.upload_front'), file);
userEvent.upload(getByLabelText('doc_auth.headings.document_capture_front'), file);

expect(onChange.calledOnce).to.be.true();
expect(onChange.getCall(0).args[0]).to.deep.equal({ front_image: file });
Expand All @@ -31,7 +31,7 @@ describe('document-capture/components/documents-step', () => {
const onChange = sinon.spy();
const { getByLabelText } = render(<DocumentsStep onChange={onChange} />);

const input = getByLabelText('doc_auth.headings.upload_front');
const input = getByLabelText('doc_auth.headings.document_capture_front');

// Ideally this wouldn't be so tightly-coupled with the DOM implementation, but instead attempt
// to upload a file of an invalid type. `@testing-library/user-event` doesn't currently support
Expand Down
Loading