mirror of
https://github.com/cheeaun/phanpy.git
synced 2025-02-24 08:48:47 +01:00
Adjustments to composer footer buttons
- Make it one-liner - Make the add-action buttons scrollable - Introduce 'Add' button that shows a menu of the actions to allow more actions in the future
This commit is contained in:
parent
b6d1522480
commit
28bdd9a0fa
2 changed files with 335 additions and 115 deletions
|
@ -20,11 +20,15 @@
|
|||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
padding: 8px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
white-space: nowrap;
|
||||
|
||||
@media (min-width: 480px) {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
#compose-container .compose-top .account-block {
|
||||
text-align: start;
|
||||
|
@ -110,10 +114,10 @@
|
|||
}
|
||||
|
||||
#compose-container form {
|
||||
--form-padding-inline: 8px;
|
||||
--form-padding-block: 0;
|
||||
--form-spacing-inline: 4px;
|
||||
--form-spacing-block: 0;
|
||||
/* border-radius: 16px; */
|
||||
padding: var(--form-padding-block) var(--form-padding-inline);
|
||||
padding: var(--form-spacing-block) var(--form-spacing-inline);
|
||||
background-color: var(--bg-blur-color);
|
||||
/* background-image: linear-gradient(var(--bg-color) 85%, transparent); */
|
||||
position: relative;
|
||||
|
@ -121,6 +125,10 @@
|
|||
--drop-shadow: 0 3px 6px -3px var(--drop-shadow-color);
|
||||
box-shadow: var(--drop-shadow);
|
||||
|
||||
@media (min-width: 480px) {
|
||||
--form-spacing-inline: 8px;
|
||||
}
|
||||
|
||||
@media (min-width: 40em) {
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
@ -153,8 +161,8 @@
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
gap: 8px;
|
||||
padding: var(--form-spacing-inline) 0;
|
||||
gap: var(--form-spacing-inline);
|
||||
}
|
||||
#compose-container .toolbar.wrap {
|
||||
flex-wrap: wrap;
|
||||
|
@ -181,6 +189,11 @@
|
|||
white-space: nowrap;
|
||||
border: 2px solid transparent;
|
||||
vertical-align: middle;
|
||||
|
||||
&.active {
|
||||
filter: brightness(0.8);
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
}
|
||||
#compose-container .toolbar-button > * {
|
||||
vertical-align: middle;
|
||||
|
@ -248,6 +261,38 @@
|
|||
max-width: 100%;
|
||||
}
|
||||
|
||||
#compose-container .compose-footer {
|
||||
.add-toolbar-button-group {
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
}
|
||||
.add-sub-toolbar-button-group {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
transition: 0.5s ease-in-out;
|
||||
transition-property: opacity, width;
|
||||
scrollbar-width: none;
|
||||
padding-inline-end: 16px;
|
||||
mask-image: linear-gradient(
|
||||
var(--to-backward),
|
||||
transparent 0,
|
||||
black 16px,
|
||||
black 100%
|
||||
);
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&[hidden] {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#compose-container text-expander {
|
||||
position: relative;
|
||||
display: block;
|
||||
|
@ -516,6 +561,37 @@
|
|||
color: var(--red-color);
|
||||
}
|
||||
|
||||
.compose-menu-add-media {
|
||||
position: relative;
|
||||
|
||||
.compose-menu-add-media-field {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
cursor: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-gif {
|
||||
display: inline-block !important;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
font-size: 10px !important;
|
||||
letter-spacing: -0.5px;
|
||||
font-size-adjust: none;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
line-height: 16px;
|
||||
font-weight: bold;
|
||||
text-rendering: optimizeSpeed;
|
||||
|
||||
&:after {
|
||||
display: block;
|
||||
content: 'GIF';
|
||||
}
|
||||
}
|
||||
|
||||
@media (display-mode: standalone) {
|
||||
/* No popping in standalone mode */
|
||||
#compose-container .pop-button {
|
||||
|
@ -525,8 +601,10 @@
|
|||
|
||||
#compose-container button[type='submit'] {
|
||||
border-radius: 8px;
|
||||
|
||||
@media (min-width: 480px) {
|
||||
padding-inline: 24px;
|
||||
font-size: 125%;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -820,8 +898,8 @@
|
|||
.compose-field-container {
|
||||
display: grid !important;
|
||||
|
||||
@media (width < 30em) {
|
||||
margin-inline: calc(-1 * var(--form-padding-inline));
|
||||
@media (width < 480px) {
|
||||
margin-inline: calc(-1 * var(--form-spacing-inline));
|
||||
width: 100vw !important;
|
||||
max-width: 100vw;
|
||||
|
||||
|
@ -929,15 +1007,55 @@
|
|||
}
|
||||
}
|
||||
|
||||
@keyframes jump-scare {
|
||||
from {
|
||||
opacity: 0.5;
|
||||
transform: scale(0.25) translateX(80px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateX(0);
|
||||
}
|
||||
}
|
||||
@keyframes jump-scare-rtl {
|
||||
from {
|
||||
opacity: 0.5;
|
||||
transform: scale(0.25) translateX(-80px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.add-button {
|
||||
transform-origin: var(--forward) center;
|
||||
background-color: var(--bg-blur-color) !important;
|
||||
animation: jump-scare 0.2s ease-in-out both;
|
||||
:dir(rtl) & {
|
||||
animation-name: jump-scare-rtl;
|
||||
}
|
||||
|
||||
.icon {
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
&.active {
|
||||
.icon {
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gif-picker-button {
|
||||
span {
|
||||
/* span {
|
||||
font-weight: bold;
|
||||
font-size: 11.5px;
|
||||
display: block;
|
||||
}
|
||||
line-height: 1;
|
||||
} */
|
||||
|
||||
&:is(:hover, :focus) {
|
||||
span {
|
||||
.icon {
|
||||
animation: gif-shake 0.3s 3;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import stringLength from 'string-length';
|
|||
// import { detectAll } from 'tinyld/light';
|
||||
import { uid } from 'uid/single';
|
||||
import { useDebouncedCallback, useThrottledCallback } from 'use-debounce';
|
||||
import useResizeObserver from 'use-resize-observer';
|
||||
import { useSnapshot } from 'valtio';
|
||||
|
||||
import poweredByGiphyURL from '../assets/powered-by-giphy.svg';
|
||||
|
@ -201,6 +202,13 @@ const LF = mem((locale) => new Intl.ListFormat(locale || undefined));
|
|||
|
||||
const CUSTOM_EMOJIS_COUNT = 100;
|
||||
|
||||
const ADD_LABELS = {
|
||||
media: msg`Add media`,
|
||||
customEmoji: msg`Add custom emoji`,
|
||||
gif: msg`Add GIF`,
|
||||
poll: msg`Add poll`,
|
||||
};
|
||||
|
||||
function Compose({
|
||||
onClose,
|
||||
replyToStatus,
|
||||
|
@ -209,7 +217,7 @@ function Compose({
|
|||
standalone,
|
||||
hasOpener,
|
||||
}) {
|
||||
const { i18n } = useLingui();
|
||||
const { i18n, _ } = useLingui();
|
||||
const rtf = RTF(i18n.locale);
|
||||
const lf = LF(i18n.locale);
|
||||
|
||||
|
@ -732,6 +740,39 @@ function Compose({
|
|||
states.composerState.minimized = true;
|
||||
};
|
||||
|
||||
const gifPickerDisabled =
|
||||
uiState === 'loading' ||
|
||||
(maxMediaAttachments !== undefined &&
|
||||
mediaAttachments.length >= maxMediaAttachments) ||
|
||||
!!poll;
|
||||
|
||||
// If maxOptions is not defined or defined and is greater than 1, show poll button
|
||||
const showPollButton = maxOptions == null || maxOptions > 1;
|
||||
const pollButtonDisabled =
|
||||
uiState === 'loading' || !!poll || !!mediaAttachments.length;
|
||||
const onPollButtonClick = () => {
|
||||
setPoll({
|
||||
options: ['', ''],
|
||||
expiresIn: 24 * 60 * 60, // 1 day
|
||||
multiple: false,
|
||||
});
|
||||
};
|
||||
|
||||
const addSubToolbarRef = useRef();
|
||||
const [showAddButton, setShowAddButton] = useState(false);
|
||||
useResizeObserver({
|
||||
ref: addSubToolbarRef,
|
||||
box: 'border-box',
|
||||
onResize: ({ width }) => {
|
||||
// If scrollable, it's truncated
|
||||
const { scrollWidth } = addSubToolbarRef.current;
|
||||
const truncated = scrollWidth > width;
|
||||
const overTruncated = width < 84; // roughly two buttons width
|
||||
setShowAddButton(overTruncated || truncated);
|
||||
addSubToolbarRef.current.hidden = overTruncated;
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div id="compose-container-outer">
|
||||
<div id="compose-container" class={standalone ? 'standalone' : ''}>
|
||||
|
@ -1318,86 +1359,87 @@ function Compose({
|
|||
}}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
class="toolbar wrap"
|
||||
style={{
|
||||
justifyContent: 'flex-end',
|
||||
<div class="toolbar compose-footer">
|
||||
<span class="add-toolbar-button-group spacer">
|
||||
{showAddButton && (
|
||||
<Menu2
|
||||
portal={{
|
||||
target: document.body,
|
||||
}}
|
||||
containerProps={{
|
||||
style: {
|
||||
zIndex: 1001,
|
||||
},
|
||||
}}
|
||||
menuButton={({ open }) => (
|
||||
<button
|
||||
class={`toolbar-button add-button ${
|
||||
open ? 'active' : ''
|
||||
}`}
|
||||
>
|
||||
<span>
|
||||
<label class="toolbar-button">
|
||||
<input
|
||||
type="file"
|
||||
accept={supportedMimeTypes?.join(',')}
|
||||
multiple={
|
||||
maxMediaAttachments === undefined ||
|
||||
maxMediaAttachments - mediaAttachments >= 2
|
||||
}
|
||||
<Icon icon="plus" title={t`Add`} />
|
||||
</button>
|
||||
)}
|
||||
>
|
||||
<MenuItem className="compose-menu-add-media">
|
||||
<label class="compose-menu-add-media-field">
|
||||
<FilePickerInput
|
||||
hidden
|
||||
supportedMimeTypes={supportedMimeTypes}
|
||||
maxMediaAttachments={maxMediaAttachments}
|
||||
mediaAttachments={mediaAttachments}
|
||||
disabled={
|
||||
uiState === 'loading' ||
|
||||
mediaAttachments.length >= maxMediaAttachments ||
|
||||
!!poll
|
||||
}
|
||||
onChange={(e) => {
|
||||
const files = e.target.files;
|
||||
if (!files) return;
|
||||
|
||||
const mediaFiles = Array.from(files).map((file) => ({
|
||||
file,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
url: URL.createObjectURL(file),
|
||||
id: null, // indicate uploaded state
|
||||
description: null,
|
||||
}));
|
||||
console.log('MEDIA ATTACHMENTS', files, mediaFiles);
|
||||
|
||||
// Validate max media attachments
|
||||
if (
|
||||
mediaAttachments.length + mediaFiles.length >
|
||||
maxMediaAttachments
|
||||
) {
|
||||
alert(
|
||||
plural(maxMediaAttachments, {
|
||||
one: 'You can only attach up to 1 file.',
|
||||
other: 'You can only attach up to # files.',
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
setMediaAttachments((attachments) => {
|
||||
return attachments.concat(mediaFiles);
|
||||
});
|
||||
}
|
||||
// Reset
|
||||
e.target.value = '';
|
||||
}}
|
||||
setMediaAttachments={setMediaAttachments}
|
||||
/>
|
||||
<Icon icon="attachment" />
|
||||
</label>
|
||||
{/* If maxOptions is not defined or defined and is greater than 1, show poll button */}
|
||||
{maxOptions == null ||
|
||||
(maxOptions > 1 && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
class="toolbar-button"
|
||||
disabled={
|
||||
uiState === 'loading' ||
|
||||
!!poll ||
|
||||
!!mediaAttachments.length
|
||||
}
|
||||
<Icon icon="media" /> <span>{_(ADD_LABELS.media)}</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setPoll({
|
||||
options: ['', ''],
|
||||
expiresIn: 24 * 60 * 60, // 1 day
|
||||
multiple: false,
|
||||
});
|
||||
setShowEmoji2Picker(true);
|
||||
}}
|
||||
>
|
||||
<Icon icon="poll" alt={t`Add poll`} />
|
||||
</button>
|
||||
</>
|
||||
))}
|
||||
<Icon icon="emoji2" />{' '}
|
||||
<span>{_(ADD_LABELS.customEmoji)}</span>
|
||||
</MenuItem>
|
||||
{!!states.settings.composerGIFPicker && (
|
||||
<MenuItem
|
||||
disabled={gifPickerDisabled}
|
||||
onClick={() => {
|
||||
setShowGIFPicker(true);
|
||||
}}
|
||||
>
|
||||
<span class="icon icon-gif" role="img" />
|
||||
<span>{_(ADD_LABELS.gif)}</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
disabled={pollButtonDisabled}
|
||||
onClick={onPollButtonClick}
|
||||
>
|
||||
<Icon icon="poll" /> <span>{_(ADD_LABELS.poll)}</span>
|
||||
</MenuItem>
|
||||
</Menu2>
|
||||
)}
|
||||
<span class="add-sub-toolbar-button-group" ref={addSubToolbarRef}>
|
||||
<label class="toolbar-button">
|
||||
<FilePickerInput
|
||||
supportedMimeTypes={supportedMimeTypes}
|
||||
maxMediaAttachments={maxMediaAttachments}
|
||||
mediaAttachments={mediaAttachments}
|
||||
disabled={
|
||||
uiState === 'loading' ||
|
||||
mediaAttachments.length >= maxMediaAttachments ||
|
||||
!!poll
|
||||
}
|
||||
setMediaAttachments={setMediaAttachments}
|
||||
/>
|
||||
<Icon icon="media" alt={_(ADD_LABELS.media)} />
|
||||
</label>
|
||||
{/* <button
|
||||
type="button"
|
||||
class="toolbar-button"
|
||||
|
@ -1416,27 +1458,39 @@ function Compose({
|
|||
setShowEmoji2Picker(true);
|
||||
}}
|
||||
>
|
||||
<Icon icon="emoji2" alt={t`Add custom emoji`} />
|
||||
<Icon icon="emoji2" alt={_(ADD_LABELS.customEmoji)} />
|
||||
</button>
|
||||
{!!states.settings.composerGIFPicker && (
|
||||
<button
|
||||
type="button"
|
||||
class="toolbar-button gif-picker-button"
|
||||
disabled={
|
||||
uiState === 'loading' ||
|
||||
(maxMediaAttachments !== undefined &&
|
||||
mediaAttachments.length >= maxMediaAttachments) ||
|
||||
!!poll
|
||||
}
|
||||
disabled={gifPickerDisabled}
|
||||
onClick={() => {
|
||||
setShowGIFPicker(true);
|
||||
}}
|
||||
>
|
||||
<span>GIF</span>
|
||||
<span
|
||||
class="icon icon-gif"
|
||||
aria-label={_(ADD_LABELS.gif)}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
{}
|
||||
{showPollButton && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
class="toolbar-button"
|
||||
disabled={pollButtonDisabled}
|
||||
onClick={onPollButtonClick}
|
||||
>
|
||||
<Icon icon="poll" alt={_(ADD_LABELS.poll)} />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
<div class="spacer" />
|
||||
</span>
|
||||
{/* <div class="spacer" /> */}
|
||||
{uiState === 'loading' ? (
|
||||
<Loader abrupt />
|
||||
) : (
|
||||
|
@ -1495,11 +1549,7 @@ function Compose({
|
|||
})}
|
||||
</select>
|
||||
</label>{' '}
|
||||
<button
|
||||
type="submit"
|
||||
class="large"
|
||||
disabled={uiState === 'loading'}
|
||||
>
|
||||
<button type="submit" disabled={uiState === 'loading'}>
|
||||
{replyToStatus
|
||||
? t`Reply`
|
||||
: editStatus
|
||||
|
@ -1671,6 +1721,58 @@ function Compose({
|
|||
);
|
||||
}
|
||||
|
||||
function FilePickerInput({
|
||||
hidden,
|
||||
supportedMimeTypes,
|
||||
maxMediaAttachments,
|
||||
mediaAttachments,
|
||||
disabled = false,
|
||||
setMediaAttachments,
|
||||
}) {
|
||||
return (
|
||||
<input
|
||||
type="file"
|
||||
hidden={hidden}
|
||||
accept={supportedMimeTypes?.join(',')}
|
||||
multiple={
|
||||
maxMediaAttachments === undefined ||
|
||||
maxMediaAttachments - mediaAttachments >= 2
|
||||
}
|
||||
disabled={disabled}
|
||||
onChange={(e) => {
|
||||
const files = e.target.files;
|
||||
if (!files) return;
|
||||
|
||||
const mediaFiles = Array.from(files).map((file) => ({
|
||||
file,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
url: URL.createObjectURL(file),
|
||||
id: null, // indicate uploaded state
|
||||
description: null,
|
||||
}));
|
||||
console.log('MEDIA ATTACHMENTS', files, mediaFiles);
|
||||
|
||||
// Validate max media attachments
|
||||
if (mediaAttachments.length + mediaFiles.length > maxMediaAttachments) {
|
||||
alert(
|
||||
plural(maxMediaAttachments, {
|
||||
one: 'You can only attach up to 1 file.',
|
||||
other: 'You can only attach up to # files.',
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
setMediaAttachments((attachments) => {
|
||||
return attachments.concat(mediaFiles);
|
||||
});
|
||||
}
|
||||
// Reset
|
||||
e.target.value = '';
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function autoResizeTextarea(textarea) {
|
||||
if (!textarea) return;
|
||||
const { value, offsetHeight, scrollHeight, clientHeight } = textarea;
|
||||
|
|
Loading…
Reference in a new issue