@openmrs/esm-styleguide
Version:
The styleguide for OpenMRS SPA
121 lines (103 loc) • 4.75 kB
text/typescript
import { createContext, type CSSProperties, type ReactNode, useContext, useMemo } from 'react';
import { createCalendar, getLocalTimeZone, toCalendar, today, type Calendar } from '@internationalized/date';
import { type AriaLabelingProps, type DOMProps } from '@react-types/shared';
import { useConfig } from '@openmrs/esm-react-utils';
import { getLocale, getDefaultCalendar } from '@openmrs/esm-utils';
import { type StyleguideConfigObject } from '../config-schema';
export const OpenmrsIntlLocaleContext = createContext<Intl.Locale | null>(null);
export const useIntlLocale = () => useContext(OpenmrsIntlLocaleContext)!;
/**
* This is the context provided to the OpenmrsDatePicker and OpenmrsDateRangePicker
*/
interface DatepickerContext {
calendar: Calendar | undefined;
intlLocale: Intl.Locale;
today_: ReturnType<typeof today>;
}
/**
* Resolves the active locale, calendar system, and "today" value for use
* in both OpenmrsDatePicker and OpenmrsDateRangePicker.
*
* The locale is resolved from i18next, mapped through the user's preferred
* date locale config, and then used to derive the calendar system. This
* supports non-Gregorian calendars (e.g., Ethiopic) based on locale settings.
*
* Depends on `window.i18next.language` to re-compute when the UI language changes.
*/
export function useDatepickerContext(): DatepickerContext {
const config = useConfig<StyleguideConfigObject>({ externalModuleName: '@openmrs/esm-styleguide' });
const preferredDateLocaleMap = config.preferredDateLocale;
const locale = useMemo(() => {
let loc = getLocale();
if (preferredDateLocaleMap[loc]) {
loc = preferredDateLocaleMap[loc];
}
return loc;
}, [window.i18next.language]);
const calendar = useMemo(() => {
const cal = getDefaultCalendar(locale);
return cal !== undefined ? createCalendar(cal) : undefined;
}, [locale]);
const intlLocale = useMemo(() => new Intl.Locale(locale, { calendar: calendar?.identifier }), [locale, calendar]);
const today_ = useMemo(
() => (calendar ? toCalendar(today(getLocalTimeZone()), calendar) : today(getLocalTimeZone())),
[calendar],
);
return { calendar, intlLocale, today_ };
}
// These are largely copied from non-exported hooks that are part of
// React Aria Components which we need versions of for some of our components.
interface RenderPropsHookOptions<T> extends DOMProps, AriaLabelingProps {
/** The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the element. A function may be provided to compute the class based on component state. */
className?: string | ((values: T & { defaultClassName: string | undefined }) => string);
/** The inline [style](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style) for the element. A function may be provided to compute the style based on component state. */
style?: CSSProperties | ((values: T & { defaultStyle: CSSProperties }) => CSSProperties | undefined);
/** The children of the component. A function may be provided to alter the children based on component state. */
children?: ReactNode | ((values: T & { defaultChildren: ReactNode | undefined }) => ReactNode);
values: T;
defaultChildren?: ReactNode;
defaultClassName?: string;
defaultStyle?: CSSProperties;
}
/**
* Hook to provide standard handling for certain properties, especially className, style, and children.
*/
export function useRenderProps<T>(props: RenderPropsHookOptions<T>) {
const {
className,
style,
children,
defaultClassName = undefined,
defaultChildren = undefined,
defaultStyle,
values,
} = props;
return useMemo(() => {
let computedClassName: string | undefined;
let computedStyle: CSSProperties | undefined;
let computedChildren: ReactNode | undefined;
if (typeof className === 'function') {
computedClassName = className({ ...values, defaultClassName });
} else {
computedClassName = className;
}
if (typeof style === 'function') {
computedStyle = style({ ...values, defaultStyle: defaultStyle || {} });
} else {
computedStyle = style;
}
if (typeof children === 'function') {
computedChildren = children({ ...values, defaultChildren });
} else if (children == null) {
computedChildren = defaultChildren;
} else {
computedChildren = children;
}
return {
className: computedClassName ?? defaultClassName,
style: computedStyle || defaultStyle ? { ...defaultStyle, ...computedStyle } : undefined,
children: computedChildren ?? defaultChildren,
'data-rac': '',
};
}, [className, style, children, defaultClassName, defaultChildren, defaultStyle, values]);
}