Skip to content

Instantly share code, notes, and snippets.

@horaciosystem
Created July 15, 2021 18:54
Show Gist options
  • Save horaciosystem/2f9c8b84ca298504af24eccaa5178966 to your computer and use it in GitHub Desktop.
Save horaciosystem/2f9c8b84ca298504af24eccaa5178966 to your computer and use it in GitHub Desktop.
import clsx from 'clsx';
import { useEffect, useState } from 'react';
import { Input } from 'reakit';
import { BehaviorSubject } from 'rxjs';
import {
debounceTime,
map,
filter,
distinctUntilChanged,
} from 'rxjs/operators';
import {
Margin,
PageLayoutUpdateSubject,
usePageLayout,
} from '../PageLayoutSubject';
const minimumTypeableArea = 0.5;
const minimumMarginValue = '0';
const oppositeMargin: Record<keyof Margin, keyof Margin> = {
right: 'left',
left: 'right',
top: 'bottom',
bottom: 'top',
};
function getOppositeSide(side: keyof Margin): keyof Margin {
return oppositeMargin[side];
}
export type MarginUpdater = {
side: keyof Margin;
value: number;
updateInputValue: (newValue: number) => void;
};
export default function MarginsFieldSet(): JSX.Element {
const [pageLayout] = usePageLayout();
const [margin, setMargin] = useState<MarginUpdater | null>(null);
useEffect(() => {
if (margin) {
const oppositeSide = getOppositeSide(margin.side);
const dimensionAxis =
oppositeSide === 'left' || oppositeSide === 'right'
? 'width'
: 'height';
const editableArea =
pageLayout.paperSize[dimensionAxis] -
(pageLayout.margins[oppositeSide] + margin.value);
let result = margin.value;
if (editableArea < minimumTypeableArea) {
const available =
pageLayout.paperSize[dimensionAxis] -
minimumTypeableArea -
pageLayout.margins[oppositeSide];
margin.updateInputValue(available);
result = available;
}
PageLayoutUpdateSubject.next({
margins: {
...pageLayout.margins,
[margin.side]: result,
},
});
setMargin(null);
}
return () => setMargin(null);
}, [margin, pageLayout]);
return (
<>
<div className="flex space-x-6">
<NumberInput
id="margin-top"
label="Top"
initialValue={pageLayout.margins.top}
side="top"
onChange={setMargin}
/>
<NumberInput
id="margin-bottom"
label="Bottom"
initialValue={pageLayout.margins.bottom}
side="bottom"
onChange={setMargin}
/>
</div>
<div className="flex space-x-6">
<NumberInput
id="margin-left"
label="Left"
initialValue={pageLayout.margins.left}
side="left"
onChange={setMargin}
/>
<NumberInput
id="margin-right"
label="Right"
initialValue={pageLayout.margins.right}
side="right"
onChange={setMargin}
/>
</div>
</>
);
}
const numberPattern = /[^0-9.]/g;
type NumberInputType = {
id: string;
label: string;
initialValue: number;
side: keyof Margin;
onChange({ side, value, updateInputValue }: MarginUpdater): void;
};
function NumberInput({
label,
initialValue,
onChange,
id,
side,
}: NumberInputType): JSX.Element {
const [value, setValue] = useState<string>(String(initialValue));
const [persistSubject] = useState(() => new BehaviorSubject(value));
useEffect(() => {
persistSubject
.pipe(
distinctUntilChanged(),
debounceTime(500),
map(parseFloat),
filter((value: number) => !isNaN(value))
)
.subscribe((value: number) =>
onChange({
side,
value,
updateInputValue: newValue => setValue(String(newValue)),
})
);
return () => persistSubject.unsubscribe();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.currentTarget.value;
const result = value.replace(numberPattern, '');
setValue(result);
persistSubject.next(result);
};
const handleBlur = () => {
if (!value) {
setValue(minimumMarginValue);
persistSubject.next(minimumMarginValue);
}
};
return (
<div>
<label htmlFor={id} className="text-sm block font-medium mb-1">
{label}
</label>
<Input
inputMode="numeric"
className={clsx(
'py-2 px-3 border border-gray-300 rounded-md w-full text-left',
'hover:bg-gray-200'
)}
id={id}
value={value}
onChange={handleChange}
onBlur={handleBlur}
/>
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment