style(marketplace): potential marketplace page redesign

This commit is contained in:
alexsparkes
2025-11-01 00:50:33 +00:00
parent b303d02492
commit 9727346ff1
11 changed files with 437 additions and 56 deletions

View File

@@ -0,0 +1,113 @@
import { memo } from 'react';
import { MdClose } from 'react-icons/md';
import './installButton.scss';
const MueLogo = () => (
<svg
width="24"
height="24"
viewBox="0 0 500 500"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="mueLogo"
>
<circle cx="250" cy="250" r="250" fill="url(#paint0_linear_1870_2)" />
<path
d="M291.02 132.002V164.879H362.13V235.422H395.302V132.002H291.02Z"
fill="url(#paint1_linear_1870_2)"
/>
<path
d="M314.592 241.186H285.201V270.542H258.081V241.186H228.669V214.119H258.081V184.784H285.201V214.119H314.592V241.186ZM354.25 171.261H283.307V146.651H164.332V308.676H378.929V242.089H354.25V171.261Z"
fill="url(#paint2_linear_1870_2)"
/>
<path
d="M156.949 176.811H134.164V338.836H348.761V316.031H156.949V176.811Z"
fill="url(#paint3_linear_1870_2)"
/>
<path
d="M126.785 206.975H104V369H318.597V346.195H126.785V206.975Z"
fill="url(#paint4_linear_1870_2)"
/>
<defs>
<linearGradient
id="paint0_linear_1870_2"
x1="462"
y1="120"
x2="29.5"
y2="383"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FF5C25" />
<stop offset="0.484375" stopColor="#D21A11" />
<stop offset="1" stopColor="#FF456E" />
</linearGradient>
<linearGradient
id="paint1_linear_1870_2"
x1="343.161"
y1="235.422"
x2="343.161"
y2="132.002"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#F18D91" />
<stop offset="1" stopColor="#FBD3C6" />
</linearGradient>
<linearGradient
id="paint2_linear_1870_2"
x1="271.631"
y1="308.676"
x2="271.631"
y2="146.651"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#F18D91" />
<stop offset="1" stopColor="#FBD3C6" />
</linearGradient>
<linearGradient
id="paint3_linear_1870_2"
x1="241.463"
y1="338.836"
x2="241.463"
y2="176.811"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#F18D91" />
<stop offset="1" stopColor="#FBD3C6" />
</linearGradient>
<linearGradient
id="paint4_linear_1870_2"
x1="211.298"
y1="369"
x2="211.298"
y2="206.975"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#F18D91" />
<stop offset="1" stopColor="#FBD3C6" />
</linearGradient>
</defs>
</svg>
);
const InstallButton = ({ onClick, isInstalled, label }) => {
return (
<button
className={`installButton ${isInstalled ? 'installed' : 'notInstalled'}`}
onClick={onClick}
>
<span className="buttonContent">
<span className="labelText">{label}</span>
<span className="iconWrapper">
<span className={`icon installIcon ${isInstalled ? 'hide' : 'show'}`}>
<MueLogo />
</span>
<span className={`icon removeIcon ${isInstalled ? 'show' : 'hide'}`}>
<MdClose />
</span>
</span>
</span>
</button>
);
};
export default memo(InstallButton);

View File

@@ -0,0 +1 @@
export { default } from './InstallButton';

View File

