-
Notifications
You must be signed in to change notification settings - Fork 34
feat: Add native Safari-compatible FileUploadButton component #247
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
96990e7
d6c482c
fe6d0fc
afe4bdf
e39e9b4
fbb9632
88d3c93
fde04f6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -192,6 +192,53 @@ NULL | |
| #' @name Button | ||
| NULL | ||
|
|
||
| #' FileUploadButton | ||
| #' | ||
| #' @description | ||
| #' A Safari-compatible file upload button that combines Fluent UI styling with cross-browser file selection functionality. This component solves the issue where Safari blocks programmatic file input clicks by using React-based file input handling. | ||
| #' | ||
| #' For more details about Fluent UI buttons visit the [official docs](https://developer.microsoft.com/en-us/fluentui#/controls/web/Button). | ||
R3myG marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| #' The R package cannot handle each and every case, so for advanced use cases | ||
| #' you need to work using the original docs to achieve the desired result. | ||
R3myG marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| #' | ||
| #' @param inputId ID of the component. | ||
| #' @param value Starting value. | ||
| #' @param session Object passed as the `session` argument to Shiny server. | ||
| #' @param ... Props to pass to the component. | ||
| #' The allowed props are listed below in the \bold{Details} section. | ||
| #' | ||
| #' @section Best practices: | ||
| #' ### Usage | ||
| #' - Use FileUploadButton when you need Safari-compatible file uploads | ||
| #' - Choose appropriate buttonType for visual hierarchy (primary for main actions) | ||
| #' - Set meaningful accept attributes to filter file types | ||
R3myG marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| #' - Use multiple=TRUE for bulk file uploads | ||
R3myG marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| #' | ||
| #' ### Content | ||
| #' - Use clear, action-oriented text ("Upload Files", "Select Document") | ||
R3myG marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| #' - Include relevant icons when helpful (Upload, Attach, FolderOpen) | ||
R3myG marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| #' - Keep button text concise but descriptive | ||
| #' | ||
| #' ### Accessibility | ||
| #' - Button automatically includes proper ARIA attributes | ||
|
||
| #' - Supports keyboard navigation and screen readers | ||
| #' - File input maintains semantic meaning for assistive technology | ||
|
||
| #' | ||
| #' @details | ||
| #' | ||
| #' * \bold{ text } `string` \cr Text to display on the button. | ||
| #' * \bold{ buttonType } `string` \cr Type of Fluent button: "primary", "default", "compound", "action", "command", "commandBar", or "icon". Defaults to "default". | ||
| #' * \bold{ icon } `string` \cr Optional Fluent UI icon name (e.g., "Upload", "Attach", "FolderOpen"). | ||
| #' * \bold{ accept } `string` \cr File types to accept (e.g., ".xlsx,.csv", ".pdf"). Passed to underlying file input. | ||
R3myG marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| #' * \bold{ multiple } `boolean` \cr Whether to allow multiple file selection. Defaults to FALSE. | ||
| #' * \bold{ disabled } `boolean` \cr Whether the button is disabled. | ||
| #' * \bold{ className } `string` \cr Additional CSS class name for the button. | ||
| #' * \bold{ style } `string` \cr Inline CSS styles for the button. | ||
| #' | ||
| #' @md | ||
| #' @name FileUploadButton | ||
| NULL | ||
|
|
||
| #' Calendar | ||
| #' | ||
| #' @description | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issue: this example doesn't work. The app crashes when using Data Files and Images inputs. Additionally, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @R3myG, the upload now works, but the Additionally, the comment about using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The file could also use formatting with {styler}. |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| import * as React from 'react'; | ||
| import * as Fluent from '@fluentui/react'; | ||
| import { ButtonAdapter, InputAdapter, debounce } from '@/shiny.react'; | ||
|
|
||
|
|
@@ -107,3 +108,82 @@ export const Toggle = InputAdapter(Fluent.Toggle, (value, setValue) => ({ | |
| checked: value, | ||
| onChange: (e, v) => setValue(v), | ||
| })); | ||
|
|
||
| // Safari-compatible file upload button | ||
R3myG marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| export const FileUploadButton = InputAdapter( | ||
| ({ | ||
| value, | ||
| onChange, | ||
| buttonType = 'default', | ||
| icon, | ||
| text, | ||
| accept, | ||
| multiple, | ||
| ...otherProps | ||
| }) => { | ||
| const fileInputRef = React.useRef(null); | ||
|
|
||
| const handleClick = () => { | ||
| if (fileInputRef.current) { | ||
| fileInputRef.current.click(); | ||
| } | ||
| }; | ||
|
|
||
| const handleFileChange = (event) => { | ||
| const { files } = event.target; | ||
| if (files && files.length > 0) { | ||
| // Convert FileList to format Shiny expects | ||
| const fileData = Array.from(files).map((file) => ({ | ||
| name: file.name, | ||
| size: file.size, | ||
| type: file.type, | ||
| lastModified: file.lastModified, | ||
| })); | ||
| onChange(multiple ? fileData : fileData[0]); | ||
| } | ||
| }; | ||
|
|
||
| // Select the appropriate button component | ||
| let ButtonComponent; | ||
| if (buttonType === 'primary') { | ||
| ButtonComponent = Fluent.PrimaryButton; | ||
| } else if (buttonType === 'compound') { | ||
| ButtonComponent = Fluent.CompoundButton; | ||
| } else if (buttonType === 'action') { | ||
| ButtonComponent = Fluent.ActionButton; | ||
| } else if (buttonType === 'command') { | ||
| ButtonComponent = Fluent.CommandButton; | ||
| } else if (buttonType === 'commandBar') { | ||
| ButtonComponent = Fluent.CommandBarButton; | ||
| } else if (buttonType === 'icon') { | ||
| ButtonComponent = Fluent.IconButton; | ||
| } else { | ||
| ButtonComponent = Fluent.DefaultButton; | ||
| } | ||
|
|
||
| return ( | ||
| <div> | ||
| <ButtonComponent | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issue: it looks like it's not possible to pass props to the button than the hard-coded handful. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @R3myG this is still an issue. Although, I see the list of supported attributed extended. |
||
| onClick={handleClick} | ||
| text={text} | ||
| iconProps={icon ? { iconName: icon } : undefined} | ||
| disabled={otherProps.disabled} | ||
| className={otherProps.className} | ||
| style={otherProps.style} | ||
| /> | ||
| <input | ||
| ref={fileInputRef} | ||
| type="file" | ||
| accept={accept} | ||
| multiple={multiple} | ||
| onChange={handleFileChange} | ||
| style={{ display: 'none' }} | ||
| /> | ||
| </div> | ||
| ); | ||
| }, | ||
| (value, setValue) => ({ | ||
| value, | ||
| onChange: setValue, | ||
| }), | ||
| ); | ||

Uh oh!
There was an error while loading. Please reload this page.