New component: Menu

It's time to do this menu thing the right way instead of hacky CSS
This commit is contained in:
Lim Chee Aun 2023-01-24 20:56:43 +08:00
parent 19ee95d188
commit 28281bb752
7 changed files with 166 additions and 105 deletions

83
package-lock.json generated
View file

@ -10,6 +10,7 @@
"dependencies": {
"@github/text-expander-element": "~2.3.0",
"@iconify-icons/mingcute": "~1.2.3",
"@szhsin/react-menu": "~3.3.1",
"dayjs": "~1.11.7",
"dayjs-twitter": "~0.5.0",
"fast-blurhash": "~1.1.2",
@ -2364,6 +2365,19 @@
"string.prototype.matchall": "^4.0.6"
}
},
"node_modules/@szhsin/react-menu": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@szhsin/react-menu/-/react-menu-3.3.1.tgz",
"integrity": "sha512-e8vK+N1YWwTdYXElvRRf5GIImtcDecqTCzpAa0DkGAknKwfQwtQtUnBn+DECodwsWi5H5ONKTU+kn0qJ70hEYQ==",
"dependencies": {
"prop-types": "^15.7.2",
"react-transition-state": "^1.1.5"
},
"peerDependencies": {
"react": ">=16.14.0",
"react-dom": ">=16.14.0"
}
},
"node_modules/@trivago/prettier-plugin-sort-imports": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.0.0.tgz",
@ -4130,7 +4144,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"peer": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
@ -4370,6 +4383,14 @@
"node": ">=0.10.0"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
@ -4565,6 +4586,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"node_modules/proxy-compare": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.4.0.tgz",
@ -4664,6 +4695,11 @@
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react-router": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.6.2.tgz",
@ -4694,6 +4730,15 @@
"react-dom": ">=16.8"
}
},
"node_modules/react-transition-state": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/react-transition-state/-/react-transition-state-1.1.5.tgz",
"integrity": "sha512-ITY2mZqc2dWG2eitJkYNdcSFW8aKeOlkL2A/vowRrLL8GH3J6Re/SpD/BLvQzrVOTqjsP0b5S9N10vgNNzwMUQ==",
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@ -7477,6 +7522,15 @@
"string.prototype.matchall": "^4.0.6"
}
},
"@szhsin/react-menu": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@szhsin/react-menu/-/react-menu-3.3.1.tgz",
"integrity": "sha512-e8vK+N1YWwTdYXElvRRf5GIImtcDecqTCzpAa0DkGAknKwfQwtQtUnBn+DECodwsWi5H5ONKTU+kn0qJ70hEYQ==",
"requires": {
"prop-types": "^15.7.2",
"react-transition-state": "^1.1.5"
}
},
"@trivago/prettier-plugin-sort-imports": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.0.0.tgz",
@ -8819,7 +8873,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"peer": true,
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
@ -9001,6 +9054,11 @@
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
"dev": true
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
},
"object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
@ -9131,6 +9189,16 @@
"integrity": "sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg==",
"dev": true
},
"prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"proxy-compare": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.4.0.tgz",
@ -9196,6 +9264,11 @@
"integrity": "sha512-IXpIsPe6BleFOEHKzKh5UjwRUaz/JYS0lT/HPsupWEQou2hDqjhLMStc5zyE3eQVT4Fk3FufM8Fw33qW1uyeiw==",
"requires": {}
},
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"react-router": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.6.2.tgz",
@ -9213,6 +9286,12 @@
"react-router": "6.6.2"
}
},
"react-transition-state": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/react-transition-state/-/react-transition-state-1.1.5.tgz",
"integrity": "sha512-ITY2mZqc2dWG2eitJkYNdcSFW8aKeOlkL2A/vowRrLL8GH3J6Re/SpD/BLvQzrVOTqjsP0b5S9N10vgNNzwMUQ==",
"requires": {}
},
"regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",

View file