@@ -0,0 +1,166 @@
.installButton {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 10px;
padding: 12px 24px;
font-size: 16px;
font-weight: 600;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
min-width: 180px;
.buttonContent {
display: flex;
align-items: center;
gap: 10px;
position: relative;
z-index: 1;
}
.iconWrapper {
position: relative;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.icon {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
&.show {
opacity: 1;
transform: rotate(0deg) scale(1);
}
&.hide {
opacity: 0;
transform: rotate(90deg) scale(0.3);
}
}
.mueLogo {
width: 24px;
height: 24px;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
}
.removeIcon {
svg {
width: 24px;
height: 24px;
}
}
.labelText {
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
// Install state (not installed yet)
&.notInstalled {
background: linear-gradient(135deg, #ff5c25 0%, #d21a11 48%, #ff456e 100%);
color: white;
// box-shadow: 0 4px 12px rgba(255, 92, 37, 0.3);
&:hover {
// box-shadow: 0 6px 16px rgba(255, 92, 37, 0.4);
.mueLogo {
animation: logoFloat 0.6s ease-in-out;
}
// .labelText {
// transform: translateX(2px);
// }
}
&:active {
box-shadow: 0 2px 8px rgba(255, 92, 37, 0.2);
}
}
// Installed state (already installed)
&.installed {
background: rgba(255, 255, 255, 0.1);
color: white;
// border: 2px solid rgba(255, 255, 255, 0.2);
// backdrop-filter: blur(10px);
&:hover {
background: rgba(255, 92, 37, 0.2);
border-color: rgba(255, 92, 37, 0.4);
// transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(255, 92, 37, 0.2);
.removeIcon {
animation: rotateIcon 0.4s ease-in-out;
}
// .labelText {
// transform: translateX(2px);
// }
}
&:active {
transform: translateY(0);
}
}
// Loading state (optional - for future use)
&:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none !important;
&:hover {
transform: none !important;
}
}
}
@keyframes logoFloat {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-4px);
}
}
@keyframes rotateIcon {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(90deg);
}
}
// Smooth state transition animation
.installButton.notInstalled.transitioning,
.installButton.installed.transitioning {
animation: buttonPulse 0.5s ease-out;
}
@keyframes buttonPulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}

View File

