Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(BFormFile): add properties placement and browser as in BootstrapVue #1797

Merged
merged 10 commits into from Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
57 changes: 54 additions & 3 deletions apps/docs/src/data/components/formFile.data.ts
Expand Up @@ -5,107 +5,158 @@ export default {
{
component: 'BFormFile',
props: [
{
prop: 'ariaLabel',
type: 'string',
default: undefined,
description: 'Sets the value of `aria-label` attribute on the rendered element',
},
{
prop: 'ariaLabelledBy',
type: 'string',
default: undefined,
description:
'The ID of the element that provides a label for this component. Used as the value for the `aria-labelledby` attribute',
},
{
prop: 'accept',
type: 'string | string[]',
default: '',
description: "Value to set on the file input's `accept` attribute",
},
{
prop: 'autofocus',
type: 'boolean',
default: false,
description:
'When set to `true`, attempts to auto-focus the control when it is mounted, or re-activated when in a keep-alive. Does not set the `autofocus` attribute on the control',
},
{
prop: 'capture',
type: "'boolean' | 'user' | 'environment'",
default: false,
description:
'When set, will instruction the browser to use the devices camera (if supported)',
},
{
prop: 'directory',
type: 'boolean',
default: false,
description: 'Enable `directory` mode (on browsers that support it)',
},
{
prop: 'disabled',
type: 'boolean',
default: false,
description:
"When set to `true`, disables the component's functionality and places it in a disabled state",
},
{
prop: 'form',
type: 'string',
default: undefined,
description:
'ID of the form that the form control belongs to. Sets the `form` attribute on the control',
},
{
prop: 'id',
type: 'string',
default: undefined,
description:
'Used to set the `id` attribute on the rendered content, and used as the base to generate any additional element IDs as needed',
},
{
prop: 'multiple',
type: 'boolean',
default: false,
description:
'When set, will allow multiple files to be selected. `v-model` will be an array',
},
{
prop: 'name',
type: 'string',
default: undefined,
description: 'Sets the value of the `name` attribute on the form control',
},
{
prop: 'noDrop',
type: 'boolean',
default: false,
description: 'Disable drag and drop mode',
},
{
prop: 'noTraverse',
type: 'boolean',
default: false,
description: 'Wether to returns files as a flat array when in `directory` mode',
},
{
prop: 'required',
type: 'boolean',
default: false,
description: 'Adds the `required` attribute to the form control',
},
{
prop: 'size',
type: 'Size',
default: undefined,
description: "Set the size of the component's appearance. 'sm', 'md' (default), or 'lg'",
},
{
prop: 'state',
type: 'boolean | null',
default: undefined,
description:
'Controls the validation state appearance of the component. `true` for valid, `false` for invalid, or `null` for no validation state',
},
{
prop: 'modelValue',
type: 'File[] | File | null',
default: undefined,
description:
'The current value of the file input. Will be a single `File` object or an array of `File` objects (if `multiple` or `directory` is set). Can be set to `null`, or an empty array to reset the file input',
},
{
prop: 'label',
type: 'string',
default: '',
description: 'Sets the label for the form group which the file input is rendered',
},
{
prop: 'labelClass',
type: 'ClassValue',
default: undefined,
description: 'Sets the styling for the label',
},
{
prop: 'placement',
type: `'start' | 'end'`,
default: `'start'`,
description: 'Sets the placement for the file button',
},
{
prop: 'browser',
type: 'String',
default: 'Browse',
description: 'Text content for the file browse button',
},
],
emits: [
{
event: 'update:modelValue',
description: '',
description: 'Updates the `v-model` value (see docs for more details)',
args: [
{
arg: 'value',
type: 'File | File[] | null',
description: '',
description:
'Will be a single File object in single mode or an array of File objects in multiple mode',
},
],
},
{
event: 'change',
description: '',
description: 'Original change event of the input',
args: [
{
arg: 'value',
Expand Down
89 changes: 66 additions & 23 deletions packages/bootstrap-vue-next/src/components/BFormFile/BFormFile.vue
@@ -1,36 +1,47 @@
<template>
<label v-if="hasLabelSlot || label" :for="computedId" class="form-label" :class="labelClass">
<label v-if="hasLabelSlot || label" class="form-label" :class="labelClass" :for="computedId">
<slot name="label">
{{ label }}
</slot>
</label>
<input
:id="computedId"
v-bind="$attrs"
ref="input"
type="file"
class="form-control"
:class="computedClasses"
:form="form"
:name="name"
:multiple="props.multiple"
:disabled="props.disabled"
:capture="props.capture"
:accept="computedAccept || undefined"
:required="props.required || undefined"
:aria-required="props.required || undefined"
:directory="props.directory"
:webkitdirectory="props.directory"
@change="onChange"
@drop="onDrop"
/>

<div class="input-group form-input-file">
<label v-if="placement === 'start'" class="input-group-text" :for="computedId">
anrolmar marked this conversation as resolved.
Show resolved Hide resolved
{{ browserText }}
</label>
<input
:id="computedId"
v-bind="$attrs"
ref="input"
type="file"
class="form-control"
:class="computedClasses"
:form="form"
:name="name"
:multiple="props.multiple"
:disabled="props.disabled"
:capture="props.capture"
:accept="computedAccept || undefined"
:required="props.required || undefined"
:aria-label="ariaLabel"
:aria-labelledby="ariaLabelledby"
:aria-required="props.required || undefined"
:directory="props.directory"
:webkitdirectory="props.directory"
@change="onChange"
@drop="onDrop"
/>
<label v-if="placement === 'end'" class="input-group-text" :for="computedId">
{{ browserText }}
</label>
</div>
</template>

<script setup lang="ts">
import {computed, ref, toRef, watch} from 'vue'
import {useFocus, useVModel} from '@vueuse/core'
import type {ClassValue, Size} from '../../types'
import {computed, ref, toRef, watch} from 'vue'
import {useId, useStateClass} from '../../composables'
import type {ClassValue, Size} from '../../types'
import {isEmptySlot} from '../../utils'

defineOptions({
Expand All @@ -44,8 +55,11 @@ const slots = defineSlots<{

const props = withDefaults(
defineProps<{
ariaLabel?: string
ariaLabelledby?: string
accept?: string | readonly string[]
autofocus?: boolean
browserText?: string
capture?: boolean | 'user' | 'environment'
directory?: boolean
disabled?: boolean
Expand All @@ -58,13 +72,17 @@ const props = withDefaults(
name?: string
noDrop?: boolean
noTraverse?: boolean
placement?: 'start' | 'end'
required?: boolean
size?: Size
state?: boolean | null
}>(),
{
ariaLabel: undefined,
ariaLabelledby: undefined,
accept: '',
autofocus: false,
browserText: 'Choose',
// eslint-disable-next-line vue/require-valid-default-prop
capture: false,
directory: false,
Expand All @@ -78,6 +96,7 @@ const props = withDefaults(
name: undefined,
noDrop: false,
noTraverse: false,
placement: 'start',
required: false,
size: undefined,
state: null,
Expand All @@ -100,6 +119,7 @@ const input = ref<HTMLInputElement | null>(null)
const {focused} = useFocus(input, {initialValue: props.autofocus})

const hasLabelSlot = toRef(() => !isEmptySlot(slots['label']))

const computedAccept = toRef(() =>
typeof props.accept === 'string' ? props.accept : props.accept.join(',')
)
Expand Down Expand Up @@ -147,3 +167,26 @@ defineExpose({
reset,
})
</script>

<style scoped>
.form-input-file {
input[type='file'] {
margin-left: -2px !important;

&::-webkit-file-upload-button {
display: none;
}

&::file-selector-button {
display: none;
}
}

&:hover {
label {
background-color: #dde0e3;
cursor: pointer;
}
}
}
</style>