diff --git a/py/h2o_wave/types.py b/py/h2o_wave/types.py
index 955a4dc0184..14cac74fd64 100644
--- a/py/h2o_wave/types.py
+++ b/py/h2o_wave/types.py
@@ -2892,6 +2892,7 @@ def __init__(
compact: Optional[bool] = None,
visible: Optional[bool] = None,
tooltip: Optional[str] = None,
+ required: Optional[bool] = None,
):
_guard_scalar('FileUpload.name', name, (str,), True, False, False)
_guard_scalar('FileUpload.label', label, (str,), False, True, False)
@@ -2904,6 +2905,7 @@ def __init__(
_guard_scalar('FileUpload.compact', compact, (bool,), False, True, False)
_guard_scalar('FileUpload.visible', visible, (bool,), False, True, False)
_guard_scalar('FileUpload.tooltip', tooltip, (str,), False, True, False)
+ _guard_scalar('FileUpload.required', required, (bool,), False, True, False)
self.name = name
"""An identifying name for this component."""
self.label = label
@@ -2926,6 +2928,8 @@ def __init__(
"""True if the component should be visible. Defaults to True."""
self.tooltip = tooltip
"""An optional tooltip message displayed when a user clicks the help icon to the right of the component."""
+ self.required = required
+ """True if this is a required field. Defaults to False."""
def dump(self) -> Dict:
"""Returns the contents of this object as a dict."""
@@ -2940,6 +2944,7 @@ def dump(self) -> Dict:
_guard_scalar('FileUpload.compact', self.compact, (bool,), False, True, False)
_guard_scalar('FileUpload.visible', self.visible, (bool,), False, True, False)
_guard_scalar('FileUpload.tooltip', self.tooltip, (str,), False, True, False)
+ _guard_scalar('FileUpload.required', self.required, (bool,), False, True, False)
return _dump(
name=self.name,
label=self.label,
@@ -2952,6 +2957,7 @@ def dump(self) -> Dict:
compact=self.compact,
visible=self.visible,
tooltip=self.tooltip,
+ required=self.required,
)
@staticmethod
@@ -2979,6 +2985,8 @@ def load(__d: Dict) -> 'FileUpload':
_guard_scalar('FileUpload.visible', __d_visible, (bool,), False, True, False)
__d_tooltip: Any = __d.get('tooltip')
_guard_scalar('FileUpload.tooltip', __d_tooltip, (str,), False, True, False)
+ __d_required: Any = __d.get('required')
+ _guard_scalar('FileUpload.required', __d_required, (bool,), False, True, False)
name: str = __d_name
label: Optional[str] = __d_label
multiple: Optional[bool] = __d_multiple
@@ -2990,6 +2998,7 @@ def load(__d: Dict) -> 'FileUpload':
compact: Optional[bool] = __d_compact
visible: Optional[bool] = __d_visible
tooltip: Optional[str] = __d_tooltip
+ required: Optional[bool] = __d_required
return FileUpload(
name,
label,
@@ -3002,6 +3011,7 @@ def load(__d: Dict) -> 'FileUpload':
compact,
visible,
tooltip,
+ required,
)
diff --git a/py/h2o_wave/ui.py b/py/h2o_wave/ui.py
index 33e6a779957..9ee1c43b7cc 100644
--- a/py/h2o_wave/ui.py
+++ b/py/h2o_wave/ui.py
@@ -1123,6 +1123,7 @@ def file_upload(
compact: Optional[bool] = None,
visible: Optional[bool] = None,
tooltip: Optional[str] = None,
+ required: Optional[bool] = None,
) -> Component:
"""Create a file upload component.
A file upload component allows a user to browse, select and upload one or more files.
@@ -1139,6 +1140,7 @@ def file_upload(
compact: True if the component should be displayed compactly (without drag-and-drop capabilities). Defaults to False.
visible: True if the component should be visible. Defaults to True.
tooltip: An optional tooltip message displayed when a user clicks the help icon to the right of the component.
+ required: True if this is a required field. Defaults to False.
Returns:
A `h2o_wave.types.FileUpload` instance.
"""
@@ -1154,6 +1156,7 @@ def file_upload(
compact,
visible,
tooltip,
+ required,
))
diff --git a/r/R/ui.R b/r/R/ui.R
index 708ac43770c..09dc70aca44 100644
--- a/r/R/ui.R
+++ b/r/R/ui.R
@@ -1324,6 +1324,7 @@ ui_mini_buttons <- function(
#' @param compact True if the component should be displayed compactly (without drag-and-drop capabilities). Defaults to False.
#' @param visible True if the component should be visible. Defaults to True.
#' @param tooltip An optional tooltip message displayed when a user clicks the help icon to the right of the component.
+#' @param required True if this is a required field. Defaults to False.
#' @return A FileUpload instance.
#' @export
ui_file_upload <- function(
@@ -1337,7 +1338,8 @@ ui_file_upload <- function(
width = NULL,
compact = NULL,
visible = NULL,
- tooltip = NULL) {
+ tooltip = NULL,
+ required = NULL) {
.guard_scalar("name", "character", name)
.guard_scalar("label", "character", label)
.guard_scalar("multiple", "logical", multiple)
@@ -1349,6 +1351,7 @@ ui_file_upload <- function(
.guard_scalar("compact", "logical", compact)
.guard_scalar("visible", "logical", visible)
.guard_scalar("tooltip", "character", tooltip)
+ .guard_scalar("required", "logical", required)
.o <- list(file_upload=list(
name=name,
label=label,
@@ -1360,7 +1363,8 @@ ui_file_upload <- function(
width=width,
compact=compact,
visible=visible,
- tooltip=tooltip))
+ tooltip=tooltip,
+ required=required))
class(.o) <- append(class(.o), c(.wave_obj, "WaveComponent"))
return(.o)
}
diff --git a/tools/intellij-plugin/src/main/resources/templates/wave-components.xml b/tools/intellij-plugin/src/main/resources/templates/wave-components.xml
index 7b887b96211..bd1ca4e2d29 100644
--- a/tools/intellij-plugin/src/main/resources/templates/wave-components.xml
+++ b/tools/intellij-plugin/src/main/resources/templates/wave-components.xml
@@ -1195,7 +1195,7 @@
-
+
@@ -1206,6 +1206,7 @@
+
diff --git a/tools/vscode-extension/component-snippets.json b/tools/vscode-extension/component-snippets.json
index 5c1de3ebb85..703082bbdd4 100644
--- a/tools/vscode-extension/component-snippets.json
+++ b/tools/vscode-extension/component-snippets.json
@@ -1066,7 +1066,7 @@
"Wave Full FileUpload": {
"prefix": "w_full_file_upload",
"body": [
- "ui.file_upload(name='$1', label='${2:Upload\"}', multiple=${3:False}, max_file_size=${4:None}, max_size=${5:None}, height='${6:300px}', width='${7:100%'}', compact=${8:False}, visible=${9:True}, tooltip='$10', file_extensions=[\n\t\t$11\t\t\n]),$0"
+ "ui.file_upload(name='$1', label='${2:Upload\"}', multiple=${3:False}, max_file_size=${4:None}, max_size=${5:None}, height='${6:300px}', width='${7:100%'}', compact=${8:False}, visible=${9:True}, tooltip='$10', required=${11:False}, file_extensions=[\n\t\t$12\t\t\n]),$0"
],
"description": "Create a full Wave FileUpload."
},
diff --git a/ui/src/file_upload.tsx b/ui/src/file_upload.tsx
index b00bc8a6fda..1f29f1da7b5 100644
--- a/ui/src/file_upload.tsx
+++ b/ui/src/file_upload.tsx
@@ -15,7 +15,7 @@
import * as Fluent from '@fluentui/react'
import { B, F, Id, S, U, xid } from 'h2o-wave'
import React from 'react'
-import { stylesheet } from 'typestyle'
+import { style, stylesheet } from 'typestyle'
import { centerMixin, clas, cssVar, dashed, padding } from './theme'
import { wave } from './ui'
@@ -46,6 +46,8 @@ export interface FileUpload {
visible?: B
/** An optional tooltip message displayed when a user clicks the help icon to the right of the component. */
tooltip?: S
+ /** True if this is a required field. Defaults to False. */
+ required?: B
}
const
@@ -72,9 +74,16 @@ const
minWidth: 80,
boxSizing: 'border-box',
height: 32,
+ display: 'inline-block'
+ },
+ asterisk: {
$nest: {
- '&:hover': {
- cursor: 'pointer'
+ '&::after': {
+ content: "'*'",
+ verticalAlign: 'top',
+ paddingLeft: '2px',
+ lineHeight: '12px',
+ position: 'absolute',
}
}
},
@@ -103,13 +112,14 @@ const convertMegabytesToBytes = (bytes: F) => bytes * 1024 * 1024
export const
XFileUpload = ({ model }: { model: FileUpload }) => {
const
- { name, label, file_extensions, max_file_size, compact, height, max_size, multiple } = model,
+ { name, label, file_extensions, max_file_size, compact, height, max_size, multiple, required } = model,
[isDragging, setIsDragging] = React.useState(false),
[files, setFiles] = React.useState([]),
[fileNames, setFileNames] = React.useState(''),
[percentComplete, setPercentComplete] = React.useState(0.0),
[error, setError] = React.useState(''),
[successMsg, setSuccessMsg] = React.useState(''),
+ { semanticColors: { errorText: errorTextColor } } = Fluent.useTheme(),
maxFileSizeBytes = max_file_size ? convertMegabytesToBytes(max_file_size) : 0,
maxSizeBytes = max_size ? convertMegabytesToBytes(max_size) : 0,
fileExtensions = file_extensions ? file_extensions.map(e => e.startsWith('.') ? e : `.${e}`) : null,
@@ -304,7 +314,9 @@ export const
type='file'
accept={fileExtensions?.join(',')}
multiple={multiple} />
-
+
+
+
Or drag and drop {multiple ? 'files' : 'a file'} here.
>
)
@@ -328,7 +340,7 @@ export const
<>
{label && {label}}
-
+