Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c47502c
feat(BFormFile): Initial implementation of BFormFile using vueUse
dwgray Dec 10, 2025
19188c5
fix(BFormFile): Fixes based on manual tests
dwgray Dec 10, 2025
76986fd
fix(BFormFile): Fix styles for sm and lg
dwgray Dec 10, 2025
5c917fa
test(BFormFile): update tests for new implementation
dwgray Dec 10, 2025
1224fa7
docs(BFormFile): document updated vueUse based control
dwgray Dec 10, 2025
b3e608f
fix(BFormFile): improvemetns to directory mode + docs
dwgray Dec 10, 2025
ebf9ebb
fix(BFormFile): route $ttrs + more docs fixes
dwgray Dec 10, 2025
962f7dc
docs(BFormFile): document typescript extension of File
dwgray Dec 10, 2025
bec7f92
docs(BFormFile): fix type based on parity spreadsheet check
dwgray Dec 11, 2025
dd2a5fb
fix(BFormFile): use external style sheet + update copilot-instructions
dwgray Dec 11, 2025
2730273
docs(BFromFile): add architecture doc
dwgray Dec 11, 2025
651c7a5
fix(BFormFile): react to PR comments
dwgray Dec 11, 2025
f2e4247
fix(App): revert App.vue
dwgray Dec 11, 2025
a82adf5
fix(BFormFile): improve change event
dwgray Dec 11, 2025
f0923dc
fix(BFormFile): remove props that we can't implement
dwgray Dec 11, 2025
8d47c61
fix(BFormFile): more changes based on code review
dwgray Dec 11, 2025
68cc4b2
fix(BFormFile): enable participaction in form submission
dwgray Dec 11, 2025
7cce8e5
feat(BFormFile): deprecate $path in favor of the native property
dwgray Dec 11, 2025
a4b4154
fix(BFormFile): Fix build issues
dwgray Dec 11, 2025
3abe57e
fix(BFormFile): make accept, multiple and directory reactive.
dwgray Dec 11, 2025
b622c46
BFormFile(docs): Clean up remaning references to $path
dwgray Dec 11, 2025
f0cd943
fix(BFormFile): make autofocus more robust
dwgray Dec 11, 2025
909d538
Update architecture/BFORMFILE.md
dwgray Dec 11, 2025
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
6 changes: 4 additions & 2 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,11 @@ templates/

### Working with Styles

- Main styles: `packages/bootstrap-vue-next/src/styles/styles.scss`
- Component styles are typically in the component `.vue` files
- **Component styles**: Place in separate `_component-name.scss` files within the component directory (e.g., `packages/bootstrap-vue-next/src/components/BFormFile/_form-file.scss`)
- **Global styles**: Import component SCSS files in `packages/bootstrap-vue-next/src/styles/styles.scss`
- **DO NOT** use `<style>` blocks in `.vue` component files - always use separate SCSS files
- Bootstrap 5.3.x is the base CSS framework
- Follow Bootstrap variable and mixin conventions

## Timing and Performance

