= Partial & {
+export type ComponentProps = Partial & {
readonly value: T;
};
+export type MaybeComponentProps = Partial<
+ Omit
+> & {
+ readonly value: undefined | null | T;
+};
+
export type MaybeRendererProps = Partial<
Omit
> & {
@@ -26,3 +32,29 @@ export const MaybeRenderer = ({
// `rest` has already had everything that's not ComponentProps removed.
return )} value={value} />;
};
+
+/**
+ * Binds a regular renderer into a MaybeRenderer so it can be used via e.g. the
+ * render prop of a
+ * @param Component
+ */
+export function maybeRender(
+ Component: React.ComponentType>
+) {
+ /** Wrapped version of Component which allows for undefined/null values */
+ function wrapped(props: MaybeComponentProps) {
+ return ;
+ }
+ wrapped.displayName = `Maybe${Component.displayName ?? Component.name}`;
+ return wrapped;
+}
+
+/**
+ * Returns a memoized version of Component bound to a MaybeRenderer
+ * @param Component
+ */
+export function useMaybeRender(
+ Component: React.ComponentType>
+) {
+ return useMemo(() => maybeRender(Component), [Component]);
+}
diff --git a/src/renderers/number-renderer/index.tsx b/src/renderers/number-renderer/index.tsx
new file mode 100644
index 00000000..ed0e28d4
--- /dev/null
+++ b/src/renderers/number-renderer/index.tsx
@@ -0,0 +1 @@
+export * from './number-renderer';
diff --git a/src/renderers/number-renderer/number-renderer.stories.tsx b/src/renderers/number-renderer/number-renderer.stories.tsx
new file mode 100644
index 00000000..5eac6918
--- /dev/null
+++ b/src/renderers/number-renderer/number-renderer.stories.tsx
@@ -0,0 +1,13 @@
+import {NumberRenderer} from './number-renderer';
+
+export default {
+ component: NumberRenderer,
+ title: 'Renderers/NumberRenderer',
+};
+
+export const integer = () => ;
+export const float = () => ;
+export const fixed = () => ;
+export const precision = () => ;
+export const nan = () => ;
+export const infinity = () => ;
diff --git a/src/renderers/number-renderer/number-renderer.tsx b/src/renderers/number-renderer/number-renderer.tsx
new file mode 100644
index 00000000..20ef5f8f
--- /dev/null
+++ b/src/renderers/number-renderer/number-renderer.tsx
@@ -0,0 +1,41 @@
+import {fi} from '@faker-js/faker';
+import React, {useMemo} from 'react';
+
+import {useLocale} from '../../core/locale';
+import {useContextWithDefaults} from '../../support';
+import {RendererProps} from '../types';
+
+export interface NumberRendererContextType {
+ fixed?: number;
+ precision?: number;
+}
+
+export const NumberRendererContext =
+ React.createContext({});
+
+export type NumberRendererProps = RendererProps<
+ number,
+ NumberRendererContextType
+>;
+
+export const NumberRenderer = ({value, ...rest}: NumberRendererProps) => {
+ const locale = useLocale();
+
+ const {fixed, precision} = useContextWithDefaults(
+ NumberRendererContext,
+ rest
+ );
+
+ const nf = useMemo(
+ () =>
+ new Intl.NumberFormat(locale.language, {
+ maximumFractionDigits: fixed,
+ maximumSignificantDigits: precision,
+ minimumFractionDigits: fixed,
+ minimumSignificantDigits: precision,
+ }),
+ [locale.language]
+ );
+
+ return {nf.format(value)};
+};
diff --git a/src/renderers/percent-renderer/index.tsx b/src/renderers/percent-renderer/index.tsx
new file mode 100644
index 00000000..aefee843
--- /dev/null
+++ b/src/renderers/percent-renderer/index.tsx
@@ -0,0 +1 @@
+export * from './percent-renderer';
diff --git a/src/renderers/percent-renderer/percent-renderer.stories.tsx b/src/renderers/percent-renderer/percent-renderer.stories.tsx
new file mode 100644
index 00000000..7ff91e2a
--- /dev/null
+++ b/src/renderers/percent-renderer/percent-renderer.stories.tsx
@@ -0,0 +1,19 @@
+import {PercentRenderer} from './percent-renderer';
+
+export default {
+ component: PercentRenderer,
+ title: 'Renderers/PercentRenderer',
+};
+
+export const fixed = () => (
+
+);
+export const precision = () => (
+
+);
+export const nan = () => ;
+export const infinity = () => ;
+export const percentZeroToOne = () => ;
+export const percentZeroToOneHundred = () => (
+
+);
diff --git a/src/renderers/percent-renderer/percent-renderer.tsx b/src/renderers/percent-renderer/percent-renderer.tsx
new file mode 100644
index 00000000..b9a612e1
--- /dev/null
+++ b/src/renderers/percent-renderer/percent-renderer.tsx
@@ -0,0 +1,46 @@
+import React, {useMemo} from 'react';
+
+import {useLocale} from '../../core/locale';
+import {useContextWithDefaults} from '../../support';
+import {NumberRendererContextType} from '../number-renderer';
+import {RendererProps} from '../types';
+
+export interface PercentRendererContextType extends NumberRendererContextType {
+ base?: 1 | 100;
+}
+
+export const PercentRendererContext =
+ React.createContext({});
+
+export type PercentRendererProps = RendererProps<
+ number,
+ PercentRendererContextType
+>;
+
+export const PercentRenderer = ({value, ...rest}: PercentRendererProps) => {
+ const locale = useLocale();
+
+ const {base, fixed, precision} = useContextWithDefaults(
+ PercentRendererContext,
+ rest
+ );
+
+ const nf = useMemo(
+ () =>
+ new Intl.NumberFormat(locale.language, {
+ maximumFractionDigits: fixed,
+ maximumSignificantDigits: precision,
+ minimumFractionDigits: fixed,
+ minimumSignificantDigits: precision,
+ style: 'percent',
+ }),
+ [fixed, locale.language, precision]
+ );
+
+ const formattedValue = useMemo(
+ () => (base === 100 ? value / 100 : value),
+ [base, value]
+ );
+
+ return {nf.format(formattedValue)};
+};