Skip to content

Commit

Permalink
feat: adding right content to text field
Browse files Browse the repository at this point in the history
  • Loading branch information
thegrannychaseroperation committed Jun 12, 2024
1 parent 55d1bfb commit 50665b4
Show file tree
Hide file tree
Showing 10 changed files with 122 additions and 117 deletions.
10 changes: 10 additions & 0 deletions src/renderer/src/components/text-field/text-field.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ export const textField = recipe({
backgroundColor: vars.color.background,
},
},
state: {
error: {
borderColor: vars.color.danger,
},
},
},
});

Expand Down Expand Up @@ -73,3 +78,8 @@ export const togglePasswordButton = style({
color: vars.color.muted,
padding: `${SPACING_UNIT}px`,
});

export const textFieldWrapper = style({
display: "flex",
gap: `${SPACING_UNIT}px`,
});
62 changes: 35 additions & 27 deletions src/renderer/src/components/text-field/text-field.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useId, useMemo, useState } from "react";
import React, { useId, useMemo, useState } from "react";
import type { RecipeVariants } from "@vanilla-extract/recipes";
import * as styles from "./text-field.css";
import { EyeClosedIcon, EyeIcon } from "@primer/octicons-react";
Expand All @@ -20,6 +20,8 @@ export interface TextFieldProps
React.HTMLAttributes<HTMLDivElement>,
HTMLDivElement
>;
rightContent?: React.ReactNode | null;
state?: NonNullable<RecipeVariants<typeof styles.textField>>["state"];
}

