Skip to content

Commit

Permalink
Improve Mobile UX (#69)
Browse files Browse the repository at this point in the history
* Fixed some problems with modal layout in mobile
* Moved current pentomino box inline with individual tiles so it doesn't wrap by itself on mobile
* Added a bit of left-padding to header & toolbars
* Added preload buttons for height & width
* Fixed a bug where the warning about resetting the grid wasn't behaving properly
  • Loading branch information
RheingoldRiver authored Oct 30, 2023
1 parent 20ac3fb commit 7920966
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 75 deletions.
2 changes: 1 addition & 1 deletion src/components/BottomToolbar/BottomToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const BottomToolbar = forwardRef(({ style }: { style: CSSProperties }, ref) => {
return (
<Toolbar.Root
style={style as CSSProperties}
className="space-x-3 my-2 w-full flex justify-start"
className="pl-2 space-x-3 my-2 w-full flex justify-start"
aria-label="Game controls"
>
<ToolbarButton
Expand Down
52 changes: 23 additions & 29 deletions src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,35 @@ export const Header = ({ ...rest }) => {
<div
{...rest}
className={clsx(
"flex flex-row flex-wrap justify-center p-4 rounded-lg mb-3 mx-2",
"py-4 px-6 rounded-lg mb-3 mx-2",
"bg-slate-100 dark:bg-slate-800",
"shadow-md shadow-slate-300 dark:shadow-none",
"max-w-[calc(100vw_-_2rem)]"
"flex flex-wrap max-w-[calc(100vw_-_2rem)] md:max-w-[calc(100vw_-_18em)] items-center gap-4"
)}
>
{ALL_PENTOMINO_NAMES.map((l) => (
<div
key={l}
className={clsx(
currentPentomino.name === l && showKeyboardIndicators ? "border-b border-b-px border-b-slate-300" : "",
"rounded-sm"
)}
>
<PentominoDisplay
pentomino={PENTOMINOES[l]}
color={appPreferences.displayColors[pentominoColors[l]]}
onClick={() => {
updateCurrentPentomino(PENTOMINOES[l]);
}}
style={{
cursor: "pointer",
}}
></PentominoDisplay>
</div>
))}
<div
className={clsx(
"flex flex-wrap max-w-[calc(100vw_-_1em)] md:max-w-[calc(100vw_-_18em)] items-center gap-4 px-2 py-3"
)}
>
{ALL_PENTOMINO_NAMES.map((l) => (
<div
key={l}
className={clsx(
currentPentomino.name === l && showKeyboardIndicators ? "border-b border-b-px border-b-slate-300" : "",
"rounded-sm"
)}
>
<PentominoDisplay
pentomino={PENTOMINOES[l]}
color={appPreferences.displayColors[pentominoColors[l]]}
onClick={() => {
updateCurrentPentomino(PENTOMINOES[l]);
}}
style={{
cursor: "pointer",
}}
></PentominoDisplay>
</div>
))}
</div>
<div
className={clsx(
"lg:ml-6 xl:ml-16 p-1 border-solid rounded border w-28 h-28 md:w-32 md:h-32 flex justify-center items-center self-end",
"lg:ml-6 ml-auto p-1 border-solid rounded border w-28 h-28 md:w-32 md:h-32 flex justify-center items-center",
"border-black dark:border-slate-50"
)}
>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Information/Information.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export const Information = () => {
return (
<Modal trigger={<QuestionMarkCircleIcon className="h-10 w-10 text-gray-800 dark:text-gray-300" />}>
<Dialog.Title className="text-center font-bold text-md mb-2">About Pentominoes</Dialog.Title>
<div className="px-4">
<div className="px-4 pb-4">
<p className="mb-2">
Pentominoes are tiles of area 5. There are 12 distinct pentominoes, up to rotation & reflection, with each
tile having somewhere between 2 (the {<InformationPentominoDisplay p="I" />} tile) and 8 (
Expand Down
2 changes: 1 addition & 1 deletion src/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const Modal = ({
className={clsx(
"bg-gray-300 dark:bg-gray-800 dark:text-gray-50",
"rounded-lg pt-8 pb-0 shadow-md shadow-gray-500 dark:shadow-none",
"fixed top-1/2 left-1/2 translate-x-[-50%] translate-y-[-50%]",
"fixed top-[5vw] left-[5vw] sm:top-1/2 sm:left-1/2 sm:translate-x-[-50%] sm:translate-y-[-50%]",
"max-h-[90vh] overflow-y-auto w-[min(90vw,_40rem)]"
)}
>
Expand Down
125 changes: 83 additions & 42 deletions src/components/Settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AppStateContext } from "../AppStateProvider/AppStateProvider";
import {
Colors,
DEFAULT_DISPLAY_COLORS,
Dimensions,
EMPTY_PENTOMINO,
MAX_DIMENSION_SIZE,
MAX_NUM_COLORS,
Expand Down Expand Up @@ -58,16 +59,46 @@ export const Settings = () => {
const [open, setOpen] = useState<boolean>(false);
const [warnGridReset, setWarnGridReset] = useState<boolean>(false);

const PRESET_SIZES: Dimensions[] = [
{
height: 8,
width: 8,
},
{
height: 5,
width: 12,
},
{
height: 6,
width: 10,
},
{
height: 12,
width: 5,
},
{
height: 10,
width: 6,
},
];

function nextColorsOnMaxChange(newMax: number) {
return Object.entries(currentState.pentominoColors).reduce((acc: Colors, [p, c]) => {
acc[p] = c >= newMax ? 0 : c;
return acc;
}, {});
}

function updateCurrentState(newState: Partial<CurrentState>) {
const nextState = { ...currentState, ...newState };
setCurrentState(nextState);
if (warnGridReset) {
setWarnGridReset(gridChangeNeeded(nextState, grid.length, grid[0].length));
}
}
return (
<Modal
trigger={<Cog8ToothIcon className="h-10 w-10 text-gray-800 dark:text-gray-300" />}
trigger={<Cog8ToothIcon className="h-10 w-10 text-gray-800 dark:text-gray-300" />}
onOpenAutoFocus={() => {
setCurrentState({
height: grid.length,
Expand Down Expand Up @@ -130,7 +161,7 @@ export const Settings = () => {
setOpen(false);
}}
>
<div className="px-8">
<div className="px-4">
<Dialog.Title className="text-center font-bold text-md mb-2">Tile Size</Dialog.Title>
<fieldset className="flex gap-4 items-center mb-4">
<label className="text-right" htmlFor="name">
Expand All @@ -154,45 +185,55 @@ export const Settings = () => {
</select>
</fieldset>
<Dialog.Title className="text-center font-bold text-md mb-2">Grid shape</Dialog.Title>
<fieldset className="flex gap-4 items-center mb-4">
<label className="text-right" htmlFor="width">
Width
</label>
<input
className="bg-white dark:bg-slate-950"
size={4}
id="width"
value={currentState.width}
pattern="[0-9]*"
onChange={(e) => {
const valAsNum = toNumber(e.target.value);
if (isNaN(valAsNum)) return;
setCurrentState({ ...currentState, width: valAsNum });
setWarnGridReset(!gridChangeNeeded);
}}
/>
</fieldset>
{showErrors && errorWidth(currentState) && (
<ErrorText>Width must be between 3 & {MAX_DIMENSION_SIZE}, inclusive</ErrorText>
)}
<fieldset className="flex gap-4 items-center mb-4">
<label className="text-right" htmlFor="height">
Length
</label>
<input
className="bg-white dark:bg-slate-950"
size={4}
id="height"
value={currentState.height}
pattern="[0-9]*"
onChange={(e) => {
const valAsNum = toNumber(e.target.value);
if (isNaN(valAsNum)) return;
setCurrentState({ ...currentState, height: valAsNum });
setWarnGridReset(!gridChangeNeeded);
}}
/>
</fieldset>
<div className="flex flex-row justify-around">
<fieldset className="flex gap-4 items-center mb-4">
<label className="text-right" htmlFor="width">
Width
</label>
<input
className="bg-white dark:bg-slate-950"
size={4}
id="width"
value={currentState.width}
pattern="[0-9]*"
onChange={(e) => {
const valAsNum = toNumber(e.target.value);
if (isNaN(valAsNum)) return;
updateCurrentState({ width: valAsNum });
}}
/>
</fieldset>
{showErrors && errorWidth(currentState) && (
<ErrorText>Width must be between 3 & {MAX_DIMENSION_SIZE}, inclusive</ErrorText>
)}
<fieldset className="flex gap-4 items-center mb-4">
<label className="text-right" htmlFor="height">
Height
</label>
<input
className="bg-white dark:bg-slate-950"
size={4}
id="height"
value={currentState.height}
pattern="[0-9]*"
onChange={(e) => {
const valAsNum = toNumber(e.target.value);
if (isNaN(valAsNum)) return;
updateCurrentState({ height: valAsNum });
}}
/>
</fieldset>
</div>
<div className="flex flex-row justify-around">
{PRESET_SIZES.map((size) => (
<Button
onClick={(e) => {
e.preventDefault();
updateCurrentState(size);
}}
>{`${size.width}x${size.height}`}</Button>
))}
</div>
{showErrors && errorHeight(currentState) && (
<ErrorText>Height must be between 3 & {MAX_DIMENSION_SIZE}, inclusive</ErrorText>
)}
Expand Down Expand Up @@ -373,7 +414,7 @@ export const Settings = () => {
</div>
{/* End of settings area */}
{/* Start confirmation area */}
<div className={clsx("sticky bottom-0 px-8", "bg-gray-400 dark:bg-gray-900", "pt-4 pb-4", "rounded-t-xl")}>
<div className={clsx("sticky bottom-0 px-8", "bg-gray-400 dark:bg-slate-900", "pt-2", "rounded-t-xl")}>
{warnGridReset && <ErrorText>Saving will clear your current board! Submit again to proceed.</ErrorText>}
{showErrors && errorConfig(currentState) && (
<ErrorText>One or more errors detected, see field-specific warnings.</ErrorText>
Expand Down
2 changes: 1 addition & 1 deletion src/components/TopToolbar/TopToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
const TopToolbar = ({ ...rest }) => {
const { orientationDispatch } = useContext(GameStateContext);
return (
<Toolbar.Root {...rest} className="space-x-3 mb-2 w-full flex justify-start" aria-label="Game controls">
<Toolbar.Root {...rest} className="pl-2 space-x-3 mb-2 w-full flex justify-start" aria-label="Game controls">
<ToolbarButton
onClick={() => {
orientationDispatch({ type: OrientationActionType.rotate, direction: RotationDirection.Left });
Expand Down
5 changes: 5 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export const PENTOMINO_DIMENSIONS: PentominoDimensions = {
5: "grid-cols-5",
};

export interface Dimensions {
height: number; // corresponds to the x-coordinate
width: number; // corresponds to the y-coordinate
}

export interface Orientation {
rotation: number;
reflection: number;
Expand Down

0 comments on commit 7920966

Please sign in to comment.