@ -12,6 +12,7 @@
"dependencies": {
"@github/text-expander-element": "~2.3.0",
"@iconify-icons/mingcute": "~1.2.3",
"@szhsin/react-menu": "~3.3.1",
"dayjs": "~1.11.7",
"dayjs-twitter": "~0.5.0",
"fast-blurhash": "~1.1.2",

View file

@ -805,54 +805,27 @@ button.carousel-dot:is(.active, [disabled].active) {
/* MENU POPUP */
.menu-container {
position: relative;
}
.menu-container button {
color: inherit !important;
}
.menu-container button:is(:hover, :active, :focus) {
background-color: var(--button-plain-bg-hover-color);
}
.menu-container menu {
position: absolute;
right: 0;
top: 0;
transform: translateY(-100%);
opacity: 0;
pointer-events: none;
padding: 8px 0;
.szh-menu {
padding: 8px 0 !important;
margin: 0;
font-size: 16px;
background-color: var(--bg-color);
width: 10em;
list-style: none;
z-index: 100;
border: 1px solid var(--outline-color);
border-radius: 8px;
transition: all 0.2s ease-in-out;
box-shadow: 0 0 8px var(--bg-faded-color), 0 4px 8px var(--bg-faded-color),
0 2px 4px var(--bg-faded-color);
}
.menu-container menu li {
margin: 0;
padding: 0;
list-style: none;
}
.menu-container > button:is(:hover, :active, :focus) + menu,
.menu-container menu:is(:hover, :active) {
opacity: 1;
pointer-events: auto;
}
.menu-container menu button {
width: 100%;
text-align: left;
color: var(--text-color) !important;
border-radius: 0;
}
.menu-container menu button:is(:hover, :focus) {
color: var(--bg-color) !important;
background-color: var(--link-color);
.szh-menu .szh-menu__item {
padding: 8px 16px !important;
}
.szh-menu
.szh-menu__item:not(.szh-menu__item--disabled, .szh-menu__item--hover) {
color: var(--text-color);
}
.szh-menu .szh-menu__item--hover {
color: var(--button-text-color);
background-color: var(--button-bg-color);
}
/* DONUT METER */

View file

@ -1,5 +1,6 @@
import './status.css';
import { Menu, MenuItem } from '@szhsin/react-menu';
import { getBlurHashAverageColor } from 'fast-blurhash';
import mem from 'mem';
import { memo } from 'preact/compat';
@ -587,30 +588,32 @@ function Status({
/>
</div>
{isSelf && (
<span class="menu-container">
<button type="button" title="More" class="plain more-button">
<Icon icon="more" size="l" alt="More" />
</button>
<menu>
{isSelf && (
<li>
<button
type="button"
class="plain"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
states.showCompose = {
editStatus: status,
};
}}
>
Edit&hellip;
</button>
</li>
)}
</menu>
</span>
<Menu
align="end"
menuButton={
<div class="action">
<button
type="button"
title="More"
class="plain more-button"
>
<Icon icon="more" size="l" alt="More" />
</button>
</div>
}
>
{isSelf && (
<MenuItem
onClick={() => {
states.showCompose = {
editStatus: status,
};
}}
>
Edit&hellip;
</MenuItem>
)}
</Menu>
)}
</div>
</>

View file

@ -1,5 +1,7 @@
import './index.css';
import '@szhsin/react-menu/dist/core.css';
import { render } from 'preact';
import { HashRouter } from 'react-router-dom';

View file

@ -29,7 +29,7 @@
padding: 0;
list-style: none;
}
#settings-container ul li {
#settings-container ul:not([role='menu']) > li {
padding: 8px 0 16px;
display: flex;
justify-content: space-between;
@ -37,26 +37,26 @@
flex-wrap: wrap;
border-bottom: var(--hairline-width) solid var(--outline-color);
}
#settings-container ul li .current {
#settings-container ul:not([role='menu']) > li .current {
margin-right: 8px;
color: var(--green-color);
opacity: 0.1;
}
#settings-container ul li .current.is-current {
#settings-container ul:not([role='menu']) > li .current.is-current {
opacity: 1;
}
#settings-container ul li .current.is-current + .avatar {
#settings-container ul:not([role='menu']) > li .current.is-current + .avatar {
box-shadow: 0 0 0 1.5px var(--green-color), 0 0 8px var(--green-color);
}
#settings-container ul li > div {
#settings-container ul:not([role='menu']) > li > div {
flex-grow: 1;
max-width: 100%;
}
#settings-container ul li > div.actions {
#settings-container ul:not([role='menu']) > li > div.actions {
flex-basis: fit-content;
margin-top: 8px;
}
#settings-container ul li > div:last-child {
#settings-container ul:not([role='menu']) > li > div:last-child {
text-align: right;
}
#settings-container div,

View file

@ -1,5 +1,6 @@
import './settings.css';
import { Menu, MenuItem } from '@szhsin/react-menu';
import { useReducer, useRef, useState } from 'preact/hooks';
import { useSnapshot } from 'valtio';
@ -92,43 +93,45 @@ function Settings({ onClose }) {
<Icon icon="transfer" /> Switch
</button>
)}
<span>
{!isDefault && moreThanOneAccount && (
<Menu
align="end"
menuButton={
<button
type="button"
class="plain small"
onClick={() => {
// Move account to the top of the list
accounts.splice(i, 1);
accounts.unshift(account);
store.local.setJSON('accounts', accounts);
setCurrentDefault(i);
}}
title="More"
class="plain more-button"
>
Set as default
<Icon icon="more" size="l" alt="More" />
</button>
)}
{isCurrent && (
<>
{' '}
<button
type="button"
class="plain small"
onClick={() => {
const yes = confirm(
'Are you sure you want to log out?',
);
if (!yes) return;
accounts.splice(i, 1);
store.local.setJSON('accounts', accounts);
location.reload();
}}
>
Log out
</button>
</>
)}
</span>
}
>
<MenuItem
disabled={isDefault || !moreThanOneAccount}
onClick={() => {
// Move account to the top of the list
accounts.splice(i, 1);
accounts.unshift(account);
store.local.setJSON('accounts', accounts);
setCurrentDefault(i);
}}
>
Set as default
</MenuItem>
<MenuItem
disabled={!isCurrent}
onClick={() => {
const yes = confirm(
'Are you sure you want to log out?',
);
if (!yes) return;
accounts.splice(i, 1);
store.local.setJSON('accounts', accounts);
location.reload();
}}
>
Log out
</MenuItem>
</Menu>
</div>
</li>
);