Skip to content

Instantly share code, notes, and snippets.

@aparx
Last active March 6, 2023 15:05
Show Gist options
  • Save aparx/588007b77ddb651713489a6005d9d170 to your computer and use it in GitHub Desktop.
Save aparx/588007b77ddb651713489a6005d9d170 to your computer and use it in GitHub Desktop.
Little type & runtime utility useful for styling, providing utilities to map any kind of units (such as XXL, XL, numbers and more) into a specfic unit string with custom piping to mutate given values.
/*
* Example:
*
*
*/
const ExampleUnitMap = { xl: 3.0, md: 1.0, sm: 0.5, };
type ExampleUnit = keyof typeof ExampleUnitMap;
const testMapper = UnitMapper<
ExampleUnit /* type of "value" */,
number /* result of ExampleUnitMap[value]; type of "input" */,
number /* result of 8 * input */,
'px' /* kind of final representation unit (optional) */
>(
// we transform a given unit into its number
(value) => ExampleUnitMap[value],
// we transform
(input) => 8 * input /* input * 8px */,
'px'
);
const spacingInPx = testMapper('xl');
// ^? `${number}px`
const testCaseA = testMapper('xl'); // => 24px (8 * 3.0)
const testCaseB = testMapper('md'); // => 8px (8 * 1.0)
const testCaseC = testMapper('sm'); // => 4px (8 * 0.5)
/*
* <----> Implementation <---->
*/
export type UnitMappingPipe<TValue, TOutput> = (value: TValue) => TOutput;
export type UnitMappingBase<TValue, TComputedValue, TOutput> = (
value: TValue,
then?: UnitMappingPipe<TComputedValue, TComputedValue>
) => TOutput;
export type UnitMappingOutputType = string | undefined;
export type UnitMapperComputedValue = string | number;
export type UnitMapperOutput<
TOutUnitAppendix extends UnitMappingOutputType,
TComputedValue extends UnitMapperComputedValue
> = TOutUnitAppendix extends string ? `${TComputedValue}${TOutUnitAppendix}` : TComputedValue;
export type UnitMapper<
TValue,
TComputedValue extends UnitMapperComputedValue,
TOutUnitAppendix extends UnitMappingOutputType
> = UnitMappingBase<TValue, TComputedValue, UnitMapperOutput<TOutUnitAppendix, TComputedValue>>;
export type UnitAssociator<TValue, TTransformerInput> = (value: TValue) => TTransformerInput;
export type UnitTransformer<TTransformerInput, TComputedValue extends UnitMapperComputedValue> = (
input: TTransformerInput
) => TComputedValue;
/* UnitMapper Constructor */
export type UnitMapperConstructor = {
<
TValue,
TTransformerInput,
TComputedValue extends UnitMapperComputedValue,
TOutUnitAppendix extends UnitMappingOutputType
>(
associator: UnitAssociator<TValue, TTransformerInput>,
transformer: UnitTransformer<TTransformerInput, TComputedValue>,
displayUnit?: TOutUnitAppendix
): UnitMapper<TValue, TComputedValue, TOutUnitAppendix>;
<TValue, TTransformerInput, TComputedValue extends UnitMapperComputedValue>(
associator: UnitAssociator<TValue, TTransformerInput>,
transformer: UnitTransformer<TTransformerInput, TComputedValue>
): UnitMapper<TValue, TComputedValue, undefined>;
};
export const UnitMapper = function <
TValue,
TTransformerInput,
TComputedValue extends UnitMapperComputedValue,
TOutUnitAppendix extends UnitMappingOutputType
>(
associator: UnitAssociator<TValue, TTransformerInput>,
transformer: UnitTransformer<TTransformerInput, TComputedValue>,
displayUnit: TOutUnitAppendix
): UnitMapper<TValue, TComputedValue, TOutUnitAppendix> {
return function (
value: TValue,
then?: UnitMappingPipe<TComputedValue, TComputedValue>
): UnitMapperOutput<TOutUnitAppendix, TComputedValue> {
let computed: TComputedValue = transformer(associator(value));
computed = then != null ? then(computed) : computed;
// prettier-ignore
return (
displayUnit !== undefined ? `${computed}${displayUnit}` : computed
) as UnitMapperOutput<TOutUnitAppendix, TComputedValue>;
};
} as UnitMapperConstructor;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment