📁 feat: Integrate SharePoint File Picker and Download Workflow (#8651)

* feat(sharepoint): integrate SharePoint file picker and download workflow
Introduces end‑to‑end SharePoint import support:
* Token exchange with Microsoft Graph and scope management (`useSharePointToken`)
* Re‑usable hooks: `useSharePointPicker`, `useSharePointDownload`,
  `useSharePointFileHandling`
* FileSearch dropdown now offers **From Local Machine** / **From SharePoint**
  sources and gracefully falls back when SharePoint is disabled
* Agent upload model, `AttachFileMenu`, and `DropdownPopup` extended for
  SharePoint files and sub‑menus
* Blurry overlay with progress indicator and `maxSelectionCount` limit during
  downloads
* Cache‑flush utility (`config/flush-cache.js`) supporting Redis & filesystem,
  with dry‑run and npm script
* Updated `SharePointIcon` (uses `currentColor`) and new i18n keys
* Bug fixes: placeholder syntax in progress message, picker event‑listener
  cleanup
* Misc style and performance optimizations

* Fix ESLint warnings

---------

Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com>
This commit is contained in:
Danny Avila 2025-07-25 00:03:23 -04:00
parent 47caafa8f8
commit d786b1b419
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
40 changed files with 2500 additions and 123 deletions

View file

@ -1,4 +1,6 @@
import { useState, useRef } from 'react';
import { Folder } from 'lucide-react';
import * as Ariakit from '@ariakit/react';
import {
EModelEndpoint,
EToolResources,
@ -7,16 +9,21 @@ import {
} from 'librechat-data-provider';
import {
HoverCard,
HoverCardContent,
HoverCardPortal,
HoverCardTrigger,
DropdownPopup,
AttachmentIcon,
CircleHelpIcon,
AttachmentIcon,
CircleHelpIcon,
SharePointIcon,
HoverCardPortal,
HoverCardContent,
HoverCardTrigger,
} from '@librechat/client';
import type { ExtendedFile } from '~/common';
import { useFileHandling, useLocalize, useLazyEffect } from '~/hooks';
import { useFileHandling, useLocalize, useLazyEffect, useSharePointFileHandling } from '~/hooks';
import { useGetFileConfig, useGetStartupConfig } from '~/data-provider';
import { SharePointPickerDialog } from '~/components/SharePoint';
import FileRow from '~/components/Chat/Input/Files/FileRow';
import { useGetFileConfig } from '~/data-provider';
import { useChatContext } from '~/Providers';
import { ESide } from '~/common';
@ -31,6 +38,10 @@ export default function FileContext({
const { setFilesLoading } = useChatContext();
const fileInputRef = useRef<HTMLInputElement>(null);
const [files, setFiles] = useState<Map<string, ExtendedFile>>(new Map());
const [isPopoverActive, setIsPopoverActive] = useState(false);
const [isSharePointDialogOpen, setIsSharePointDialogOpen] = useState(false);
const { data: startupConfig } = useGetStartupConfig();
const sharePointEnabled = startupConfig?.sharePointFilePickerEnabled;
const { data: fileConfig = defaultFileConfig } = useGetFileConfig({
select: (data) => mergeFileConfig(data),
@ -41,7 +52,11 @@ export default function FileContext({
additionalMetadata: { agent_id, tool_resource: EToolResources.ocr },
fileSetter: setFiles,
});
const { handleSharePointFiles, isProcessing, downloadProgress } = useSharePointFileHandling({
overrideEndpoint: EModelEndpoint.agents,
additionalMetadata: { agent_id, tool_resource: EToolResources.file_search },
fileSetter: setFiles,
});
useLazyEffect(
() => {
if (_files) {
@ -54,19 +69,45 @@ export default function FileContext({
const endpointFileConfig = fileConfig.endpoints[EModelEndpoint.agents];
const isUploadDisabled = endpointFileConfig.disabled ?? false;
const handleSharePointFilesSelected = async (sharePointFiles: any[]) => {
try {
await handleSharePointFiles(sharePointFiles);
setIsSharePointDialogOpen(false);
} catch (error) {
console.error('SharePoint file processing error:', error);
}
};
if (isUploadDisabled) {
return null;
}
const handleButtonClick = () => {
const handleLocalFileClick = () => {
// necessary to reset the input
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
fileInputRef.current?.click();
};
const dropdownItems = [
{
label: localize('com_files_upload_local_machine'),
onClick: handleLocalFileClick,
icon: <Folder className="icon-md" />,
},
{
label: localize('com_files_upload_sharepoint'),
onClick: () => setIsSharePointDialogOpen(true),
icon: <SharePointIcon className="icon-md" />,
},
];
const menuTrigger = (
<Ariakit.MenuButton className="btn btn-neutral border-token-border-light relative h-9 w-full rounded-lg font-medium">
<div className="flex w-full items-center justify-center gap-1">
<AttachmentIcon className="text-token-text-primary h-4 w-4" />
{localize('com_ui_upload_file_context')}
</div>
</Ariakit.MenuButton>
);
return (
<div className="w-full">
<HoverCard openDelay={50}>
@ -101,26 +142,42 @@ export default function FileContext({
Wrapper={({ children }) => <div className="flex flex-wrap gap-2">{children}</div>}
/>
<div>
<button
type="button"
disabled={!agent_id}
className="btn btn-neutral border-token-border-light relative h-9 w-full rounded-lg font-medium"
onClick={handleButtonClick}
>
<div className="flex w-full items-center justify-center gap-1">
<AttachmentIcon className="text-token-text-primary h-4 w-4" />
<input
multiple={true}
type="file"
style={{ display: 'none' }}
tabIndex={-1}
ref={fileInputRef}
disabled={!agent_id}
onChange={handleFileChange}
{sharePointEnabled ? (
<>
<DropdownPopup
gutter={2}
menuId="file-search-upload-menu"
isOpen={isPopoverActive}
setIsOpen={setIsPopoverActive}
trigger={menuTrigger}
items={dropdownItems}
modal={true}
unmountOnHide={true}
/>
{localize('com_ui_upload_file_context')}
</div>
</button>
</>
) : (
<button
type="button"
disabled={!agent_id}
className="btn btn-neutral border-token-border-light relative h-9 w-full rounded-lg font-medium"
onClick={handleLocalFileClick}
>
<div className="flex w-full items-center justify-center gap-1">
<AttachmentIcon className="text-token-text-primary h-4 w-4" />
{localize('com_ui_upload_file_context')}
</div>
</button>
)}
<input
multiple={true}
type="file"
style={{ display: 'none' }}
tabIndex={-1}
ref={fileInputRef}
disabled={!agent_id}
onChange={handleFileChange}
/>
</div>
{/* Disabled Message */}
{agent_id ? null : (
@ -129,6 +186,14 @@ export default function FileContext({
</div>
)}
</div>
<SharePointPickerDialog
isOpen={isSharePointDialogOpen}
onOpenChange={setIsSharePointDialogOpen}
onFilesSelected={handleSharePointFilesSelected}
isDownloading={isProcessing}
downloadProgress={downloadProgress}
maxSelectionCount={endpointFileConfig?.fileLimit}
/>
</div>
);
}

View file

@ -1,6 +1,8 @@
import { useState, useRef } from 'react';
import { Folder } from 'lucide-react';
import * as Ariakit from '@ariakit/react';
import { useFormContext } from 'react-hook-form';
import { AttachmentIcon } from '@librechat/client';
import { SharePointIcon, AttachmentIcon, DropdownPopup } from '@librechat/client';
import {
EModelEndpoint,
EToolResources,
@ -9,10 +11,12 @@ import {
fileConfig as defaultFileConfig,
} from 'librechat-data-provider';
import type { ExtendedFile, AgentForm } from '~/common';
import useSharePointFileHandling from '~/hooks/Files/useSharePointFileHandling';
import { useGetFileConfig, useGetStartupConfig } from '~/data-provider';
import { useFileHandling, useLocalize, useLazyEffect } from '~/hooks';
import { SharePointPickerDialog } from '~/components/SharePoint';
import FileRow from '~/components/Chat/Input/Files/FileRow';
import FileSearchCheckbox from './FileSearchCheckbox';
import { useGetFileConfig } from '~/data-provider';
import { useChatContext } from '~/Providers';
export default function FileSearch({
@ -27,6 +31,11 @@ export default function FileSearch({
const { watch } = useFormContext<AgentForm>();
const fileInputRef = useRef<HTMLInputElement>(null);
const [files, setFiles] = useState<Map<string, ExtendedFile>>(new Map());
const [isPopoverActive, setIsPopoverActive] = useState(false);
const [isSharePointDialogOpen, setIsSharePointDialogOpen] = useState(false);
// Get startup configuration for SharePoint feature flag
const { data: startupConfig } = useGetStartupConfig();
const { data: fileConfig = defaultFileConfig } = useGetFileConfig({
select: (data) => mergeFileConfig(data),
@ -38,6 +47,12 @@ export default function FileSearch({
fileSetter: setFiles,
});
const { handleSharePointFiles, isProcessing, downloadProgress } = useSharePointFileHandling({
overrideEndpoint: EModelEndpoint.agents,
additionalMetadata: { agent_id, tool_resource: EToolResources.file_search },
fileSetter: setFiles,
});
useLazyEffect(
() => {
if (_files) {
@ -53,6 +68,17 @@ export default function FileSearch({
const endpointFileConfig = fileConfig.endpoints[EModelEndpoint.agents];
const isUploadDisabled = endpointFileConfig.disabled ?? false;
const sharePointEnabled = startupConfig?.sharePointFilePickerEnabled;
const disabledUploadButton = !agent_id || fileSearchChecked === false;
const handleSharePointFilesSelected = async (sharePointFiles: any[]) => {
try {
await handleSharePointFiles(sharePointFiles);
setIsSharePointDialogOpen(false);
} catch (error) {
console.error('SharePoint file processing error:', error);
}
};
if (isUploadDisabled) {
return null;
}
@ -65,6 +91,38 @@ export default function FileSearch({
fileInputRef.current?.click();
};
const handleLocalFileClick = () => {
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
fileInputRef.current?.click();
};
const dropdownItems = [
{
label: localize('com_files_upload_local_machine'),
onClick: handleLocalFileClick,
icon: <Folder className="icon-md" />,
},
{
label: localize('com_files_upload_sharepoint'),
onClick: () => setIsSharePointDialogOpen(true),
icon: <SharePointIcon className="icon-md" />,
},
];
const menuTrigger = (
<Ariakit.MenuButton
disabled={disabledUploadButton}
className="btn btn-neutral border-token-border-light relative h-9 w-full rounded-lg font-medium"
>
<div className="flex w-full items-center justify-center gap-1">
<AttachmentIcon className="text-token-text-primary h-4 w-4" />
{localize('com_ui_upload_file_search')}
</div>
</Ariakit.MenuButton>
);
return (
<div className="w-full">
<div className="mb-1.5 flex items-center gap-2">
@ -86,26 +144,39 @@ export default function FileSearch({
Wrapper={({ children }) => <div className="flex flex-wrap gap-2">{children}</div>}
/>
<div>
<button
type="button"
disabled={!agent_id || fileSearchChecked === false}
className="btn btn-neutral border-token-border-light relative h-9 w-full rounded-lg font-medium"
onClick={handleButtonClick}
>
<div className="flex w-full items-center justify-center gap-1">
<AttachmentIcon className="text-token-text-primary h-4 w-4" />
<input
multiple={true}
type="file"
style={{ display: 'none' }}
tabIndex={-1}
ref={fileInputRef}
disabled={!agent_id || fileSearchChecked === false}
onChange={handleFileChange}
/>
{localize('com_ui_upload_file_search')}
</div>
</button>
{sharePointEnabled ? (
<DropdownPopup
gutter={2}
menuId="file-search-upload-menu"
isOpen={isPopoverActive}
setIsOpen={setIsPopoverActive}
trigger={menuTrigger}
items={dropdownItems}
modal={true}
unmountOnHide={true}
/>
) : (
<button
type="button"
disabled={disabledUploadButton}
className="btn btn-neutral border-token-border-light relative h-9 w-full rounded-lg font-medium"
onClick={handleButtonClick}
>
<div className="flex w-full items-center justify-center gap-1">
<AttachmentIcon className="text-token-text-primary h-4 w-4" />
{localize('com_ui_upload_file_search')}
</div>
</button>
)}
<input
multiple={true}
type="file"
style={{ display: 'none' }}
tabIndex={-1}
ref={fileInputRef}
disabled={disabledUploadButton}
onChange={handleFileChange}
/>
</div>
{/* Disabled Message */}
{agent_id ? null : (
@ -114,6 +185,16 @@ export default function FileSearch({
</div>
)}
</div>
<SharePointPickerDialog
isOpen={isSharePointDialogOpen}
onOpenChange={setIsSharePointDialogOpen}
onFilesSelected={handleSharePointFilesSelected}
disabled={disabledUploadButton}
isDownloading={isProcessing}
downloadProgress={downloadProgress}
maxSelectionCount={endpointFileConfig?.fileLimit}
/>
</div>
);
}

View file

@ -0,0 +1,640 @@
/* eslint-disable @typescript-eslint/no-empty-object-type */
export type ExtFilters =
| 'folder'
| 'site'
| 'documentLibrary'
| 'list'
| 'onenote'
| 'file'
| 'media'
| 'photo'
| 'video'
| 'audio'
| 'document'
| 'listItem'
| 'playlist'
| 'syntexTemplate'
| 'syntexSnippet'
| 'syntexField'
| `.${string}`;
//NOTE: IItem type references the following docs: https://learn.microsoft.com/en-us/graph/api/resources/driveitem?view=graph-rest-1.0#properties
export type IItem = Record<string, any>;
export type SPPickerConfig = {
sdk: '8.0';
/**
* Establishes the messaging parameters used to setup the post message communications between
* picker and host application
*/
messaging: {
/**
* A unique id assigned by the host app to this File Picker instance.
* This should ideally be a new GUID generated by the host.
*/
channelId: string;
/**
* The host app's authority, used as the target origin for post-messaging.
*/
origin: string;
/**
* Whether or not the host app window will need to identify itself.
*/
identifyParent?: boolean;
/**
* Whether or not the client app must wait for a 'configure' command to be sent by the host before rendering.
*/
waitForConfiguration?: boolean;
/**
* Override timeout for acknowledgement messages.
*/
acknowledgeTimeout?: number;
/**
* Override timeout for the initialization handshake.
*/
initializeTimeout?: number;
/**
* Override timeout for command responses.
*/
resultTimeout?: number;
};
/**
* Configuration for the entry location to which the File Picker will navigate on load.
* The File Picker app will prioritize path-based navigation if provided, falling back to other address forms
* on error (in case of Site redirection or content rename) or if path information is not provided.
*/
entry: {
sharePoint?: {
/**
* Specify an exact SharePoint content location by path segments.
*/
byPath?: {
/**
* Full URL to the root of a Web, or server-relative URL.
* @example
* 'https://contoso-my.sharepoint.com/personal/user_contoso_com'
* @example
* '/path/to/web'
* @example
* 'subweb'
*/
web?: string;
/**
* Full URL or path segement to identity a List.
* If not preceded with a `/` or a URL scheme, this is assumed to be a list in the specified web.
* @example
* 'Shared Documents'
* @example
* '/path/to/web/Shared Documents'
* @example
* 'https://contoso.sharepoint.com/path/to/web/Shared Documents'
*/
list?: string;
/**
* Path segment to a folder within a list, or a server-relative URL to a folder.
* @example
* 'General'
* @example
* 'foo/bar'
* @example
* '/path/to/web/Shared Documents/General'
*/
folder?: string;
/**
* Auto fallback to root folder if the specified entry sub folder doesn't exist.
*/
fallbackToRoot?: boolean;
};
};
/**
* Indicates that File Picker should start in the Site Pivot
* This pivot is only supported in OneDrive for Business
*/
site?: {};
/**
* Indicates that File Picker should start in the OAL (My Organization) Pivot
* This pivot is only supported in OneDrive for Business
*/
myOrganization?: {};
/**
* Indicates that the File Picker should start in the user's OneDrive.
*/
oneDrive?: {
/**
* Specifies that File Picker should start in the user's Files tab.
*/
files?: {
/**
* Path segment for sub-folder within the user's OneDrive for Business.
* @example
* 'Pictures'
* @example
* '/personal/user_contoso_com/Documents/Attachments'
*/
folder?: string;
/**
* Auto fallback to root folder if the specified entry sub folder doesn't exist.
*/
fallbackToRoot?: boolean;
};
/**
* Indicates that File Picker should start in the user's recent files.
*/
recent?: {};
/**
* Indicates that File Picker should start in the files shared with the user.
*/
sharedWithMe?: {};
/**
* Indicates that File Picker should start in the user's photos.
* This pivot is only available in OneDrive for Consumer
*/
photos?: {};
};
sortBy?: {
/**
* Name of the field *in SharePoint* on which to sort.
*/
fieldName: string;
/**
* Whether or not to sort in ascending order. Default is `true`.
*/
isAscending?: boolean;
};
filterBy?: {
/**
* Name of the field *in SharePoint* on which to filter on.
*/
fieldName: string;
/**
* Filter value
*/
value: string;
};
};
/**
* Specifies how to enable a Search behavior.
*/
search?: {
enabled: boolean;
};
/**
* Configuration for handling authentication requests from the embedded app.
* Presence of this object (even if empty) indicates that the host will handle authentication.
* Omitting this will make the embedded content attempt to rely on cookies.
*/
authentication?: {
/**
* @default true
*/
enabled?: boolean;
/**
* Indicates support for individual token types.
*/
tokens?: {
/**
* @defaultValue true
*/
graph?: boolean;
/**
* @defaultValue true
*/
sharePoint?: boolean;
/**
* @defaultValue false
*/
substrate?: boolean;
};
/**
* Indicates that the host app can handle 'claims' challenges.
*/
claimsChallenge?: {
/**
* @default false
*/
enabled?: boolean;
};
};
/**
* Configures what types of items are allowed to be picked within the experience.
* Note that the default configuration accounts for the expected authentication capabilities of the host app.
* Depending on what else is enabled by the host, the host may be expected to provide tokens for more services and scopes.
*/
typesAndSources?: {
/**
* Specifies the general category of items picked. Switches between 'file' vs. 'folder' picker mode,
* or a general-purpose picker.
* @default 'all'
*/
mode?: 'files' | 'folders' | 'all';
/**
* `filters` options: file extension, i.e. .xlsx, .docx, .ppt, etc.
* `filters` options: 'photo', 'folder', 'video', 'documentLibrary'
*/
filters?: ExtFilters[];
/**
* Specifies a filter for *where* the item may come from.
*/
locations?: {
/**
* Items may only come from the user's OneDrive.
*/
oneDrive?: {};
/**
* Items may only come from a specific location within SharePoint.
*/
sharePoint?: {
byPath?: {
web?: string;
list?: string;
folder?: string;
};
};
};
/**
* Specifies filtering based on user access level.
*/
access?: {
/**
* Filter for requires user access level for picked items. Default is `'read'`.
*/
mode?: 'read' | 'read-write';
};
/**
* Specifies which pivots the user may access while browsing files and lists.
* Note that if a pivot is disabled here but still targeted in `entry`, it will still be visible in the nav.
*/
pivots?: {
/**
* Show "My files".
*/
oneDrive?: boolean;
/**
* Show "Recent".
*/
recent?: boolean;
/**
* Show "Shared"
*/
shared?: boolean;
/**
* Show "Quick access".
*/
sharedLibraries?: boolean;
/**
* Show "My organization".
* This pivot is only supported in OneDrive for Business
*/
myOrganization?: boolean;
/**
* Show the site pivot
* This pivot is only supported in OneDrive for Business
*/
site?: boolean;
};
};
/**
* Configuration for what item types may be selected within the picker and returned to the host.
*/
selection?: {
/**
* Controls how selection works within the list.
* @default 'single' for the Picker.
*/
mode?: 'single' | 'multiple' | 'pick';
/**
* Whether or not to allow the user to maintain a selection across folders and pivots.
*/
enablePersistence?: boolean;
/**
* Whether or not the host expects to be notified whenever selection changes.
*/
enableNotifications?: boolean;
/**
* The maximum number of items which may be selected.
*/
maximumCount?: number;
/**
* A set of items to pre-select.
*/
sourceItems?: IItem[];
};
/**
* Configures how commands behave within the experience.
*/
commands?: {
/**
* Specifies the behavior for file-picking.
*/
pick?: {
/**
* A special action to perform when picking the file, before handing the result
* back to the host app.
*/
action?: 'select' | 'share' | 'download' | 'move';
/**
* A custom label to apply to the button which picks files.
* This must be localized by the host app if supplied.
*/
label?: string;
/**
* Configures the 'move' action for picking files.
*/
move?: {
sourceItems?: IItem[];
};
/**
* Configures the 'copy' action for picking files.
*/
copy?: {
sourceItems?: IItem[];
};
/**
* Configures the 'select' action for picking files.
*/
select?: {
/**
* Specify if we want download urls to be returned when items are selected.
*/
urls?: {
download?: boolean;
};
};
};
/**
* Specifies the behavior for closing the experience.
*/
close?: {
/**
* A custom label to apply to the 'cancel' button.
* This must be localized by the host app if supplied.
*/
label?: string;
};
/**
* Behavior for a "Browse this device" command to pick local files.
*/
browseThisDevice?: {
enabled?: boolean;
label?: string;
mode?: 'upload' | 'pick';
};
/**
* Behavior for a "From a link" command to pick from a link.
*/
fromALink?: {
enabled?: boolean;
mode?: 'nav' | 'pivot';
};
/**
* Behavior for a "Switch account" command.
*/
switchAccount?: {
mode?: 'host' | 'none';
};
/**
* Behavior for a "Manage accounts" command.
*/
manageAccounts?: {
mode?: 'host' | 'none';
label?: string;
};
/**
* Behavior for "Upload"
*/
upload?: {
enabled?: boolean;
};
/**
* Behavior for "Create folder"
*/
createFolder?: {
enabled?: boolean;
};
/**
* Behavior for "Filter by" in the column headers.
*/
filterByColumn?: {
mode?: 'panel' | 'menu';
};
/**
* How to handle actions defined by custom formatters.
*/
customFormatter?: {
actions?: {
key: string;
mode?: 'host' | 'none';
}[];
};
/**
* How to handle specified values for `key` in custom commands
* in the tray, nav, or command bar.
*/
custom?: {
actions?: {
key: string;
/**
* Filters defining what types of items the action operates on.
* If specified, the action will only be available for items which match the given filters.
*/
filters?: ExtFilters[];
/**
* How the action is invoked.
* 'host': Invokes a `custom` command message against the host app.
* 'none': Disables the action.
*/
mode?: 'host' | 'none';
/**
* Selection criteria to which the item applies.
*/
selection?: 'single' | 'multiple' | 'current' | 'none';
}[];
};
};
/**
* Specifies accessibility cues such as auto-focus behaviors.
*/
accessibility?: {
/**
* Whether or not to 'trap focus' within the component. If this is enabled, tab-stops will loop from the last element back to the left navigation automatically.
* This is useful if the components's frame is hosted as the only content of a modal overlay and focus should not jump to the outside content.
*
* @default false
*/
enableFocusTrap?: boolean;
/**
* Whether or not the component should immediately grab focus once the content has loaded.
*
* @default true
*/
trapFocusOnLoad?: boolean;
/**
* Whether or not to force the currently-focused element within the component to be highlighted.
* By default, the focused element is highlighted if the user navigates elements with the keyboard but not when the user interacts via the mouse.
* However, if a host application launches the component due to keyboard input it should set this flag to `true` to ensure continuity of behavior.
*
* @default false
*/
showFocusOnLoad?: boolean;
};
tray?: {
/**
* Configures the commands normally used to pick files or close the picker.
*/
commands?: {
/**
* A key to differentiate the command from others.
*/
key: string;
/**
* A custom string for the command.
* Must be localized by the host.
*/
label?: string;
/**
* The action to perform when the button is clicked.
*/
action: 'pick' | 'close' | 'custom';
/**
* If `'pick'` is specified, which pick behavior to use.
*/
pick?: {
action: 'select' | 'share' | 'download' | 'move';
};
/**
* Whether the button should show as the primary button.
*/
primary?: boolean;
/**
* Whether the button should remain visible at all times even if unavailable.
*/
permanent?: boolean;
}[];
/**
* Whether or not the picker tray might be provided by the host instead.
* @defaultValue 'default'
*/
mode?: 'host' | 'default';
/**
* Configures a component to render in the picker tray to the left of the commands.
* @default 'selection-summary'
*/
prompt?: 'keep-sharing' | 'selection-summary' | 'selection-editor' | 'save-as' | 'none';
/**
* Configures use of the 'save-as' prompt.
*/
saveAs?: {
/**
* Default file name to show in 'save-as' prompt.
*/
fileName?: string;
};
/**
* Settings for handling conflicts with existing file names.
*/
conflicts?: {
/**
* How to handle when a file name matches an existing file.
* `'warn'` - Show a prompt to ask the user to confirm the choice.
* `'block'` - Block the choice as an error.
* `'accept'` - Accept the choice automatically.
* `'none'` - Do not try to match with existing items.
*/
mode?: 'warn' | 'block' | 'accept' | 'none';
};
/**
* Configures use of the 'keep-sharing' prompt.
*/
keepSharing?: {
active?: boolean;
};
};
leftNav?: {
/**
* Whether or not a Left Nav should be rendered by the embedded content.
*/
enabled?: boolean;
/**
* Mode of presentation of the nav.
* If the nav is enabled but this is set to `host`, the embedded app
* will show a button to ask the host app to show a nav.
*/
mode?: 'host' | 'default';
/**
* Indicates whether the left nav will be initially modal.
*/
initialModality?: 'modal' | 'hidden';
/**
* Type of left nav
*/
preset?: 'oneDrive' | 'current-site';
/**
* Custom commands to insert at the end of the left nav. Will appear before the default set.
*/
commands?: {
/**
* Name to use when notifying the host that the command is being invoked.
*/
key: string;
/**
* Localized string to use for the button text.
*/
label: string;
/**
* Type of action which will be performed when the command is clicked.
* 'custom': Configured via `commands.custom`.
*/
action: 'custom' | 'pick' | 'close' | 'browse-this-device';
/**
* Name of a Fluent icon to use for the command button.
*/
icon?: string;
}[];
};
/**
* The theme to use for the file-picker. Will change the coloring.
* Note: custom theme objects are expected in addition to the strings below
* @default 'default': Light theme
*/
theme?: 'default' | 'dark' | 'lists';
list?: {
/**
* A custom override for the initial list layout.
*/
layout?: {
/**
* Sets the preferred starting layout for the initial content.
*/
type?: 'details' | 'compact-details' | 'tiles';
};
/**
* Configures scrolling behavior within the Picker.
*/
scrolling?: {
enableStickyHeaders?: boolean;
};
};
/**
* Provides a header title for the Picker.
*/
title?: string;
/**
* Specifies customizations for specific pivots
*/
pivots?: {
/**
* Customize the site pivot
*/
site?: {
byPath?: {
/**
* Chose the site url to use for this pivot
* Required to show the site pivot, if undefined
* the site pivot will not be shown
*/
web?: string;
};
};
};
};