Files
healthchat-web/packages/shared-components/src/room/composer/UploadButton/UploadButton.tsx
T
Will Hunt 02b6520f09 Module API for adding new file upload mechanisms (#33355)
* Initial reword of upload to MVVM.

* Update tests

* More incremental improvements

* Refactor tests to use helper method for composer uploads.

* Add drag and drop tests

* lint

* Add commentary

* fixup test

* More precise selector

* Retarget uploads

* lint

* fixup

* one more type

* update snap

* Fixup composerUploadFiles

* fix import

* lint

* Copy and paste fixes too

* Add tests for pasting

* Add tests for pasting files.

* Remove redundant fn

* rm comment

* tidy up

* Test cleanup

* More clean up

* another fix

* Begin fleshing out

* Park changes

* More stuff

* Use condensed version

* Cleanup tests

* more cleaning

* last bity

* Add a test for the composer

* Park up changes

* Rewrite Measured to be a functional component

* Add tests to cover narrow viewports

* lint

* breakpoint is optional

* Cleanup

* Support narrow mode

* fixup

* begone

* Provide default value

* add label

* fixup test

* update copyright

* cleanup

* Be a bit more lazy with FileDropTarget

* remove a debug statement

* Fixup

* fix two snaps

* Update screenshot

* and the other one

* Update snaps

* unfake CIDER

* update screens again

* remove extra test

* Undo accidental snapshots

* Bit of tidyup

* fixup

* even more tidyup

* may drag and drop file

* tidy up again

* snap snap snap

* Use load to make sonarQube happy

* Bunch of refactors

* More cleanup

* cleanup debug code

* tweaks

* remove a test we no longer need

* make it happy

* fix import

* fixup

* Update snaps

* typo

* one off

* Add tests

* lint

* remove only

* Reduce screenshot scope

* fix snapshot usage

* cleanup
2026-05-18 21:41:38 +00:00

113 lines
3.2 KiB
TypeScript

/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import React, {
type ReactElement,
type PropsWithChildren,
useState,
type ComponentProps,
type SVGAttributes,
type ComponentType,
useCallback,
type MouseEventHandler,
} from "react";
import { IconButton, Menu, MenuItem } from "@vector-im/compound-web";
import { AttachmentIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { useI18n } from "../../../core/i18n/i18nContext";
import { useViewModel, type ViewModel } from "../../../core/viewmodel";
export interface UploadButtonViewSnapshot {
options: { type: string; label: string; icon?: ComponentType<SVGAttributes<SVGElement>> }[];
}
export interface UploadButtonViewActions {
onUploadOptionSelected(type: string): void;
}
/**
* A composer button to initiate uploading files. The button may also be
* Ctrl+Clicked to pick the first option automatically.
*
* @example
* ```tsx
* <UploadButton vm={} />
* ```
*/
export function UploadButton({
vm,
defaultOpen = false,
...rootButtonProps
}: PropsWithChildren<
{ vm: ViewModel<UploadButtonViewSnapshot, UploadButtonViewActions>; defaultOpen?: boolean } & ComponentProps<
typeof IconButton
>
>): ReactElement {
const i18n = useI18n();
const [open, setOpen] = useState(defaultOpen);
const { options } = useViewModel(vm);
// Ctrl+click is a shortcut to selecting the first item.
const onMenuClick: MouseEventHandler<HTMLButtonElement> = useCallback(
(ev) => {
if (!ev.ctrlKey) {
return;
}
ev.preventDefault();
ev.stopPropagation();
vm.onUploadOptionSelected(options[0].type);
},
[options, vm],
);
if (options.length === 1) {
const { label, icon: Icon } = options[0];
return (
<IconButton
size="26px"
{...rootButtonProps}
tooltip={label}
aria-label={label}
onClick={() => vm.onUploadOptionSelected(options[0].type)}
>
{Icon ? <Icon /> : <AttachmentIcon />}
</IconButton>
);
}
const trigger = (
<IconButton
{...rootButtonProps}
size="26px"
tooltip={i18n.translate("common|attachment")}
onClick={onMenuClick}
title={i18n.translate("common|attachment")}
>
<AttachmentIcon />
</IconButton>
);
return (
<Menu
side="top"
title={i18n.translate("common|attachment")}
showTitle={false}
trigger={trigger}
open={open}
onOpenChange={(o) => setOpen(o)}
>
{options.map((o) => (
<MenuItem
key={o.label}
label={o.label}
Icon={o.icon}
onSelect={() => vm.onUploadOptionSelected(o.type)}
/>
))}
</Menu>
);
}