Skip to content

Commit

Permalink
feat: implement basic filepicker on viewer
Browse files Browse the repository at this point in the history
  • Loading branch information
vsgoulart committed Aug 23, 2024
1 parent 6d03ef4 commit 757c9f8
Show file tree
Hide file tree
Showing 3 changed files with 334 additions and 5 deletions.
25 changes: 22 additions & 3 deletions packages/form-js-viewer/assets/form-js-base.css
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@
.fjs-container .fjs-input[type='tel'],
.fjs-container .fjs-input[type='number'],
.fjs-container .fjs-button[type='submit'],
.fjs-container .fjs-button[type='button'],
.fjs-container .fjs-button[type='reset'],
.fjs-container .fjs-textarea,
.fjs-container .fjs-select {
Expand Down Expand Up @@ -631,7 +632,8 @@
margin: 6px 10px 6px 4px;
}

.fjs-container .fjs-button[type='submit'] {
.fjs-container .fjs-button[type='submit'],
.fjs-container .fjs-button[type='button'] {
color: var(--cds-text-inverse, var(--color-white));
background-color: var(--color-accent);
border-color: var(--color-accent);
Expand All @@ -644,12 +646,14 @@
}

.fjs-container .fjs-button[type='submit'],
.fjs-container .fjs-button[type='button'],
.fjs-container .fjs-button[type='reset'] {
min-width: 100px;
width: auto;
}

.fjs-container .fjs-button[type='submit'] {
.fjs-container .fjs-button[type='submit'],
.fjs-container .fjs-button[type='button'] {
font-weight: 600;
}

Expand All @@ -660,6 +664,7 @@
.fjs-container .fjs-input[type='tel']:focus,
.fjs-container .fjs-input[type='number']:focus,
.fjs-container .fjs-button[type='submit']:focus,
.fjs-container .fjs-button[type='button']:focus,
.fjs-container .fjs-button[type='reset']:focus,
.fjs-container .fjs-textarea:focus,
.fjs-container .fjs-select:focus {
Expand All @@ -676,7 +681,8 @@
outline: none;
}

.fjs-container .fjs-button[type='submit']:focus {
.fjs-container .fjs-button[type='submit']:focus,
.fjs-container .fjs-button[type='button']:focus {
border-color: var(--color-accent);
}

Expand Down Expand Up @@ -719,13 +725,15 @@
}

.fjs-container .fjs-button[type='submit']:disabled,
.fjs-container .fjs-button[type='button']:disabled,
.fjs-container .fjs-button[type='reset']:disabled {
color: var(--cds-text-on-color-disabled, var(--color-text-light));
background-color: var(--color-background-disabled);
border-color: var(--color-borders-disabled);
}

.fjs-container .fjs-button[type='submit']:read-only,
.fjs-container .fjs-button[type='button']:read-only,
.fjs-container .fjs-button[type='reset']:read-only {
color: var(--text-light);
background-color: var(--color-background-readonly);
Expand Down Expand Up @@ -1269,3 +1277,14 @@
.fjs-container .flatpickr-calendar {
width: 326px;
}

.fjs-hidden {
display: none;
}

.fjs-container .fjs-filepicker-container {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,86 @@
import { formFieldClasses } from '../Util';
import { Label } from '../Label';
import { Errors } from '../Errors';
import { useRef, useState } from 'preact/hooks';
import { useSingleLineTemplateEvaluation } from '../../hooks';

const type = 'filepicker';

/**
* @typedef Props
* @property {(props: { value: string }) => void} onChange
* @property {string} domId
* @property {string[]} errors
* @property {boolean} disabled
* @property {boolean} readonly
* @property {boolean} required
* @property {Object} field
* @property {string} field.id
* @property {string} [field.label]
* @property {string} [field.accept]
* @property {boolean} [field.multiple]
*
* @param {Props} props
* @returns {import("preact").JSX.Element}
*/
export function FilePicker() {
return null;
export function FilePicker(props) {
/** @type {import("preact/hooks").Ref<HTMLInputElement>} */
const fileInputRef = useRef(null);
/** @type {[File[],import("preact/hooks").StateUpdater<File[]>]} */
const [selectedFiles, setSelectedFiles] = useState([]);
const { field, onChange, domId, errors = [], disabled, readonly, required } = props;
const { label, multiple = '', accept = '', id } = field;
const evaluatedAccept = useSingleLineTemplateEvaluation(accept);
const evaluatedMultiple =
useSingleLineTemplateEvaluation(typeof multiple === 'string' ? multiple : multiple.toString()) === 'true';
const errorMessageId = `${domId}-error-message`;

return (
<div class={formFieldClasses(type, { errors, disabled, readonly })}>
<Label htmlFor={domId} label={label} required={required} />
<input
type="file"
className="fjs-hidden"
ref={fileInputRef}
id={domId}
name={domId}
multiple={evaluatedMultiple === false ? undefined : evaluatedMultiple}
accept={evaluatedAccept === '' ? undefined : evaluatedAccept}
onChange={(event) => {
const input = /** @type {HTMLInputElement} */ (event.target);

if (input.files === null || input.files.length === 0) {
onChange({
value: null,
});
return;
}

const files = Array.from(input.files);

onChange({
value: `${id}_value_key`,
});

setSelectedFiles(files);
}}
/>
<div className="fjs-filepicker-container">
<button
type="button"
disabled={disabled}
readonly={readonly}
class="fjs-button"
onClick={() => {
fileInputRef.current.click();
}}>
Browse
</button>
<span className="fjs-form-field-label">{getSelectedFilesLabel(selectedFiles)}</span>
</div>
<Errors id={errorMessageId} errors={errors} />
</div>
);
}

FilePicker.config = {
Expand All @@ -16,3 +94,21 @@ FilePicker.config = {
},
create: (options = {}) => ({ ...options }),
};

// helper //////////

/**
* @param {File[]} files
* @returns {string}
*/
function getSelectedFilesLabel(files) {
if (files.length === 0) {
return 'No files selected';
}

if (files.length === 1) {
return files[0].name;
}

return `${files.length} files selected`;
}
Loading

0 comments on commit 757c9f8

Please sign in to comment.