mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-30 14:21:04 +00:00
Feat: updated input field design, fixed text overflow
This commit is contained in:
@@ -33,9 +33,18 @@ export function ConfirmModal({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal visible={visible} title={title} description={description} onClose={onClose}>
|
<Modal
|
||||||
|
visible={visible}
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
<div className="confirm-modal__actions">
|
<div className="confirm-modal__actions">
|
||||||
<Button onClick={handleConfirm} theme={confirmTheme} disabled={confirmDisabled}>
|
<Button
|
||||||
|
onClick={handleConfirm}
|
||||||
|
theme={confirmTheme}
|
||||||
|
disabled={confirmDisabled}
|
||||||
|
>
|
||||||
{confirmLabel || t("confirm")}
|
{confirmLabel || t("confirm")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
|||||||
@@ -201,7 +201,8 @@ export function GameDetailsContextProvider({
|
|||||||
}, [objectId, gameTitle, dispatch]);
|
}, [objectId, gameTitle, dispatch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const state = (location && (location.state as Record<string, unknown>)) || {};
|
const state =
|
||||||
|
(location && (location.state as Record<string, unknown>)) || {};
|
||||||
if (state.openRepacks) {
|
if (state.openRepacks) {
|
||||||
setShowRepacksModal(true);
|
setShowRepacksModal(true);
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -104,6 +104,8 @@ export function GameDetailsContent() {
|
|||||||
const [reviewsLoading, setReviewsLoading] = useState(false);
|
const [reviewsLoading, setReviewsLoading] = useState(false);
|
||||||
const [reviewScore, setReviewScore] = useState(5);
|
const [reviewScore, setReviewScore] = useState(5);
|
||||||
const [submittingReview, setSubmittingReview] = useState(false);
|
const [submittingReview, setSubmittingReview] = useState(false);
|
||||||
|
const [reviewCharCount, setReviewCharCount] = useState(0);
|
||||||
|
const MAX_REVIEW_CHARS = 1000;
|
||||||
const [reviewsSortBy, setReviewsSortBy] = useState("newest");
|
const [reviewsSortBy, setReviewsSortBy] = useState("newest");
|
||||||
const [reviewsPage, setReviewsPage] = useState(0);
|
const [reviewsPage, setReviewsPage] = useState(0);
|
||||||
const [hasMoreReviews, setHasMoreReviews] = useState(true);
|
const [hasMoreReviews, setHasMoreReviews] = useState(true);
|
||||||
@@ -127,6 +129,31 @@ export function GameDetailsContent() {
|
|||||||
class: "game-details__review-editor",
|
class: "game-details__review-editor",
|
||||||
"data-placeholder": t("write_review_placeholder"),
|
"data-placeholder": t("write_review_placeholder"),
|
||||||
},
|
},
|
||||||
|
handlePaste: (view, event) => {
|
||||||
|
// Strip formatting from pasted content to prevent overflow issues
|
||||||
|
const text = event.clipboardData?.getData('text/plain') || '';
|
||||||
|
const currentText = view.state.doc.textContent;
|
||||||
|
const remainingChars = MAX_REVIEW_CHARS - currentText.length;
|
||||||
|
|
||||||
|
if (text && remainingChars > 0) {
|
||||||
|
event.preventDefault();
|
||||||
|
const truncatedText = text.slice(0, remainingChars);
|
||||||
|
view.dispatch(view.state.tr.insertText(truncatedText));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onUpdate: ({ editor }) => {
|
||||||
|
const text = editor.getText();
|
||||||
|
setReviewCharCount(text.length);
|
||||||
|
|
||||||
|
// Prevent typing beyond character limit
|
||||||
|
if (text.length > MAX_REVIEW_CHARS) {
|
||||||
|
const truncatedContent = text.slice(0, MAX_REVIEW_CHARS);
|
||||||
|
editor.commands.setContent(truncatedContent);
|
||||||
|
setReviewCharCount(MAX_REVIEW_CHARS);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -266,7 +293,7 @@ export function GameDetailsContent() {
|
|||||||
console.log("reviewScore:", reviewScore);
|
console.log("reviewScore:", reviewScore);
|
||||||
console.log("submittingReview:", submittingReview);
|
console.log("submittingReview:", submittingReview);
|
||||||
|
|
||||||
if (!objectId || !reviewHtml.trim() || submittingReview) {
|
if (!objectId || !reviewHtml.trim() || submittingReview || reviewCharCount > MAX_REVIEW_CHARS) {
|
||||||
console.log("Early return - validation failed");
|
console.log("Early return - validation failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -523,11 +550,7 @@ export function GameDetailsContent() {
|
|||||||
|
|
||||||
<div className="game-details__review-form">
|
<div className="game-details__review-form">
|
||||||
<div className="game-details__review-input-container">
|
<div className="game-details__review-input-container">
|
||||||
<EditorContent
|
<div className="game-details__review-input-header">
|
||||||
editor={editor}
|
|
||||||
className="game-details__review-input"
|
|
||||||
/>
|
|
||||||
<div className="game-details__review-input-bottom">
|
|
||||||
<div className="game-details__review-editor-toolbar">
|
<div className="game-details__review-editor-toolbar">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -560,19 +583,16 @@ export function GameDetailsContent() {
|
|||||||
<u>U</u>
|
<u>U</u>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="game-details__review-char-counter">
|
||||||
<button
|
<span className={reviewCharCount > MAX_REVIEW_CHARS ? "over-limit" : ""}>
|
||||||
className="game-details__review-submit-button"
|
{reviewCharCount}/{MAX_REVIEW_CHARS}
|
||||||
onClick={handleSubmitReview}
|
</span>
|
||||||
disabled={
|
</div>
|
||||||
!editor?.getHTML().trim() || submittingReview
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{submittingReview
|
|
||||||
? t("submitting")
|
|
||||||
: t("submit_review")}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<EditorContent
|
||||||
|
editor={editor}
|
||||||
|
className="game-details__review-input"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="game-details__review-form-bottom">
|
<div className="game-details__review-form-bottom">
|
||||||
@@ -599,6 +619,18 @@ export function GameDetailsContent() {
|
|||||||
<option value={10}>10/10</option>
|
<option value={10}>10/10</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="game-details__review-submit-button"
|
||||||
|
onClick={handleSubmitReview}
|
||||||
|
disabled={
|
||||||
|
!editor?.getHTML().trim() || submittingReview || reviewCharCount > MAX_REVIEW_CHARS
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{submittingReview
|
||||||
|
? t("submitting")
|
||||||
|
: t("submit_review")}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -30,12 +30,8 @@ $hero-height: 300px;
|
|||||||
&__review-form {
|
&__review-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: calc(globals.$spacing-unit * 2);
|
gap: 16px;
|
||||||
margin-bottom: calc(globals.$spacing-unit * 3);
|
margin-bottom: 24px;
|
||||||
padding: calc(globals.$spacing-unit * 2);
|
|
||||||
background: rgba(255, 255, 255, 0.02);
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__review-form-controls {
|
&__review-form-controls {
|
||||||
@@ -54,55 +50,35 @@ $hero-height: 300px;
|
|||||||
&__review-form-bottom {
|
&__review-form-bottom {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-end;
|
align-items: center;
|
||||||
gap: calc(globals.$spacing-unit * 2);
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
@media (max-width: 768px) {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: calc(globals.$spacing-unit * 1.5);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__review-score-container {
|
&__review-score-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
gap: calc(globals.$spacing-unit * 0.75);
|
gap: 8px;
|
||||||
min-width: 120px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__review-score-label {
|
&__review-score-label {
|
||||||
display: block;
|
font-size: 14px;
|
||||||
font-size: globals.$body-font-size;
|
color: #ffffff;
|
||||||
color: globals.$body-color;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__review-score-select {
|
&__review-score-select {
|
||||||
background-color: rgba(255, 255, 255, 0.05);
|
background-color: #2a2a2a;
|
||||||
border: 1px solid globals.$border-color;
|
border: 1px solid #3a3a3a;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: calc(globals.$spacing-unit * 0.75) calc(globals.$spacing-unit * 1);
|
color: #ffffff;
|
||||||
color: globals.$body-color;
|
padding: 6px 12px;
|
||||||
font-size: globals.$body-font-size;
|
font-size: 14px;
|
||||||
font-family: inherit;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition:
|
|
||||||
border-color 0.2s ease,
|
|
||||||
background-color 0.2s ease;
|
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
background-color: rgba(255, 255, 255, 0.08);
|
border-color: #0078d4;
|
||||||
border-color: globals.$brand-teal;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: rgba(255, 255, 255, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
option {
|
|
||||||
background-color: globals.$dark-background-color;
|
|
||||||
color: globals.$body-color;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,19 +126,14 @@ $hero-height: 300px;
|
|||||||
|
|
||||||
&__review-submit-button {
|
&__review-submit-button {
|
||||||
background-color: rgba(255, 255, 255, 0.05);
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
border: 1px solid globals.$border-color;
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
color: globals.$body-color;
|
|
||||||
padding: calc(globals.$spacing-unit * 0.75)
|
|
||||||
calc(globals.$spacing-unit * 1.5);
|
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: globals.$small-font-size;
|
transition: all 0.2s ease;
|
||||||
font-family: inherit;
|
|
||||||
transition: all ease 0.2s;
|
|
||||||
height: 32px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
@@ -170,12 +141,8 @@ $hero-height: 300px;
|
|||||||
border-color: rgba(255, 255, 255, 0.15);
|
border-color: rgba(255, 255, 255, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
background: rgba(255, 255, 255, 0.1);
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
color: rgba(255, 255, 255, 0.5);
|
color: rgba(255, 255, 255, 0.5);
|
||||||
}
|
}
|
||||||
@@ -918,99 +885,75 @@ $hero-height: 300px;
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__review-input-container {
|
&__review-input-container {
|
||||||
width: 100%;
|
display: flex;
|
||||||
position: relative;
|
flex-direction: column;
|
||||||
|
border: 1px solid #3a3a3a;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__review-input-bottom {
|
&__review-input-header {
|
||||||
position: absolute;
|
|
||||||
bottom: 8px;
|
|
||||||
left: 8px;
|
|
||||||
right: 8px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
padding: 8px 12px;
|
||||||
z-index: 10;
|
background-color: #2a2a2a;
|
||||||
|
border-bottom: 1px solid #3a3a3a;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__review-editor-toolbar {
|
&__review-editor-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 4px;
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
background: rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__editor-button {
|
&__editor-button {
|
||||||
background: rgba(255, 255, 255, 0.05);
|
background: none;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
border: 1px solid #4a4a4a;
|
||||||
border-radius: 3px;
|
border-radius: 4px;
|
||||||
color: globals.$body-color;
|
color: #ffffff;
|
||||||
padding: 4px 6px;
|
padding: 4px 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
min-width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover {
|
||||||
background: rgba(255, 255, 255, 0.1);
|
background-color: #3a3a3a;
|
||||||
border-color: rgba(255, 255, 255, 0.2);
|
border-color: #5a5a5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
background-color: #0078d4;
|
||||||
|
border-color: #0078d4;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.is-active {
|
&__review-char-counter {
|
||||||
background: globals.$brand-teal;
|
font-size: 12px;
|
||||||
border-color: globals.$brand-teal;
|
color: #888888;
|
||||||
color: white;
|
|
||||||
|
.over-limit {
|
||||||
|
color: #ff6b6b;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__review-input {
|
&__review-input {
|
||||||
width: 100%;
|
min-height: 120px;
|
||||||
background-color: rgba(255, 255, 255, 0.05);
|
padding: 12px;
|
||||||
border: 1px solid globals.$border-color;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: calc(globals.$spacing-unit * 1.5);
|
|
||||||
padding-bottom: calc(globals.$spacing-unit * 3.5);
|
|
||||||
color: globals.$body-color;
|
|
||||||
font-size: globals.$body-font-size;
|
|
||||||
font-family: inherit;
|
|
||||||
line-height: 1.5;
|
|
||||||
min-height: 100px;
|
|
||||||
transition:
|
|
||||||
border-color 0.2s ease,
|
|
||||||
background-color 0.2s ease;
|
|
||||||
|
|
||||||
&:focus-within {
|
|
||||||
outline: none;
|
|
||||||
background-color: rgba(255, 255, 255, 0.08);
|
|
||||||
border-color: globals.$brand-teal;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: rgba(255, 255, 255, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ProseMirror {
|
.ProseMirror {
|
||||||
outline: none;
|
outline: none;
|
||||||
min-height: 80px;
|
color: #ffffff;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
&:empty:before {
|
&:focus {
|
||||||
content: attr(data-placeholder);
|
outline: none;
|
||||||
color: rgba(208, 209, 215, 0.6);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@@ -1022,7 +965,7 @@ $hero-height: 300px;
|
|||||||
}
|
}
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
font-weight: 700;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
em {
|
em {
|
||||||
|
|||||||
@@ -69,14 +69,32 @@ export function HeroPanelActions() {
|
|||||||
updateGame();
|
updateGame();
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener("hydra:game-favorite-toggled", onFavoriteToggled as EventListener);
|
window.addEventListener(
|
||||||
window.addEventListener("hydra:game-removed-from-library", onGameRemoved as EventListener);
|
"hydra:game-favorite-toggled",
|
||||||
window.addEventListener("hydra:game-files-removed", onFilesRemoved as EventListener);
|
onFavoriteToggled as EventListener
|
||||||
|
);
|
||||||
|
window.addEventListener(
|
||||||
|
"hydra:game-removed-from-library",
|
||||||
|
onGameRemoved as EventListener
|
||||||
|
);
|
||||||
|
window.addEventListener(
|
||||||
|
"hydra:game-files-removed",
|
||||||
|
onFilesRemoved as EventListener
|
||||||
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("hydra:game-favorite-toggled", onFavoriteToggled as EventListener);
|
window.removeEventListener(
|
||||||
window.removeEventListener("hydra:game-removed-from-library", onGameRemoved as EventListener);
|
"hydra:game-favorite-toggled",
|
||||||
window.removeEventListener("hydra:game-files-removed", onFilesRemoved as EventListener);
|
onFavoriteToggled as EventListener
|
||||||
|
);
|
||||||
|
window.removeEventListener(
|
||||||
|
"hydra:game-removed-from-library",
|
||||||
|
onGameRemoved as EventListener
|
||||||
|
);
|
||||||
|
window.removeEventListener(
|
||||||
|
"hydra:game-files-removed",
|
||||||
|
onFilesRemoved as EventListener
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}, [updateLibrary, updateGame]);
|
}, [updateLibrary, updateGame]);
|
||||||
|
|
||||||
@@ -226,7 +244,7 @@ export function HeroPanelActions() {
|
|||||||
onClick={() => setShowRepacksModal(true)}
|
onClick={() => setShowRepacksModal(true)}
|
||||||
theme="outline"
|
theme="outline"
|
||||||
disabled={isGameDownloading}
|
disabled={isGameDownloading}
|
||||||
className={`hero-panel-actions__action ${!repacks.length ? 'hero-panel-actions__action--disabled' : ''}`}
|
className={`hero-panel-actions__action ${!repacks.length ? "hero-panel-actions__action--disabled" : ""}`}
|
||||||
>
|
>
|
||||||
<DownloadIcon />
|
<DownloadIcon />
|
||||||
{t("download")}
|
{t("download")}
|
||||||
|
|||||||
Reference in New Issue
Block a user