Created
July 15, 2021 18:54
-
-
Save horaciosystem/2f9c8b84ca298504af24eccaa5178966 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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