animating height for attachments

This commit is contained in:
Roland Osborne 2024-12-16 10:29:15 -08:00
parent 968bf983ee
commit 9f78c7b54b
4 changed files with 80 additions and 62 deletions

View File

@ -27,6 +27,7 @@
"jest": "29.1.1", "jest": "29.1.1",
"jsencrypt": "^3.3.2", "jsencrypt": "^3.3.2",
"react": "18.3.1", "react": "18.3.1",
"react-animate-height": "^3.2.3",
"react-color": "^2.19.3", "react-color": "^2.19.3",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-easy-crop": "^5.0.8", "react-easy-crop": "^5.0.8",

View File

@ -75,12 +75,20 @@
} }
.add { .add {
position: relative;
overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex-shrink: 0; flex-shrink: 0;
width: 100%; width: 100%;
} }
.staging {
position: absolute;
bottom: 0;
width: 100%;
}
.close { .close {
cursor: pointer; cursor: pointer;
padding-top: 2px; padding-top: 2px;

View File

@ -11,6 +11,8 @@ import { VideoFile } from './videoFile/VideoFile';
import { AudioFile } from './audioFile/AudioFile'; import { AudioFile } from './audioFile/AudioFile';
import { BinaryFile } from './binaryFile/BinaryFile'; import { BinaryFile } from './binaryFile/BinaryFile';
import { SketchPicker } from "react-color"; import { SketchPicker } from "react-color";
import AnimateHeight from 'react-animate-height';
import { useResizeDetector } from 'react-resize-detector';
const PAD_HEIGHT = (1024 - 64); const PAD_HEIGHT = (1024 - 64);
const LOAD_DEBOUNCE = 1000; const LOAD_DEBOUNCE = 1000;
@ -33,6 +35,7 @@ export function Conversation() {
const attachVideo = useRef({ click: ()=>{} } as HTMLInputElement); const attachVideo = useRef({ click: ()=>{} } as HTMLInputElement);
const attachAudio = useRef({ click: ()=>{} } as HTMLInputElement); const attachAudio = useRef({ click: ()=>{} } as HTMLInputElement);
const attachBinary = useRef({ click: ()=>{} } as HTMLInputElement); const attachBinary = useRef({ click: ()=>{} } as HTMLInputElement);
const { width, height, ref } = useResizeDetector();
const addImage = (image: File | undefined) => { const addImage = (image: File | undefined) => {
if (image) { if (image) {
@ -141,8 +144,7 @@ export function Conversation() {
} }
}); });
console.log("SEND? ", sending, state.progress); console.log("HIIGHT: ", height);
return ( return (
<div className={classes.conversation}> <div className={classes.conversation}>
@ -207,68 +209,70 @@ console.log("SEND? ", sending, state.progress);
<div className={classes.progress} style={{ width: `${state.progress}%` }}/> <div className={classes.progress} style={{ width: `${state.progress}%` }}/>
)} )}
</div> </div>
<div className={classes.add}> <AnimateHeight className={classes.add} duration={500} height={height}>
<input type='file' name="asset" accept="image/*" ref={attachImage} onChange={e => addImage(e.target?.files?.[0])} style={{display: 'none'}}/> <div ref={ref} className={classes.staging}>
<input type='file' name="asset" accept="video/*" ref={attachVideo} onChange={e => addVideo(e.target?.files?.[0])} style={{display: 'none'}}/> <input type='file' name="asset" accept="image/*" ref={attachImage} onChange={e => addImage(e.target?.files?.[0])} style={{display: 'none'}}/>
<input type='file' name="asset" accept="audio/*" ref={attachAudio} onChange={e => addAudio(e.target?.files?.[0])} style={{display: 'none'}}/> <input type='file' name="asset" accept="video/*" ref={attachVideo} onChange={e => addVideo(e.target?.files?.[0])} style={{display: 'none'}}/>
<input type='file' name="asset" accept="*/*" ref={attachBinary} onChange={e => addBinary(e.target?.files?.[0])} style={{display: 'none'}}/> <input type='file' name="asset" accept="audio/*" ref={attachAudio} onChange={e => addAudio(e.target?.files?.[0])} style={{display: 'none'}}/>
<div className={classes.files}> <input type='file' name="asset" accept="*/*" ref={attachBinary} onChange={e => addBinary(e.target?.files?.[0])} style={{display: 'none'}}/>
{ media } <div className={classes.files}>
</div> { media }
<Textarea className={classes.message} placeholder={state.strings.newMessage} styles={{ input: {color: state.textColorSet ? state.textColor : undefined, fontSize: state.textSizeSet ? state.textSize : undefined }}} value={state.message} onChange={(event) => actions.setMessage(event.currentTarget.value)} disabled={!state.detail || state.detail.locked || sending} onKeyDown={(e) => { console.log(e); keyDown(e.key, e.shiftKey)}} /> </div>
<div className={classes.controls}> <Textarea className={classes.message} placeholder={state.strings.newMessage} styles={{ input: {color: state.textColorSet ? state.textColor : undefined, fontSize: state.textSizeSet ? state.textSize : undefined }}} value={state.message} onChange={(event) => actions.setMessage(event.currentTarget.value)} disabled={!state.detail || state.detail.locked || sending} onKeyDown={(e) => { console.log(e); keyDown(e.key, e.shiftKey)}} />
<ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked || sending} onClick={() => attachImage.current.click()}> <div className={classes.controls}>
<IconCamera /> <ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked || sending} onClick={() => attachImage.current.click()}>
</ActionIcon> <IconCamera />
<ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked || sending} onClick={() => attachVideo.current.click()}> </ActionIcon>
<IconVideo /> <ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked || sending} onClick={() => attachVideo.current.click()}>
</ActionIcon> <IconVideo />
<ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked || sending} onClick={() => attachAudio.current.click()}> </ActionIcon>
<IconDisc /> <ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked || sending} onClick={() => attachAudio.current.click()}>
</ActionIcon> <IconDisc />
<ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked || sending} onClick={() => attachBinary.current.click()}> </ActionIcon>
<IconFile /> <ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked || sending} onClick={() => attachBinary.current.click()}>
</ActionIcon> <IconFile />
<Divider size="sm" orientation="vertical" /> </ActionIcon>
<Menu shadow="md" position="top"> <Divider size="sm" orientation="vertical" />
<Menu.Target> <Menu shadow="md" position="top">
<ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked || sending}> <Menu.Target>
<IconTextSize /> <ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked || sending}>
</ActionIcon> <IconTextSize />
</Menu.Target> </ActionIcon>
<Menu.Dropdown> </Menu.Target>
<Menu.Item onClick={() => actions.setTextSize(12)}> <Menu.Dropdown>
{ state.strings.textSmall } <Menu.Item onClick={() => actions.setTextSize(12)}>
</Menu.Item> { state.strings.textSmall }
<Menu.Item onClick={() => actions.setTextSize(16)}> </Menu.Item>
{ state.strings.textMedium } <Menu.Item onClick={() => actions.setTextSize(16)}>
</Menu.Item> { state.strings.textMedium }
<Menu.Item onClick={() => actions.setTextSize(20)}> </Menu.Item>
{ state.strings.textLarge } <Menu.Item onClick={() => actions.setTextSize(20)}>
</Menu.Item> { state.strings.textLarge }
</Menu.Dropdown> </Menu.Item>
</Menu> </Menu.Dropdown>
<Menu shadow="md" position="top"> </Menu>
<Menu.Target> <Menu shadow="md" position="top">
<ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked || sending}> <Menu.Target>
<IconTextColor /> <ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked || sending}>
</ActionIcon> <IconTextColor />
</Menu.Target> </ActionIcon>
<Menu.Dropdown> </Menu.Target>
<SketchPicker disableAlpha={true} <Menu.Dropdown>
color={state.textColor} <SketchPicker disableAlpha={true}
onChange={(color) => { color={state.textColor}
actions.setTextColor(color.hex); onChange={(color) => {
}} /> actions.setTextColor(color.hex);
</Menu.Dropdown> }} />
</Menu> </Menu.Dropdown>
<div className={classes.send}> </Menu>
<ActionIcon className={classes.attach} variant="light" disabled={(!state.message && state.assets.length === 0) || !state.detail || state.detail.locked || sending} onClick={sendMessage} loading={sending}> <div className={classes.send}>
<IconSend /> <ActionIcon className={classes.attach} variant="light" disabled={(!state.message && state.assets.length === 0) || !state.detail || state.detail.locked || sending} onClick={sendMessage} loading={sending}>
</ActionIcon> <IconSend />
</ActionIcon>
</div>
</div> </div>
</div>
</div> </div>
</AnimateHeight>
</div> </div>
); );
} }

View File

@ -4245,6 +4245,11 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
react-animate-height@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/react-animate-height/-/react-animate-height-3.2.3.tgz#90929aadac1bd1851cb6a685acc105b50ccfda8c"
integrity sha512-R6DSvr7ud07oeCixScyvXWEMJY/Mt2+GyOWC1KMaRc69gOBw+SsCg4TJmrp4rKUM1hyd6p+YKw90brjPH93Y2A==
react-color@^2.19.3: react-color@^2.19.3:
version "2.19.3" version "2.19.3"
resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.19.3.tgz#ec6c6b4568312a3c6a18420ab0472e146aa5683d" resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.19.3.tgz#ec6c6b4568312a3c6a18420ab0472e146aa5683d"