mirror of
https://git.sr.ht/~ashkeel/strimertul
synced 2024-09-20 02:00:49 +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,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
CheckboxIndicator,
|
CheckboxIndicator,
|
||||||
|
ControlledInputBox,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
Field,
|
Field,
|
||||||
|
@ -268,6 +269,7 @@ function GoalItem({
|
||||||
function RewardsPage() {
|
function RewardsPage() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const [cursorPosition, setCursorPosition] = useState(0);
|
||||||
const [config] = useModule(modules.loyaltyConfig);
|
const [config] = useModule(modules.loyaltyConfig);
|
||||||
const [rewards, setRewards] = useModule(modules.loyaltyRewards);
|
const [rewards, setRewards] = useModule(modules.loyaltyRewards);
|
||||||
const [filter, setFilter] = useState('');
|
const [filter, setFilter] = useState('');
|
||||||
|
@ -343,13 +345,17 @@ function RewardsPage() {
|
||||||
<Label htmlFor="reward-id">
|
<Label htmlFor="reward-id">
|
||||||
{t('pages.loyalty-rewards.reward-id')}
|
{t('pages.loyalty-rewards.reward-id')}
|
||||||
</Label>
|
</Label>
|
||||||
<InputBox
|
<ControlledInputBox
|
||||||
id="reward-id"
|
id="reward-id"
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
disabled={!dialogReward.new}
|
disabled={!dialogReward.new}
|
||||||
value={dialogReward?.reward?.id}
|
value={dialogReward?.reward?.id}
|
||||||
|
onFocus={(e) => {
|
||||||
|
e.target.selectionStart = cursorPosition;
|
||||||
|
}}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
setCursorPosition(e.target.selectionStart);
|
||||||
setDialogReward({
|
setDialogReward({
|
||||||
...dialogReward,
|
...dialogReward,
|
||||||
reward: {
|
reward: {
|
||||||
|
@ -671,7 +677,7 @@ function GoalsPage() {
|
||||||
<Label htmlFor="goal-id">
|
<Label htmlFor="goal-id">
|
||||||
{t('pages.loyalty-rewards.goal-id')}
|
{t('pages.loyalty-rewards.goal-id')}
|
||||||
</Label>
|
</Label>
|
||||||
<InputBox
|
<ControlledInputBox
|
||||||
id="goal-id"
|
id="goal-id"
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import * as UnstyledLabel from '@radix-ui/react-label';
|
import * as UnstyledLabel from '@radix-ui/react-label';
|
||||||
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
||||||
import * as ToggleGroup from '@radix-ui/react-toggle-group';
|
import * as ToggleGroup from '@radix-ui/react-toggle-group';
|
||||||
|
import { CSS } from '@stitches/react';
|
||||||
import { styled } from './theme';
|
import { styled } from './theme';
|
||||||
import { theme } from '.';
|
import { theme } from '.';
|
||||||
|
import ControlledInput from '../components/utils/ControlledInput';
|
||||||
|
|
||||||
export const Field = styled('fieldset', {
|
export const Field = styled('fieldset', {
|
||||||
all: 'unset',
|
all: 'unset',
|
||||||
|
@ -45,7 +47,7 @@ export const Label = styled(UnstyledLabel.Root, {
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
});
|
});
|
||||||
|
|
||||||
export const InputBox = styled('input', {
|
const inputStyles: CSS = {
|
||||||
all: 'unset',
|
all: 'unset',
|
||||||
fontWeight: '300',
|
fontWeight: '300',
|
||||||
border: '1px solid $gray6',
|
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', {
|
export const Textarea = styled('textarea', {
|
||||||
all: 'unset',
|
all: 'unset',
|
||||||
|
|
Loading…
Reference in a new issue