Skip to content

Commit

Permalink
refactor: slightly more accessible and consistent number input (#415)
Browse files Browse the repository at this point in the history
  • Loading branch information
Charlie-XIAO authored Feb 12, 2025
1 parent 7e2ba34 commit f21c874
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 86 deletions.
1 change: 0 additions & 1 deletion src/manager/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ const tabs = [

const App = () => {
const theme = useAppSettingsStore((state) => state.theme);
console.log("Rerendered!");

useExitAppListener();
useInitialRescan();
Expand Down
113 changes: 113 additions & 0 deletions src/manager/components/IntegerInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { Box, BoxProps, Reset } from "@radix-ui/themes";
import { ChangeEvent, KeyboardEvent, useCallback } from "react";

type IntegerInputProps = BoxProps & {
value: number;
onValueChange: (value: number) => void;
min?: number;
max?: number;
step?: number;
disabled?: boolean;
readOnly?: boolean;
};

// Snap value to the nearest step within the min and max range
function snap(value: number, min: number, max: number, step: number) {
return Math.min(
max,
Math.max(min, min + Math.round((value - min) / step) * step),
);
}

const IntegerInput = ({
value,
onValueChange,
min = -Infinity,
max = Infinity,
step = 1,
disabled = false,
readOnly = false,
...boxProps
}: IntegerInputProps) => {
const handleChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
const targetValue = Number(event.target.value);
if (!Number.isInteger(targetValue)) return;
onValueChange(snap(targetValue, min, max, step));
},
[onValueChange, min, max, step],
);

const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
let nextValue = value;

switch (event.key) {
case "ArrowUp":
event.preventDefault();
if (event.shiftKey) nextValue += 10 * step;
else nextValue += step;
break;
case "ArrowDown":
event.preventDefault();
if (event.shiftKey) nextValue -= 10 * step;
else nextValue -= step;
break;
case "PageUp":
event.preventDefault();
nextValue += 10 * step;
break;
case "PageDown":
event.preventDefault();
nextValue -= 10 * step;
break;
case "Home":
if (!Number.isFinite(min)) return;
event.preventDefault();
nextValue = min;
break;
case "End":
if (!Number.isFinite(max)) return;
event.preventDefault();
nextValue = max;
break;
default:
return;
}

onValueChange(snap(nextValue, min, max, step));
};

return (
<Box
pl="2"
css={{
backgroundColor: "var(--gray-5)",
borderRadius: "var(--radius-2)",
lineHeight: "1.6",
}}
{...boxProps}
asChild
>
<Reset>
<input
type="number"
value={value}
min={min}
max={max}
step={step}
disabled={disabled}
readOnly={readOnly}
onChange={handleChange}
onKeyDown={handleKeyDown}
aria-valuenow={value}
aria-valuemin={min}
aria-valuemax={max}
aria-disabled={disabled}
aria-readonly={readOnly}
/>
</Reset>
</Box>
);
};

export default IntegerInput;
71 changes: 0 additions & 71 deletions src/manager/components/Widgets/NumberInput.tsx

This file was deleted.

38 changes: 29 additions & 9 deletions src/manager/components/Widgets/Settings.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,60 @@
import { Flex, Table } from "@radix-ui/themes";
import { FaTimes } from "react-icons/fa";
import { LiaTimesSolid } from "react-icons/lia";
import { updateWidgetSettings, useWidgetsStore } from "../../hooks";
import { memo, useCallback } from "react";
import NumberInput from "./NumberInput";
import IntegerInput from "../IntegerInput";

const X = ({ id }: SettingsProps) => {
const x = useWidgetsStore((state) => state.widgets[id].settings.x);
const onChange = useCallback(
const onValueChange = useCallback(
(value: number) => updateWidgetSettings(id, { x: value }, true),
[id],
);

return <NumberInput value={x} min={0} width="50px" onChange={onChange} />;
return (
<IntegerInput
value={x}
min={0}
onValueChange={onValueChange}
width="60px"
/>
);
};

const Y = ({ id }: SettingsProps) => {
const y = useWidgetsStore((state) => state.widgets[id].settings.y);
const onChange = useCallback(
const onValueChange = useCallback(
(value: number) => updateWidgetSettings(id, { y: value }, true),
[id],
);

return <NumberInput value={y} min={0} width="50px" onChange={onChange} />;
return (
<IntegerInput
value={y}
min={0}
onValueChange={onValueChange}
width="60px"
/>
);
};

const Opacity = ({ id }: SettingsProps) => {
const opacity = useWidgetsStore(
(state) => state.widgets[id].settings.opacity,
);
const onChange = useCallback(
const onValueChange = useCallback(
(value: number) => updateWidgetSettings(id, { opacity: value }, true),
[id],
);

return (
<NumberInput value={opacity} min={0} width="50px" onChange={onChange} />
<IntegerInput
value={opacity}
min={0}
max={100}
onValueChange={onValueChange}
width="60px"
/>
);
};

Expand Down Expand Up @@ -67,7 +87,7 @@ const Settings = memo(({ id }: SettingsProps) => {
<Table.Cell>
<Flex gap="1" align="center">
<X id={id} />
<FaTimes size={12} color="var(--gray-11)" />
<LiaTimesSolid size={12} color="var(--gray-11)" />
<Y id={id} />
</Flex>
</Table.Cell>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { WidgetConfigType } from "../../../types";
import { useWidgetsStore } from "../../hooks";
import { memo } from "react";

interface WidgetTriggerProps {
interface TriggerProps {
id: string;
value: string;
}

const WidgetTrigger = memo(({ id, value }: WidgetTriggerProps) => {
const Trigger = memo(({ id, value }: TriggerProps) => {
const config = useWidgetsStore((state) => state.widgets[id].config);

return (
Expand Down Expand Up @@ -47,4 +47,4 @@ const WidgetTrigger = memo(({ id, value }: WidgetTriggerProps) => {
);
});

export default WidgetTrigger;
export default Trigger;
4 changes: 2 additions & 2 deletions src/manager/components/Widgets/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Flex, ScrollArea, Separator, Tabs } from "@radix-ui/themes";
import WidgetTrigger from "./WidgetTrigger";
import { useWidgetsStore } from "../../hooks";
import { memo } from "react";
import { useShallow } from "zustand/shallow";
import Trigger from "./Trigger";
import GlobalActions from "./GlobalActions";
import Config from "./Config";
import Settings from "./Settings";
Expand All @@ -21,7 +21,7 @@ const WidgetsTab = memo(() => {
<ScrollArea scrollbars="vertical" asChild>
<Flex direction="column">
{ids.map((id, index) => (
<WidgetTrigger key={id} id={id} value={`tab${index}`} />
<Trigger key={id} id={id} value={`tab${index}`} />
))}
</Flex>
</ScrollArea>
Expand Down

0 comments on commit f21c874

Please sign in to comment.