mirror of
https://git.sr.ht/~ashkeel/strimertul
synced 2024-09-18 01:50:50 +00:00
fix: keep cursor in the correct position when cleaning up IDs
fixes #29
This commit is contained in:
parent
fd9dbedfdb
commit
8f5f38a377
3 changed files with 50 additions and 4 deletions
35
frontend/src/ui/components/utils/ControlledInput.tsx
Normal file
35
frontend/src/ui/components/utils/ControlledInput.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
// From https://stackoverflow.com/a/68928267
|
||||
// Allows to have a input with text manipulation (e.g. sanitation) without
|
||||
// messing with the cursor
|
||||
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
|
||||
const ControlledInput = (
|
||||
props: React.DetailedHTMLProps<
|
||||
React.InputHTMLAttributes<HTMLInputElement>,
|
||||
HTMLInputElement
|
||||
>,
|
||||
) => {
|
||||
const { value, onChange, ...rest } = props;
|
||||
const [cursor, setCursor] = useState<number>(null);
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const input = ref.current;
|
||||
if (input) input.setSelectionRange(cursor, cursor);
|
||||
}, [ref, cursor, value]);
|
||||
|
||||
return (
|
||||
<input
|
||||
ref={ref}
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
setCursor(e.target.selectionStart);
|
||||
if (onChange) onChange(e);
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ControlledInput;
|
|
@ -12,6 +12,7 @@ import {
|
|||
Button,
|
||||
Checkbox,
|
||||
CheckboxIndicator,
|
||||
ControlledInputBox,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
Field,
|
||||
|
@ -268,6 +269,7 @@ function GoalItem({
|
|||
function RewardsPage() {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const [cursorPosition, setCursorPosition] = useState(0);
|
||||
const [config] = useModule(modules.loyaltyConfig);
|
||||
const [rewards, setRewards] = useModule(modules.loyaltyRewards);
|
||||
const [filter, setFilter] = useState('');
|
||||
|
@ -343,13 +345,17 @@ function RewardsPage() {
|
|||
<Label htmlFor="reward-id">
|
||||
{t('pages.loyalty-rewards.reward-id')}
|
||||
</Label>
|
||||
<InputBox
|
||||
<ControlledInputBox
|
||||
id="reward-id"
|
||||
type="text"
|
||||
required
|
||||
disabled={!dialogReward.new}
|
||||
value={dialogReward?.reward?.id}
|
||||
onFocus={(e) => {
|
||||
e.target.selectionStart = cursorPosition;
|
||||
}}
|
||||
onChange={(e) => {
|
||||
setCursorPosition(e.target.selectionStart);
|
||||
setDialogReward({
|
||||
...dialogReward,
|
||||
reward: {
|
||||
|
@ -671,7 +677,7 @@ function GoalsPage() {
|
|||
<Label htmlFor="goal-id">
|
||||
{t('pages.loyalty-rewards.goal-id')}
|
||||
</Label>
|
||||
<InputBox
|
||||
<ControlledInputBox
|
||||
id="goal-id"
|
||||
type="text"
|
||||
required
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import * as UnstyledLabel from '@radix-ui/react-label';
|
||||
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
||||
import * as ToggleGroup from '@radix-ui/react-toggle-group';
|
||||
import { CSS } from '@stitches/react';
|
||||
import { styled } from './theme';
|
||||
import { theme } from '.';
|
||||
import ControlledInput from '../components/utils/ControlledInput';
|
||||
|
||||
export const Field = styled('fieldset', {
|
||||
all: 'unset',
|
||||
|
@ -45,7 +47,7 @@ export const Label = styled(UnstyledLabel.Root, {
|
|||
fontWeight: 'bold',
|
||||
});
|
||||
|
||||
export const InputBox = styled('input', {
|
||||
const inputStyles: CSS = {
|
||||
all: 'unset',
|
||||
fontWeight: '300',
|
||||
border: '1px solid $gray6',
|
||||
|
@ -74,7 +76,10 @@ export const InputBox = styled('input', {
|
|||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const InputBox = styled('input', inputStyles);
|
||||
export const ControlledInputBox = styled(ControlledInput, inputStyles);
|
||||
|
||||
export const Textarea = styled('textarea', {
|
||||
all: 'unset',
|
||||
|
|
Loading…
Reference in a new issue