@@ -10,6 +10,7 @@ import Items from '../components/Items/Items';
import { Dropdown, FileUpload } from 'components/Form/Settings';
import { Header, CustomActions } from 'components/Layout/Settings';
import { Button } from 'components/Elements';
import InstallButton from '../components/Elements/InstallButton';
import { install, uninstall, urlParser } from 'utils/marketplace';
import { updateHash } from 'utils/deepLinking';
@@ -173,9 +174,9 @@ const Added = memo(() => {
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const button = item.display_name ? (
<Button
type="settings"
<InstallButton
onClick={uninstallItem}
isInstalled={true}
label={variables.getMessage('modals.main.marketplace.product.buttons.remove')}
/>
) : '';

View File

@@ -13,6 +13,7 @@ import {
import ItemPage from './ItemPage';
import Items from '../components/Items/Items';
import { Button } from 'components/Elements';
import InstallButton from '../components/Elements/InstallButton';
import { install, urlParser, uninstall } from 'utils/marketplace';
import { updateHash } from 'utils/deepLinking';
@@ -34,18 +35,16 @@ class Marketplace extends PureComponent {
};
this.buttons = {
uninstall: (
<Button
type="settings"
<InstallButton
onClick={() => this.manage('uninstall')}
icon={<MdClose />}
isInstalled={true}
label={variables.getMessage('modals.main.marketplace.product.buttons.remove')}
/>
),
install: (
<Button
type="settings"
<InstallButton
onClick={() => this.manage('install')}
icon={<MdLibraryAdd />}
isInstalled={false}
label={variables.getMessage('modals.main.marketplace.product.buttons.addtomue')}
/>
),

View File

@@ -97,6 +97,95 @@ class ItemPage extends PureComponent {
.sort(() => 0.5 - Math.random())
.slice(0, 3);
// Extract colour from data (British spelling as used in API)
const mainColor = this.props.data.data.colour;
// Helper function to determine if a color is light or dark
const isLightColor = (hexColor) => {
if (!hexColor) return false;
// Remove # if present
const hex = hexColor.replace('#', '');
// Convert to RGB
const r = parseInt(hex.substr(0, 2), 16);
const g = parseInt(hex.substr(2, 2), 16);
const b = parseInt(hex.substr(4, 2), 16);
// Calculate relative luminance (perceived brightness)
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
return luminance > 0.6; // If > 0.6, it's a light color
};
const isLight = isLightColor(mainColor);
const textColor = isLight ? '#000000' : '#ffffff';
// Create dynamic styles for theming with the main color
const themedStyles = mainColor ? (
<style>{`
/* Icon buttons styling */
.iconButtons .btn-icon {
background: ${mainColor} !important;
background-image: none !important;
border-color: ${mainColor} !important;
box-shadow: 0 0 0 1px ${mainColor}, 0 4px 12px ${mainColor}40 !important;
color: ${textColor} !important;
}
.iconButtons .btn-icon:hover {
background: ${mainColor} !important;
filter: brightness(${isLight ? '0.95' : '1.15'});
transform: translateY(-2px);
box-shadow: 0 0 0 1px ${mainColor}, 0 6px 20px ${mainColor}60 !important;
}
/* ItemInfo background gradient */
.itemInfo {
background: linear-gradient(135deg, ${mainColor}ee 0%, ${mainColor}aa 50%, ${mainColor}66 100%) !important;
box-shadow: 0 8px 32px ${mainColor}40 !important;
}
/* Icon shadow */
.itemInfo .icon {
box-shadow: 0 8px 32px ${mainColor}80, 0 0 0 1px ${mainColor}40 !important;
}
/* Install button styling */
.itemInfo .installButton {
background: ${mainColor} !important;
background-image: linear-gradient(135deg, ${mainColor} 0%, ${mainColor}dd 100%) !important;
box-shadow: 0 4px 16px ${mainColor}60 !important;
}
.itemInfo .installButton:hover {
background: ${mainColor} !important;
filter: brightness(${isLight ? '0.95' : '1.15'});
box-shadow: 0 6px 24px ${mainColor}80 !important;
}
/* Install button text and icon color */
.itemInfo .installButton span,
.itemInfo .installButton svg {
color: ${textColor} !important;
}
/* Mue logo - circle matches text color, paths match button color */
.itemInfo .installButton .mueLogo circle {
fill: ${textColor} !important;
}
.itemInfo .installButton .mueLogo path {
fill: ${mainColor} !important;
}
/* Remove the default gradient animation when themed */
.itemInfo .installButton.installed {
background: ${mainColor}aa !important;
background-image: none !important;
}
.itemInfo .installButton.installed:hover {
background: ${mainColor}99 !important;
}
`}</style>
) : null;
if (!this.props.data.display_name) {
return null;
}
@@ -330,12 +419,7 @@ class ItemPage extends PureComponent {
</div>
</div>
</div>
<div
className="itemInfo"
style={{
backgroundImage: `url("${this.props.data.data.icon_url}")`,
}}
>
<div className="itemInfo">
<div className="front">
<img
className="icon"
@@ -357,30 +441,33 @@ class ItemPage extends PureComponent {
</p>
)}
{this.props.data.data.sideload !== true && (
<div className="iconButtons">
<Button
type="icon"
onClick={() => this.setState({ shareModal: true })}
icon={<MdIosShare />}
tooltipTitle={variables.getMessage('widgets.quote.share')}
tooltipKey="share"
/>
<Button
type="icon"
onClick={() =>
window.open(
variables.constants.REPORT_ITEM +
this.props.data.data.display_name.split(' ').join('+'),
'_blank',
)
}
icon={<MdFlag />}
tooltipTitle={variables.getMessage(
'modals.main.marketplace.product.buttons.report',
)}
tooltipKey="report"
/>
</div>
<>
{themedStyles}
<div className="iconButtons">
<Button
type="icon"
onClick={() => this.setState({ shareModal: true })}
icon={<MdIosShare />}
tooltipTitle={variables.getMessage('widgets.quote.share')}
tooltipKey="share"
/>
<Button
type="icon"
onClick={() =>
window.open(
variables.constants.REPORT_ITEM +
this.props.data.data.display_name.split(' ').join('+'),
'_blank',
)
}
icon={<MdFlag />}
tooltipTitle={variables.getMessage(
'modals.main.marketplace.product.buttons.report',
)}
tooltipKey="report"
/>
</div>
</>
)}
{this.props.data.data.in_collections?.length > 0 && (
<div>