Expand Down
80 changes: 67 additions & 13 deletions apps/docs/src/data/components/formFile.data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,38 @@ export default {
]
),
accept: {
type: 'string | string[]',
type: 'string | readonly string[]',
default: '',
description: "Value to set on the file input's `accept` attribute",
description:
"Value to set on the file input's `accept` attribute. Restricts file types that can be selected",
},
browseText: {
type: 'string',
default: 'Browse',
description: 'Text for the browse button (custom mode only)',
},
capture: {
type: "'boolean' | 'user' | 'environment'",
default: false, // TODO item not in string format
default: false,
description:
"When set, will instruct the browser to use the device's camera (if supported)",
},
directory: {
type: 'boolean',
default: false, // TODO item not in string format
default: false,
description: 'Enable `directory` mode (on browsers that support it)',
},
dropPlaceholder: {
type: 'string',
default: undefined,
description: 'Text to display when dragging files over the drop zone (custom mode only)',
},
fileNameFormatter: {
type: '(files: readonly File[]) => string',
default: undefined,
description:
'Custom formatter function for displaying selected file names (custom mode only)',
},
label: {
type: 'string',
default: '',
Expand All @@ -54,14 +71,14 @@ export default {
description: 'Sets the styling for the label',
},
modelValue: {
type: 'File[] | File | null',
type: 'readonly 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',
},
multiple: {
type: 'boolean',
default: false, // TODO item not in string format
default: false,
description:
'When set, will allow multiple files to be selected. `v-model` will be an array',
},
Expand All @@ -72,18 +89,33 @@ export default {
},
noDrop: {
type: 'boolean',
default: false, // TODO item not in string format
default: false,
description: 'Disable drag and drop mode',
},
noTraverse: {
placeholder: {
type: 'string',
default: undefined,
description: 'Text to display when no file is selected (custom mode only)',
},
showFileNames: {
type: 'boolean',
default: false, // TODO item not in string format
description: 'Whether to return files as a flat array when in `directory` mode',
default: false,
description: 'Display selected file names in custom mode',
},
} satisfies PropRecord<keyof BFormFileProps>,
emits: {
'change': {
description:
'Emitted when the file selection changes. In plain mode, receives the native Event from the file input. In custom mode, receives a CustomEvent with `detail.files` (File[]), `detail.target.files` (FileList-like), and a `files` property for convenience.',
args: {
value: {
type: 'Event | CustomEvent',
description: 'Native Event (plain mode) or CustomEvent with file data (custom mode)',
},
},
},
'update:model-value': {
description: 'Updates the `v-model` value (see docs for more details)', // TODO similar content to BAlert/update:model-value (similar purpose)
description: 'Updates the `v-model` value (see docs for more details)',
args: {
value: {
type: 'File | File[] | null',
Expand All @@ -94,8 +126,30 @@ export default {
},
},
slots: {
label: {
description: '', // TODO missing description
'label': {
description: 'Slot to customize the label content',
scope: {},
},
'file-name': {
description: 'Slot to customize how selected file names are displayed',
scope: {
files: {
type: 'readonly File[]',
description: 'Array of selected File objects',
},
names: {
type: 'readonly string[]',
description: 'Array of file names',
},
},
},
'placeholder': {
description: 'Slot to customize the placeholder text shown when no files are selected',
scope: {},
},
'drop-placeholder': {
description:
'Slot to customize the drag-and-drop overlay text (shown during drag operations)',
scope: {},
},
} satisfies SlotRecord<keyof BFormFileSlots>,
Expand Down
31 changes: 31 additions & 0 deletions apps/docs/src/docs/components/demo/FormFileCustomText.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<template>
<div>
<BFormFile
v-model="file1"
browse-text="Choose File"
placeholder="No file chosen"
/>
<div class="mt-3">
Selected: <strong>{{ file1?.name || 'None' }}</strong>
</div>

<hr />

<BFormFile
v-model="file2"
browse-text="Pick a File"
placeholder="Select a document"
class="mt-3"
/>
<div class="mt-3">
Selected: <strong>{{ file2?.name || 'None' }}</strong>
</div>
</div>
</template>

<script setup lang="ts">
import {ref} from 'vue'

const file1 = ref<File | null>(null)
const file2 = ref<File | null>(null)
</script>
37 changes: 37 additions & 0 deletions apps/docs/src/docs/components/demo/FormFileDirectory.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<template>
<div>
<BFormFile
v-model="files"
directory
multiple
placeholder="Select a directory"
browse-text="Browse Directories"
/>
<div class="mt-3">
<div v-if="files && files.length > 0">
<strong>{{ files.length }} files selected from directory:</strong>
<ul class="mt-2 font-monospace small">
<li
v-for="(file, index) in files.slice(0, 10)"
:key="index"
>
{{ file.webkitRelativePath || file.name }}
</li>
<li v-if="files.length > 10">
<em>...and {{ files.length - 10 }} more files</em>
</li>
</ul>
<p class="text-muted small mt-2">
Each file includes a <code>webkitRelativePath</code> property with its relative path.
</p>
</div>
<div v-else>No directory selected</div>
</div>
</div>
</template>

<script setup lang="ts">
import {ref} from 'vue'

const files = ref<File[] | null>(null)
</script>
19 changes: 19 additions & 0 deletions apps/docs/src/docs/components/demo/FormFileDirectoryMigration.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<template>
<BFormFile
v-model="files"
directory
/>
</template>

<script setup lang="ts">
import {ref, watch} from 'vue'

const files = ref<File[]>([])

// Access file paths via the webkitRelativePath property
watch(files, (newFiles) => {
newFiles.forEach((file) => {
console.log(file.webkitRelativePath) // e.g., "src/components/Button.vue"
})
})
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Example: After selecting a directory with BFormFile
const files: File[] = [] // Your selected files from v-model

files.forEach((file: File) => {
console.log(file.name) // "helpers.ts"
console.log(file.webkitRelativePath) // "src/utils/helpers.ts"
})
18 changes: 18 additions & 0 deletions apps/docs/src/docs/components/demo/FormFileDropPlaceholder.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<template>
<div>
<BFormFile
v-model="files"
multiple
drop-placeholder="Drop files here..."
no-drop-placeholder="Drag and drop is disabled"
placeholder="Choose files or drag them here"
/>
<div class="mt-3">Selected: {{ files?.length || 0 }} files</div>
</div>
</template>

<script setup lang="ts">
import {ref} from 'vue'
const files = ref<File[] | null>(null)
</script>
29 changes: 29 additions & 0 deletions apps/docs/src/docs/components/demo/FormFileFormatter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<template>
<div>
<BFormFile
v-model="files"
multiple
:file-name-formatter="formatNames"
placeholder="Choose multiple files"
/>
<div class="mt-3">Display: {{ displayText }}</div>
</div>
</template>

<script setup lang="ts">
import {computed, ref} from 'vue'

const files = ref<File[] | null>(null)

const formatNames = (files: readonly File[]): string => {
if (files.length === 1) {
return files[0].name
}
return `${files.length} files selected (${files.map((f) => f.name).join(', ')})`
}

const displayText = computed(() => {
if (!files.value || files.value.length === 0) return 'None'
return formatNames(files.value)
})
</script>
17 changes: 17 additions & 0 deletions apps/docs/src/docs/components/demo/FormFilePlain.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<template>
<div>
<BFormFile
v-model="files"
multiple
plain
placeholder="Choose files"
/>
<div class="mt-3">Selected: {{ files?.length || 0 }} files</div>
</div>
</template>

<script setup lang="ts">
import {ref} from 'vue'

const files = ref<File[] | null>(null)
</script>
Loading
Loading