feat(QuoteOptions): refactor custom quote UI and styles for improved layout and usability

This commit is contained in:
alexsparkes
2026-02-01 15:49:09 +00:00
parent fc3092ad44
commit f42bdf2fb8
3 changed files with 270 additions and 53 deletions

View File

@@ -25,8 +25,12 @@ export default function Quote() {
const zoomQuote = localStorage.getItem('zoomQuote');
return `${1.2 * Number((zoomQuote || 100) / 100)}em`;
});
const [authorDetails, setAuthorDetails] = useState(localStorage.getItem('authorDetails') === 'true');
const [isLegacyStyle, setIsLegacyStyle] = useState(localStorage.getItem('widgetStyle') === 'legacy');
const [authorDetails, setAuthorDetails] = useState(
localStorage.getItem('authorDetails') === 'true',
);
const [isLegacyStyle, setIsLegacyStyle] = useState(
localStorage.getItem('widgetStyle') === 'legacy',
);
// Compute if current quote is favorited
const isFavourited = useMemo(() => {
@@ -62,8 +66,9 @@ export default function Quote() {
}
};
const shouldRefresh = localStorage.getItem('quotechange') === 'refresh' ||
localStorage.getItem('quotechange') === null;
const shouldRefresh =
localStorage.getItem('quotechange') === 'refresh' ||
localStorage.getItem('quotechange') === null;
if (shouldRefresh) {
getQuote();
@@ -100,8 +105,9 @@ export default function Quote() {
{quoteData.quote}
</span>
{authorDetails && (
isLegacyStyle ? (
{authorDetails &&
quoteData.author &&
(isLegacyStyle ? (
<AuthorInfoLegacy
author={quoteData.author}
authorlink={quoteData.authorlink}
@@ -122,8 +128,7 @@ export default function Quote() {
onShare={() => toggleShareModal(true)}
isFavourited={isFavourited}
/>
)
)}
))}
</div>
);
}

View File

@@ -13,6 +13,7 @@ import {
import { Checkbox, Dropdown, Textarea } from 'components/Form/Settings';
import { Button } from 'components/Elements';
import { FREQUENCY_OPTIONS } from 'utils/frequencyManager';
import './QuoteOptions.scss';
const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) => {
const getCustom = () => {
@@ -143,9 +144,11 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) =>
localStorage.removeItem('quoteQueue');
}
// Notify the frequency interval hook that the frequency changed
window.dispatchEvent(new CustomEvent('frequencyChanged', {
detail: { type: 'quote' }
}));
window.dispatchEvent(
new CustomEvent('frequencyChanged', {
detail: { type: 'quote' },
}),
);
}}
items={FREQUENCY_OPTIONS.map((opt) => ({
value: opt.value,
@@ -209,35 +212,48 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) =>
</Row>
{customQuote.length !== 0 ? (
<div className="messagesContainer">
{customQuote.map((_url, index) => (
<div className="messageMap" key={index}>
<div className="icon">
<MdOutlineFormatQuote />
</div>
<div className="messageText">
<Textarea
value={customQuote[index].quote}
placeholder={variables.getMessage('modals.main.settings.sections.quote.title')}
onChange={(e) => handleCustomQuote(e, true, index, 'quote')}
style={{ fontSize: '22px', fontWeight: 'bold' }}
minRows={1}
/>
<Textarea
value={customQuote[index].author}
placeholder={variables.getMessage('modals.main.settings.sections.quote.author')}
className="subtitle"
onChange={(e) => handleCustomQuote(e, true, index, 'author')}
minRows={1}
/>
</div>
<div>
<div className="messageAction">
<Button
type="settings"
<div className="customQuoteContainer">
{customQuote.map((quote, index) => (
<div className="customQuoteItem" key={index}>
<div className="quoteContent">
<div className="quoteHeader">
<span className="quoteLabel">
<MdOutlineFormatQuote className="quoteLabelIcon" />
{variables.getMessage('modals.main.settings.sections.quote.title')}
</span>
<button
className="quoteRemoveBtn"
onClick={() => modifyCustomQuote('remove', index)}
icon={<MdCancel />}
label={variables.getMessage('modals.main.marketplace.product.buttons.remove')}
aria-label={variables.getMessage(
'modals.main.marketplace.product.buttons.remove',
)}
>
<MdCancel />
</button>
</div>
<div className="quoteInputGroup">
<Textarea
value={quote.quote}
placeholder={variables.getMessage(
'modals.main.settings.sections.quote.title',
)}
onChange={(e) => handleCustomQuote(e, true, index, 'quote')}
minRows={3}
className="quoteTextarea"
/>
</div>
<div className="quoteInputGroup">
<label className="quoteLabel">
{variables.getMessage('modals.main.settings.sections.quote.author')}
</label>
<input
type="text"
className="authorInput"
value={quote.author}
placeholder={variables.getMessage(
'modals.main.settings.sections.quote.author',
)}
onChange={(e) => handleCustomQuote(e, true, index, 'author')}
/>
</div>
</div>
@@ -245,20 +261,18 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) =>
))}
</div>
) : (
<div className="photosEmpty">
<div className="emptyNewMessage">
<MdOutlineFormatQuote />
<span className="title">{variables.getMessage(`${QUOTE_SECTION}.no_quotes`)}</span>
<span className="subtitle">
{variables.getMessage('modals.main.settings.sections.message.add_some')}
</span>
<Button
type="settings"
onClick={() => modifyCustomQuote('add')}
icon={<MdAdd />}
label={variables.getMessage(`${QUOTE_SECTION}.add`)}
/>
</div>
<div className="customQuoteEmpty">
<MdOutlineFormatQuote className="emptyIcon" />
<span className="emptyTitle">{variables.getMessage(`${QUOTE_SECTION}.no_quotes`)}</span>
<span className="emptySubtitle">
{variables.getMessage('modals.main.settings.sections.message.add_some')}
</span>
<Button
type="settings"
onClick={() => modifyCustomQuote('add')}
icon={<MdAdd />}
label={variables.getMessage(`${QUOTE_SECTION}.add`)}
/>
</div>
)}
</>

View File

@@ -0,0 +1,198 @@
@use 'scss/variables' as *;
.customQuoteContainer {
display: flex;
flex-direction: column;
gap: 16px;
padding-bottom: 20px;
}
.customQuoteItem {
position: relative;
padding: 20px;
border-radius: 12px;
transition: all 0.2s ease;
@include themed {
background: t($modal-secondaryColour);
border: 1px solid t($modal-border);
}
&:hover {
.quoteRemoveBtn {
opacity: 1;
}
}
.quoteHeader {
display: flex;
justify-content: space-between;
align-items: center;
.quoteLabel {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.8px;
@include themed {
color: t($color);
opacity: 0.7;
}
.quoteLabelIcon {
font-size: 1rem;
opacity: 0.6;
}
}
}
.quoteRemoveBtn {
border-radius: 8px;
padding: 8px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
transition: all 0.2s ease;
opacity: 0.7;
@include themed {
background: t($modal-sidebarActive);
border: 1px solid t($modal-border);
color: t($color);
}
&:hover {
background: rgba(255, 100, 100, 0.15);
border-color: rgba(255, 100, 100, 0.3);
color: rgba(255, 100, 100, 0.9);
transform: scale(1.05);
}
&:active {
transform: scale(0.95);
}
}
.quoteContent {
display: flex;
flex-direction: column;
gap: 20px;
padding-right: 50px;
width: 100%;
}
.quoteInputGroup {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
.quoteTextarea {
border-radius: 8px;
padding: 12px 14px;
font-size: 1rem;
line-height: 1.6;
resize: vertical;
transition: all 0.2s ease;
min-height: 80px;
width: 100%;
box-sizing: border-box;
@include themed {
background: t($modal-background);
border: 1px solid t($modal-border);
color: t($color);
}
&:focus {
outline: none;
@include themed {
border-color: t($link);
}
}
&::placeholder {
@include themed {
color: t($subColor);
}
opacity: 0.5;
}
}
.authorInput {
border-radius: 8px;
padding: 10px 14px;
font-size: 0.95rem;
transition: all 0.2s ease;
width: 100%;
box-sizing: border-box;
@include themed {
background: t($modal-background);
border: 1px solid t($modal-border);
color: t($color);
}
&:focus {
outline: none;
@include themed {
border-color: t($link);
}
}
&::placeholder {
@include themed {
color: t($subColor);
}
opacity: 0.5;
}
}
}
}
.customQuoteEmpty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
text-align: center;
gap: 16px;
.emptyIcon {
font-size: 4rem;
margin-bottom: 8px;
@include themed {
color: t($subColor);
}
opacity: 0.3;
}
.emptyTitle {
font-size: 1.25rem;
font-weight: 600;
@include themed {
color: t($color);
}
}
.emptySubtitle {
font-size: 0.95rem;
max-width: 400px;
margin-bottom: 8px;
@include themed {
color: t($subColor);
}
}
}