Skip to content

Commit 771a1d8

Browse files
committed
Improve mobile version to show only Generate button by default and a button to expand
1 parent 21f49e9 commit 771a1d8

File tree

3 files changed

+117
-30
lines changed

3 files changed

+117
-30
lines changed

src/UserInput.module.css

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,12 @@
8181
cursor: pointer;
8282
text-transform: uppercase;
8383
letter-spacing: 0.5px;
84+
overflow: hidden;
85+
text-overflow: ellipsis;
8486
}
8587

8688
.buttonPrimary {
89+
composes: button;
8790
color: #fff;
8891
background: linear-gradient(to bottom, #d35400, #c0392b);
8992
border: none;
@@ -107,28 +110,19 @@
107110

108111
.buttonOutline {
109112
background: rgba(44, 62, 80, 0.4);
110-
}
111-
112-
.buttonOutlinePrimary {
113113
color: #fff;
114114
border-color: rgba(255, 255, 255, 0.3);
115115
}
116116

117-
.buttonOutlinePrimary:hover {
117+
.buttonOutline:hover {
118118
color: #fff;
119119
background: rgba(255, 255, 255, 0.1);
120120
border-color: rgba(255, 255, 255, 0.5);
121121
}
122122

123-
.buttonOutlineSecondary {
124-
color: #fff;
125-
border-color: rgba(255, 255, 255, 0.3);
126-
}
127-
128-
.buttonOutlineSecondary:hover {
129-
color: #fff;
130-
background: rgba(255, 255, 255, 0.1);
131-
border-color: rgba(255, 255, 255, 0.5);
123+
.buttonSecondary {
124+
composes: button;
125+
composes: buttonOutline;
132126
}
133127

134128
.button:disabled {
@@ -147,8 +141,62 @@
147141
font-size: 0.9rem;
148142
}
149143

144+
.optionsContainer {
145+
transition: max-height 0.3s ease-in-out, opacity 0.3s ease-in-out;
146+
overflow: hidden;
147+
}
148+
149+
.secondaryButtons {
150+
composes: buttonGroup;
151+
/* The transition doesn't work as good because of the display: none on the buttons */
152+
transition: max-height 0.3s ease-in-out, opacity 0.3s ease-in-out;
153+
margin-top: 0;
154+
}
155+
150156
@media (max-width: 768px) {
151157
.buttonGroup {
152158
grid-template-columns: 1fr;
153159
}
160+
161+
.secondaryButtons.collapsed {
162+
max-height: 0;
163+
opacity: 0;
164+
165+
* {
166+
/* Hide all buttons otherwise they'll be clickable but not visible */
167+
display: none;
168+
}
169+
}
170+
171+
.secondaryButtons.expanded {
172+
max-height: 1000px;
173+
opacity: 1;
174+
}
175+
176+
.optionsContainer.collapsed {
177+
max-height: 0;
178+
opacity: 0;
179+
}
180+
181+
.optionsContainer.expanded {
182+
max-height: 1000px;
183+
opacity: 1;
184+
}
185+
186+
.expandButton {
187+
display: block;
188+
width: 100%;
189+
margin-bottom: 1rem;
190+
}
191+
}
192+
193+
@media (min-width: 769px) {
194+
.optionsContainer {
195+
max-height: none;
196+
opacity: 1;
197+
}
198+
199+
.expandButton {
200+
display: none;
201+
}
154202
}

src/UserInput.tsx

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import copy from "copy-to-clipboard";
22
import jsoncrush from "jsoncrush";
33
import { getNpcOptionsValues, Npc, NpcGenerateOptions } from "npc-generator";
44
import { Component } from "react";
5+
import { cs } from "./core/classSet";
56
import styles from "./UserInput.module.css";
67

78
const { alignments, occupations, classes, genders, plothooks, professions, races } = getNpcOptionsValues();
@@ -15,6 +16,7 @@ interface IProps {
1516
interface IState {
1617
npcOptions: NpcGenerateOptions;
1718
wasCopiedToClipboard?: boolean;
19+
isExpanded: boolean;
1820
}
1921

2022
type UserOption = {
@@ -104,9 +106,21 @@ export default class UserInput extends Component<IProps, IState> {
104106
super(props);
105107
this.state = {
106108
npcOptions: {},
109+
isExpanded: localStorage.getItem("isExpanded") === "true",
107110
};
108111
}
109112

113+
toggleExpand = () => {
114+
this.setState(
115+
(prevState) => ({
116+
isExpanded: !prevState.isExpanded,
117+
}),
118+
() => {
119+
localStorage.setItem("isExpanded", this.state.isExpanded.toString());
120+
},
121+
);
122+
};
123+
110124
onSubmit = (event: React.FormEvent) => {
111125
event.preventDefault();
112126
this.props.generate(this.state.npcOptions);
@@ -181,7 +195,7 @@ export default class UserInput extends Component<IProps, IState> {
181195
return (
182196
<button
183197
type="button"
184-
className={`${styles.button} ${styles.buttonOutline} ${styles.buttonOutlinePrimary}`}
198+
className={styles.buttonSecondary}
185199
title="Copied to clipboard"
186200
data-test="copy-button"
187201
onBlur={this.handleCopyBlur}
@@ -194,7 +208,7 @@ export default class UserInput extends Component<IProps, IState> {
194208
return (
195209
<button
196210
type="button"
197-
className={`${styles.button} ${styles.buttonOutline} ${styles.buttonOutlineSecondary}`}
211+
className={styles.buttonSecondary}
198212
title="Copy character to clipboard"
199213
data-test="copy-button"
200214
onClick={this.copyNpcToClipboard}
@@ -205,6 +219,8 @@ export default class UserInput extends Component<IProps, IState> {
205219
}
206220

207221
render() {
222+
const { isExpanded } = this.state;
223+
208224
const npcOptions = userOptions.map((userOption) => {
209225
const enable = !(userOption.condition && !userOption.condition(this.state.npcOptions));
210226

@@ -263,25 +279,26 @@ export default class UserInput extends Component<IProps, IState> {
263279

264280
return (
265281
<form onSubmit={this.onSubmit}>
266-
{npcOptions}
282+
<div className={cs(styles.optionsContainer, isExpanded ? styles.expanded : styles.collapsed)}>{npcOptions}</div>
267283
<div className={styles.buttonGroup}>
268-
<button type="submit" className={`${styles.button} ${styles.buttonPrimary}`} data-test="generate-button">
284+
<button type="submit" className={styles.buttonPrimary} data-test="generate-button">
269285
Generate
270286
</button>
271-
{this.renderCopyToClipboardButton()}
272-
<button type="button" className={`${styles.button} ${styles.buttonOutline} ${styles.buttonOutlineSecondary}`} onClick={this.downloadTxtFile}>
273-
Download
274-
</button>
275-
<button type="button" className={`${styles.button} ${styles.buttonOutline} ${styles.buttonOutlineSecondary}`} onClick={this.props.onToggleHistory}>
276-
History
287+
<div className={cs(styles.secondaryButtons, isExpanded ? styles.expanded : styles.collapsed)}>
288+
{this.renderCopyToClipboardButton()}
289+
<button type="button" className={styles.buttonSecondary} onClick={this.downloadTxtFile}>
290+
Download
291+
</button>
292+
<button type="button" className={styles.buttonSecondary} onClick={this.props.onToggleHistory}>
293+
History
294+
</button>
295+
<a className={cs(styles.buttonSecondary, styles.bookmarkButton)} href={npcDataUrl.toString()} data-test="bookmark-button">
296+
🔗 Bookmark
297+
</a>
298+
</div>
299+
<button type="button" className={cs(styles.buttonSecondary, styles.expandButton)} onClick={this.toggleExpand}>
300+
{isExpanded ? "Hide Options ▲" : "Show Options ▼"}
277301
</button>
278-
<a
279-
className={`${styles.button} ${styles.buttonOutline} ${styles.buttonOutlineSecondary} ${styles.bookmarkButton}`}
280-
href={npcDataUrl.toString()}
281-
data-test="bookmark-button"
282-
>
283-
🔗 Bookmark
284-
</a>
285302
</div>
286303
</form>
287304
);

src/core/classSet.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
export type IClassValue = string | undefined | null | false | [string?, boolean?] | { [className: string]: boolean | null | undefined | number };
2+
3+
/**
4+
* Combines class names into a single string.
5+
* @param args - The class names to combine.
6+
* @returns A string of combined class names.
7+
*/
8+
export function cs(...args: IClassValue[]): string {
9+
return args
10+
.map((v) => {
11+
if (Array.isArray(v)) {
12+
return v[1] ? v[0] : undefined;
13+
}
14+
if (v && typeof v === "object") {
15+
return cs(...Object.keys(v).filter((c) => !!v[c]));
16+
}
17+
return v;
18+
})
19+
.filter(Boolean)
20+
.join(" ");
21+
}
22+
export const classSet = cs;

0 commit comments

Comments
 (0)