export function TextField({
Expand All @@ -28,6 +30,8 @@ export function TextField({
hint,
textFieldProps,
containerProps,
rightContent = null,
state,
...props
}: TextFieldProps) {
const id = useId();
Expand All @@ -48,33 +52,37 @@ export function TextField({
<div className={styles.textFieldContainer} {...containerProps}>
{label && <label htmlFor={id}>{label}</label>}

<div
className={styles.textField({ focused: isFocused, theme })}
{...textFieldProps}
>
<input
id={id}
className={styles.textFieldInput({ readOnly: props.readOnly })}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
{...props}
type={inputType}
/>
<div className={styles.textFieldWrapper}>
<div
className={styles.textField({ focused: isFocused, theme, state })}
{...textFieldProps}
>
<input
id={id}
className={styles.textFieldInput({ readOnly: props.readOnly })}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
{...props}
type={inputType}
/>

{showPasswordToggleButton && (
<button
type="button"
className={styles.togglePasswordButton}
onClick={() => setIsPasswordVisible(!isPasswordVisible)}
aria-label={t("toggle_password_visibility")}
>
{isPasswordVisible ? (
<EyeClosedIcon size={16} />
) : (
<EyeIcon size={16} />
)}
</button>
)}
{showPasswordToggleButton && (
<button
type="button"
className={styles.togglePasswordButton}
onClick={() => setIsPasswordVisible(!isPasswordVisible)}
aria-label={t("toggle_password_visibility")}
>
{isPasswordVisible ? (
<EyeClosedIcon size={16} />
) : (
<EyeIcon size={16} />
)}
</button>
)}
</div>

{rightContent}
</div>

{hint && <small>{hint}</small>}
Expand Down
16 changes: 12 additions & 4 deletions src/renderer/src/pages/game-details/game-details-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import * as styles from "./game-details.css";
import { useTranslation } from "react-i18next";
import { gameDetailsContext } from "@renderer/context";

const HERO_ANIMATION_THRESHOLD = 25;

export function GameDetailsContent() {
const heroRef = useRef<HTMLDivElement | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
const [isHeaderStuck, setIsHeaderStuck] = useState(false);

Expand Down Expand Up @@ -42,14 +45,19 @@ export function GameDetailsContent() {
}, [objectID]);

const onScroll: React.UIEventHandler<HTMLElement> = (event) => {
const heroHeight = heroRef.current?.clientHeight ?? styles.HERO_HEIGHT;

const scrollY = (event.target as HTMLDivElement).scrollTop;
const opacity = Math.max(0, 1 - scrollY / styles.HERO_HEIGHT);
const opacity = Math.max(
0,
1 - scrollY / (heroHeight - HERO_ANIMATION_THRESHOLD)
);

if (scrollY >= styles.HERO_HEIGHT && !isHeaderStuck) {
if (scrollY >= heroHeight && !isHeaderStuck) {
setIsHeaderStuck(true);
}

if (scrollY <= styles.HERO_HEIGHT && isHeaderStuck) {
if (scrollY <= heroHeight && isHeaderStuck) {
setIsHeaderStuck(false);
}

Expand All @@ -70,7 +78,7 @@ export function GameDetailsContent() {
onScroll={onScroll}
className={styles.container}
>
<div className={styles.hero}>
<div ref={heroRef} className={styles.hero}>
<div
style={{
backgroundColor: gameColor,
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/pages/game-details/hero/hero-panel.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const panel = recipe({
variants: {
stuck: {
true: {
boxShadow: "0px 0px 15px 0px #000000",
boxShadow: "0px 0px 15px 0px rgba(0, 0, 0, 0.8)",
},
},
},
Expand Down
33 changes: 17 additions & 16 deletions src/renderer/src/pages/game-details/modals/game-options-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,22 +105,23 @@ export function GameOptionsModal({
{t("executable_section_description")}
</h4>
</div>
<div className={styles.gameOptionRow}>
<TextField
value={game.executablePath || ""}
readOnly
theme="dark"
disabled
placeholder={t("no_executable_selected")}
/>
<Button
type="button"
theme="outline"
onClick={handleChangeExecutableLocation}
>
{t("select_executable")}
</Button>
</div>

<TextField
value={game.executablePath || ""}
readOnly
theme="dark"
disabled
placeholder={t("no_executable_selected")}
rightContent={
<Button
type="button"
theme="outline"
onClick={handleChangeExecutableLocation}
>
{t("select_executable")}
</Button>
}
/>

{game.executablePath && (
<div className={styles.gameOptionRow}>
Expand Down
40 changes: 19 additions & 21 deletions src/renderer/src/pages/settings/add-download-source-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Button, Modal, TextField } from "@renderer/components";
import { SPACING_UNIT } from "@renderer/theme.css";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";

import * as styles from "./settings-download-sources.css";
import { Button, Modal, TextField } from "@renderer/components";
import { SPACING_UNIT } from "@renderer/theme.css";

interface AddDownloadSourceModalProps {
visible: boolean;
Expand Down Expand Up @@ -64,24 +63,23 @@ export function AddDownloadSourceModal({
minWidth: "500px",
}}
>
<div className={styles.downloadSourceField}>
<TextField
label={t("download_source_url")}
placeholder="Insert a valid JSON url"
value={value}
onChange={(e) => setValue(e.target.value)}
/>

<Button
type="button"
theme="outline"
style={{ alignSelf: "flex-end" }}
onClick={handleValidateDownloadSource}
disabled={isLoading || !value}
>
{t("validate_download_source")}
</Button>
</div>
<TextField
label={t("download_source_url")}
placeholder="Insert a valid JSON url"
value={value}
onChange={(e) => setValue(e.target.value)}
rightContent={
<Button
type="button"
theme="outline"
style={{ alignSelf: "flex-end" }}
onClick={handleValidateDownloadSource}
disabled={isLoading || !value}
>
{t("validate_download_source")}
</Button>
}
/>

{validationResult && (
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@ import { style } from "@vanilla-extract/css";
import { SPACING_UNIT, vars } from "../../theme.css";
import { recipe } from "@vanilla-extract/recipes";

export const downloadSourceField = style({
display: "flex",
gap: `${SPACING_UNIT}px`,
});

export const downloadSources = style({
padding: "0",
margin: "0",
Expand Down
36 changes: 17 additions & 19 deletions src/renderer/src/pages/settings/settings-download-sources.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,25 +137,23 @@ export function SettingsDownloadSources() {
</div>
</div>

<div className={styles.downloadSourceField}>
<TextField
label={t("download_source_url")}
value={downloadSource.url}
readOnly
theme="dark"
disabled
/>

<Button
type="button"
theme="outline"
style={{ alignSelf: "flex-end" }}
onClick={() => handleRemoveSource(downloadSource.id)}
>
<NoEntryIcon />
{t("remove_download_source")}
</Button>
</div>
<TextField
label={t("download_source_url")}
value={downloadSource.url}
readOnly
theme="dark"
disabled
rightContent={
<Button
type="button"
theme="outline"
onClick={() => handleRemoveSource(downloadSource.id)}
>
<NoEntryIcon />
{t("remove_download_source")}
</Button>
}
/>
</li>
))}
</ul>
Expand Down
7 changes: 0 additions & 7 deletions src/renderer/src/pages/settings/settings-general.css.ts

This file was deleted.

28 changes: 11 additions & 17 deletions src/renderer/src/pages/settings/settings-general.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
SelectField,
} from "@renderer/components";
import { useTranslation } from "react-i18next";
import * as styles from "./settings-general.css";
import type { UserPreferences } from "@types";
import { useAppSelector } from "@renderer/hooks";

Expand Down Expand Up @@ -113,22 +112,17 @@ export function SettingsGeneral({

return (
<>
<div className={styles.downloadsPathField}>
<TextField
label={t("downloads_path")}
value={form.downloadsPath}
readOnly
disabled
/>

<Button
style={{ alignSelf: "flex-end" }}
theme="outline"
onClick={handleChooseDownloadsPath}
>
{t("change")}
</Button>
</div>
<TextField
label={t("downloads_path")}
value={form.downloadsPath}
readOnly
disabled
rightContent={
<Button theme="outline" onClick={handleChooseDownloadsPath}>
{t("change")}
</Button>
}
/>

<SelectField
label={t("language")}
Expand Down

0 comments on commit 50665b4

Please sign in to comment.