Skip to content

Commit 4bdee25

Browse files
committed
feat: add InputAlertModal
this is just a modal primitive that has one text input, and a text validation function
1 parent df29fa3 commit 4bdee25

File tree

9 files changed

+103
-241
lines changed

9 files changed

+103
-241
lines changed

components/CreatePresetModal.tsx

Lines changed: 23 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,58 @@
11
import { useContext, useMemo, useState } from "react";
22
import { themeContext } from "@contexts/themeContext";
33
import { checkIfThemeExists, generatePreset, toast } from "../backend";
4-
import { BiPlus } from "react-icons/bi";
5-
import { AlertDialog, LabelledInput, Modal } from "./Primitives";
6-
import { twMerge } from "tailwind-merge";
4+
import { InputAlertDialog } from "./Primitives/InputAlertDialog";
75

86
export function CreatePresetModal({ closeModal }: { closeModal: () => void }) {
97
const { themes: localThemeList, refreshThemes } = useContext(themeContext);
10-
const enabledThemes = useMemo(
11-
() => localThemeList.filter((e) => e.enabled).length,
12-
[localThemeList]
13-
);
148

15-
const [presetName, setPresetName] = useState<string>("");
16-
const nameContainsInvalidCharacters = useMemo(
17-
() => !!presetName.match(/[\\/:*?\"<>|]/g),
18-
[presetName]
19-
);
20-
const invalidName = useMemo(() => {
9+
const nameContainsInvalidCharacters = (presetName: string) =>
10+
!!presetName.match(/[\\/:*?\"<>|]/g);
11+
12+
const invalidName = (presetName: string) => {
2113
return (
2214
(presetName.length === 3 || presetName.length === 4) &&
2315
!!presetName.match(/(LPT\d)|(CO(M\d|N))|(NUL)|(AUX)|(PRN)/g)
2416
);
25-
}, [presetName]);
17+
};
2618

27-
async function createPreset() {
28-
if (presetName) {
29-
const alreadyExists = await checkIfThemeExists(presetName);
19+
async function createPreset(input: string) {
20+
if (input) {
21+
const alreadyExists = await checkIfThemeExists(input);
3022
if (alreadyExists) {
3123
toast("Theme Already Exists!");
32-
setPresetName("");
3324
return;
3425
}
35-
generatePreset(presetName).then(() => {
26+
generatePreset(input).then(() => {
3627
toast("Preset Created Successfully");
3728
refreshThemes(true);
38-
setPresetName("");
29+
closeModal();
3930
});
4031
}
4132
}
4233
return (
4334
<>
44-
<AlertDialog
35+
<InputAlertDialog
4536
defaultOpen
37+
dontCloseOnAction
4638
onOpenChange={(open) => {
4739
if (!open) closeModal();
4840
}}
49-
actionDisabled={
50-
presetName.length === 0 ||
51-
presetName === "New Profile" ||
52-
nameContainsInvalidCharacters ||
53-
invalidName
54-
}
41+
validateInput={(text: string) => {
42+
return !(
43+
text.length === 0 ||
44+
text === "New Profile" ||
45+
nameContainsInvalidCharacters(text) ||
46+
invalidName(text)
47+
);
48+
}}
5549
actionText="Create"
5650
onAction={createPreset}
5751
title="Create Profile"
5852
description={`A profile saves the current state of your themes so that you can quickly re-apply it.`}
59-
Content={
60-
<div className="flex w-full items-end gap-2 px-4 pb-4">
61-
<LabelledInput
62-
inputClass="bg-base-5.5-light dark:bg-base-5.5-dark"
63-
label="Profile Name"
64-
value={presetName}
65-
onValueChange={setPresetName}
66-
/>
67-
</div>
68-
}
53+
labelText="Profile Name"
54+
inputClass="bg-base-5.5-light dark:bg-base-5.5-dark"
6955
/>
70-
{/* <Modal
71-
defaultOpen
72-
Content={
73-
<div className="flex w-full items-end gap-2 px-4 pb-4">
74-
<LabelledInput
75-
inputClass="bg-base-5.5-light dark:bg-base-5.5-dark"
76-
label="Preset Name"
77-
value={presetName}
78-
onValueChange={setPresetName}
79-
/>
80-
</div>
81-
}
82-
actionDisabled={presetName.length === 0}
83-
actionText="Create"
84-
onAction={createPreset}
85-
title="Create Preset"
86-
description={`This preset will combine ${
87-
enabledThemes === 1 ? "the " : "all "
88-
}${enabledThemes} theme${
89-
enabledThemes === 1 ? "" : "s"
90-
} you currently have enabled. Enabling/disabling it will toggle them all at once.`}
91-
// triggerDisabled={enabledThemes === 0}
92-
// Trigger={
93-
// <div
94-
// className={`flex h-8 w-fit items-center justify-center gap-2 whitespace-nowrap rounded-full border-2 border-[#2e2e2e] px-4 py-2 text-sm font-bold ring-brandBlue transition duration-100 focus:ring-2 ${
95-
// enabledThemes > 0 ? "bg-[#2563eb]" : "pointer-events-none opacity-50"
96-
// }`}
97-
// >
98-
// <BiPlus />
99-
// <span>New Preset</span>
100-
// </div>
101-
// }
102-
/> */}
10356
</>
10457
);
10558
}

components/Presets/AddThemeToPresetButton.tsx

Lines changed: 0 additions & 79 deletions
This file was deleted.

components/Presets/PresetFolderView.tsx

Lines changed: 0 additions & 30 deletions
This file was deleted.

components/Presets/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
11
export * from "./PresetSelectionDropdown";
2-
export * from "./PresetFolderView";
3-
export * from "./AddThemeToPresetButton";
42
export * from "./PresetThemeNameDisplayCard";

components/Primitives/AlertDialog.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as AD from "@radix-ui/react-alert-dialog";
22
import { fontContext } from "@contexts/FontContext";
3-
import { ReactNode, useContext, useState, useEffect } from "react";
3+
import { ReactNode, useContext, useState, useEffect, ReactElement } from "react";
44
import { twMerge } from "tailwind-merge";
5+
56
export function AlertDialog({
67
Trigger = null,
78
title,
@@ -17,7 +18,7 @@ export function AlertDialog({
1718
actionDisabled = false,
1819
dontClose = false,
1920
onAction = () => {},
20-
customAction = null,
21+
CustomAction = null,
2122
actionClass = "",
2223
}: {
2324
Trigger?: ReactNode;
@@ -33,7 +34,7 @@ export function AlertDialog({
3334
onOpenChange?: (open: boolean) => void;
3435
actionText?: string | ReactNode;
3536
actionDisabled?: boolean;
36-
customAction?: ReactNode;
37+
CustomAction?: ReactNode | null;
3738
onAction?: () => void;
3839
actionClass?: string;
3940
}) {
@@ -74,7 +75,7 @@ export function AlertDialog({
7475
</AD.Cancel>
7576
)}
7677
{Footer}
77-
{customAction || (
78+
{CustomAction || (
7879
<AD.Action
7980
onClick={(event) => {
8081
dontCloseOnAction && event.preventDefault();
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { AlertDialog } from "./AlertDialog";
2+
import { ReactNode, useState } from "react";
3+
import { LabelledInput } from "./LabelledField";
4+
5+
export function InputAlertDialog(props: {
6+
Trigger?: ReactNode;
7+
title: string;
8+
triggerDisabled?: boolean;
9+
description?: string;
10+
Content?: ReactNode;
11+
dontClose?: boolean;
12+
Footer?: ReactNode;
13+
dontCloseOnAction?: boolean;
14+
cancelText?: string;
15+
defaultOpen?: boolean;
16+
onOpenChange?: (open: boolean) => void;
17+
actionText?: string | ReactNode;
18+
actionDisabled?: boolean;
19+
customAction?: ReactNode;
20+
onAction: (input: string) => void;
21+
actionClass?: string;
22+
labelText: string;
23+
inputClass?: string;
24+
labelClass?: string;
25+
rootClass?: string;
26+
validateInput: (e: string) => boolean;
27+
}) {
28+
const [inputValue, setInputValue] = useState("");
29+
30+
return (
31+
<AlertDialog
32+
{...props}
33+
onAction={() => {
34+
props.onAction(inputValue);
35+
}}
36+
actionDisabled={!props.validateInput(inputValue)}
37+
Content={
38+
<div className="flex w-full items-end gap-2 px-4 pb-4">
39+
<LabelledInput
40+
onKeyDown={(e) => {
41+
if (e.key === "Enter" && props.validateInput(inputValue)) {
42+
props.onAction(inputValue);
43+
}
44+
}}
45+
inputClass="bg-base-5.5-light dark:bg-base-5.5-dark"
46+
label={props.labelText}
47+
value={inputValue}
48+
onValueChange={setInputValue}
49+
/>
50+
</div>
51+
}
52+
/>
53+
);
54+
}

components/Primitives/LabelledField.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as Label from "@radix-ui/react-label";
2-
import { useState, ReactNode } from "react";
2+
import { useState, ReactNode, KeyboardEventHandler } from "react";
33
import { twMerge } from "tailwind-merge";
44

55
export function LabelledInput({
@@ -11,6 +11,7 @@ export function LabelledInput({
1111
placeholder = "",
1212
inputClass = "",
1313
password = false,
14+
onKeyDown = () => {},
1415
}: {
1516
label: ReactNode | string;
1617
value: string;
@@ -20,6 +21,7 @@ export function LabelledInput({
2021
placeholder?: string;
2122
inputClass?: string;
2223
password?: boolean;
24+
onKeyDown?: KeyboardEventHandler<HTMLInputElement>;
2325
}) {
2426
const [type, setType] = useState<"text" | "password">(password ? "password" : "text");
2527
return (
@@ -28,6 +30,7 @@ export function LabelledInput({
2830
{label}
2931
</Label.Root>
3032
<input
33+
onKeyDown={onKeyDown}
3134
onFocus={() => password && setType("text")}
3235
onBlur={() => password && setType("password")}
3336
placeholder={placeholder}

0 commit comments

Comments
 (0)