Add terra direction config support

main
dev 6 months ago
parent f1dfc893b4
commit 9f60f035c6

@ -1,17 +1,24 @@
<script lang="ts"> <script lang="ts">
import type { TerraConfigProps } from '../types.ts'; import type { TerraConfigProps } from '../types.ts';
import { useTerraConfig } from '../terra-config.ts'; import { useTerraConfig } from '../terra-config.ts';
import { readableActive } from '../../../utils'; import { readableActive } from '../../../utils';
let { children, defaultPortalTo, defaultLocale, defaultDateOrder, defaultTimeFormat }: TerraConfigProps = $props(); let {
children,
useTerraConfig({ defaultPortalTo,
defaultPortalTo: readableActive(() => defaultPortalTo), defaultLocale,
defaultLocale: readableActive(() => defaultLocale), defaultDir,
defaultDateOrder: readableActive(() => defaultDateOrder), defaultDateOrder,
defaultTimeFormat: readableActive(() => defaultTimeFormat), defaultTimeFormat,
}); }: TerraConfigProps = $props();
</script>
useTerraConfig({
{@render children?.()} defaultPortalTo: readableActive(() => defaultPortalTo),
defaultLocale: readableActive(() => defaultLocale),
defaultDir: readableActive(() => defaultDir),
defaultDateOrder: readableActive(() => defaultDateOrder),
defaultTimeFormat: readableActive(() => defaultTimeFormat),
});
</script>
{@render children?.()}

@ -1,8 +1,9 @@
export { default as TerraConfig } from './components/terra-config.svelte'; export { default as TerraConfig } from './components/terra-config.svelte';
export { getTerraConfig, TerraConfigState } from './terra-config.js'; export { getTerraConfig, TerraConfigState } from './terra-config.js';
export type { export type {
TerraConfigProps, TerraConfigProps,
TerraConfigPropsWithoutChildren, TerraConfigPropsWithoutChildren,
TerraDatePresentationProps, TerraPresentationProps,
TerraDateTimePresentationProps, TerraDatePresentationProps,
} from './types.ts'; TerraDateTimePresentationProps,
} from './types.ts';

@ -1,72 +1,86 @@
import { readableActive, type Active, type Getter } from '../../utils'; import { readableActive, type Active, type Direction, type Getter } from '$terra/utils';
import { DEFAULT_DATE_ORDER, DEFAULT_TIME_FORMAT, timeFormatToHourCycle, type HourCycle } from '$terra/adapters/dates'; import {
import { type TerraConfigState, getTerraConfig } from './terra-config.ts'; DEFAULT_DATE_ORDER,
DEFAULT_TIME_FORMAT,
/** timeFormatToHourCycle,
* Creates a generic prop resolver that follows a standard priority chain: type HourCycle,
* 1. The getter's prop value (if defined) } from '$terra/adapters/dates';
* 2. The config default value (if no getter prop value is defined) import { type TerraConfigState, getTerraConfig } from './terra-config.ts';
* 3. The fallback value (if no config value found)
*/ /**
function createPropResolver<T>( * Creates a generic prop resolver that follows a standard priority chain:
configOption: (config: TerraConfigState['opts']) => { current: T | undefined }, * 1. The getter's prop value (if defined)
fallback: T * 2. The config default value (if no getter prop value is defined)
) { * 3. The fallback value (if no config value found)
return (getProp: Getter<T | undefined>): Active<T> => { */
const config = getTerraConfig(); function createPropResolver<T>(
return readableActive(() => { configOption: (config: TerraConfigState['opts']) => { current: T | undefined },
// 1. return the prop's value, if provided fallback: T
const propValue = getProp(); ) {
if (propValue !== undefined) return propValue; return (getProp: Getter<T | undefined>): Active<T> => {
// 2. return the resolved config option value, if available const config = getTerraConfig();
const option = configOption(config).current; return readableActive(() => {
if (option !== undefined) return option; const propValue = getProp();
// 3. return the fallback if no other value is found if (propValue !== undefined) return propValue;
return fallback;
}); const option = configOption(config).current;
}; if (option !== undefined) return option;
}
return fallback;
/** });
* Resolves a locale value using the prop, the config default, or a fallback. };
* }
* Default value: `"en"`
*/ /**
export const resolveLocaleProp = createPropResolver((config) => config.defaultLocale, "en"); * Resolves a locale value using the prop, the config default, or a fallback.
*
/** * Default value: `"en"`
* Resolves a portal's `to` value using the prop, the config default, or a fallback. */
* export const resolveLocaleProp = createPropResolver((config) => config.defaultLocale, 'en');
* Default value: `"body"`
*/ /**
export const resolvePortalToProp = createPropResolver((config) => config.defaultPortalTo, "body"); * Resolves a direction value using the prop, the config default, or a fallback.
*
* Default value: `"ltr"`
/** */
* Resolves a date order using the prop, the config default, or the library fallback. export const resolveDirectionProp = createPropResolver(
*/ (config) => config.defaultDir,
export const resolveDateOrderProp = createPropResolver((config) => config.defaultDateOrder, DEFAULT_DATE_ORDER); 'ltr' as Direction
);
/**
* Resolves an hour cycle using explicit time/date props, config defaults, or the library fallback. /**
*/ * Resolves a portal's `to` value using the prop, the config default, or a fallback.
export function resolveHourCycleProp( *
getTimeFormat: Getter<HourCycle | undefined>, * Default value: `"body"`
getHourCycle: Getter<HourCycle | undefined> */
): Active<HourCycle> { export const resolvePortalToProp = createPropResolver((config) => config.defaultPortalTo, 'body');
const config = getTerraConfig();
return readableActive(() => { /**
const timeFormat = getTimeFormat(); * Resolves a date order using the prop, the config default, or the library fallback.
if (timeFormat !== undefined) return timeFormatToHourCycle(timeFormat); */
export const resolveDateOrderProp = createPropResolver(
const hourCycle = getHourCycle(); (config) => config.defaultDateOrder,
if (hourCycle !== undefined) return hourCycle; DEFAULT_DATE_ORDER
);
const configTimeFormat = config.defaultTimeFormat.current;
if (configTimeFormat !== undefined) return timeFormatToHourCycle(configTimeFormat); /**
* Resolves an hour cycle using explicit time/date props, config defaults, or the library fallback.
return timeFormatToHourCycle(DEFAULT_TIME_FORMAT); */
}); export function resolveHourCycleProp(
} getTimeFormat: Getter<HourCycle | undefined>,
getHourCycle: Getter<HourCycle | undefined>
): Active<HourCycle> {
const config = getTerraConfig();
return readableActive(() => {
const timeFormat = getTimeFormat();
if (timeFormat !== undefined) return timeFormatToHourCycle(timeFormat);
const hourCycle = getHourCycle();
if (hourCycle !== undefined) return hourCycle;
const configTimeFormat = config.defaultTimeFormat.current;
if (configTimeFormat !== undefined) return timeFormatToHourCycle(configTimeFormat);
return timeFormatToHourCycle(DEFAULT_TIME_FORMAT);
});
}

@ -1,49 +1,49 @@
import { Context } from 'runed'; import { Context } from 'runed';
import { readableActive, type Active, type ActiveProps } from '../../utils'; import { readableActive, type Active, type ActiveProps } from '$terra/utils';
import type { TerraConfigPropsWithoutChildren } from './types'; import type { TerraConfigPropsWithoutChildren } from './types';
type TerraConfigStateProps = ActiveProps<TerraConfigPropsWithoutChildren>; type TerraConfigStateProps = ActiveProps<TerraConfigPropsWithoutChildren>;
export const TerraConfigContext = new Context<TerraConfigState>('TerraConfig'); export const TerraConfigContext = new Context<TerraConfigState>('TerraConfig');
export function getTerraConfig() { export function getTerraConfig() {
const fallback = new TerraConfigState(null, {}); const fallback = new TerraConfigState(null, {});
return TerraConfigContext.getOr(fallback).opts; return TerraConfigContext.getOr(fallback).opts;
} }
export function useTerraConfig(opts: TerraConfigStateProps) { export function useTerraConfig(opts: TerraConfigStateProps) {
return TerraConfigContext.set(new TerraConfigState(TerraConfigContext.getOr(null), opts)); return TerraConfigContext.set(new TerraConfigState(TerraConfigContext.getOr(null), opts));
} }
export class TerraConfigState { export class TerraConfigState {
readonly opts: Required<TerraConfigStateProps>; readonly opts: Required<TerraConfigStateProps>;
constructor(parent: TerraConfigState | null, opts: TerraConfigStateProps) { constructor(parent: TerraConfigState | null, opts: TerraConfigStateProps) {
const resolveConfigOption = createConfigResolver(parent, opts); const resolveConfigOption = createConfigResolver(parent, opts);
this.opts = { this.opts = {
defaultPortalTo: resolveConfigOption((config) => config.defaultPortalTo), defaultPortalTo: resolveConfigOption((config) => config.defaultPortalTo),
defaultLocale: resolveConfigOption((config) => config.defaultLocale), defaultLocale: resolveConfigOption((config) => config.defaultLocale),
defaultDateOrder: resolveConfigOption((config) => config.defaultDateOrder), defaultDir: resolveConfigOption((config) => config.defaultDir),
defaultTimeFormat: resolveConfigOption((config) => config.defaultTimeFormat), defaultDateOrder: resolveConfigOption((config) => config.defaultDateOrder),
}; defaultTimeFormat: resolveConfigOption((config) => config.defaultTimeFormat),
} };
} }
}
type ConfigOptionGetter<T> = (config: TerraConfigStateProps) => Active<T> | undefined;
type ConfigOptionResolver = <T>(getter: ConfigOptionGetter<T>) => Active<T | undefined>; type ConfigOptionGetter<T> = (config: TerraConfigStateProps) => Active<T> | undefined;
type ConfigOptionResolver = <T>(getter: ConfigOptionGetter<T>) => Active<T | undefined>;
function createConfigResolver(
parent: TerraConfigState | null, function createConfigResolver(
currentOpts: TerraConfigStateProps parent: TerraConfigState | null,
): ConfigOptionResolver { currentOpts: TerraConfigStateProps
return <T>(getter: ConfigOptionGetter<T>) => { ): ConfigOptionResolver {
const configOption = readableActive(() => { return <T>(getter: ConfigOptionGetter<T>) => {
const value = getter(currentOpts)?.current; const configOption = readableActive(() => {
if (value !== undefined) return value; const value = getter(currentOpts)?.current;
if (parent === null) return undefined; if (value !== undefined) return value;
return getter(parent.opts)?.current; if (parent === null) return undefined;
}); return getter(parent.opts)?.current;
return configOption; });
}; return configOption;
} };
}

@ -1,61 +1,76 @@
import type { WithChildren } from '../../utils'; import type { Direction, WithChildren } from '$terra/utils';
import type { DateOrder, HourCycle } from '$terra/adapters/dates'; import type { DateOrder, HourCycle } from '$terra/adapters/dates';
import type { PortalTarget } from '../portal/types.ts'; import type { PortalTarget } from '../portal/types.ts';
export type TerraConfigPropsWithoutChildren = { export type TerraConfigPropsWithoutChildren = {
/** /**
* The default portal `to`/target to use for the `Portal` components throughout the app. * The default portal `to`/target to use for the `Portal` components throughout the app.
*/ */
defaultPortalTo?: PortalTarget; defaultPortalTo?: PortalTarget;
/** /**
* The default locale to use for the components that support localization. * The default locale to use for the components that support localization.
*/ */
defaultLocale?: string; defaultLocale?: string;
/** /**
* The default date part order to use for date-aware components. * The default direction to use for direction-aware components.
*/ *
defaultDateOrder?: DateOrder; * @defaultValue 'ltr'
*/
/** defaultDir?: Direction;
* The default time format to use for time-aware components.
*/ /**
defaultTimeFormat?: HourCycle; * The default date part order to use for date-aware components.
}; */
defaultDateOrder?: DateOrder;
export type TerraConfigProps = WithChildren<TerraConfigPropsWithoutChildren>;
/**
export type TerraDatePresentationProps = { * The default time format to use for time-aware components.
/** */
* The locale to use for formatting date-aware components. defaultTimeFormat?: HourCycle;
* };
* @defaultValue 'en'
*/ export type TerraConfigProps = WithChildren<TerraConfigPropsWithoutChildren>;
locale?: string;
export type TerraPresentationProps = {
/** /**
* Preferred order for date segments in date-aware components. * The locale to use for formatting localized components.
* *
* @defaultValue 'DMY' * @defaultValue 'en'
*/ */
dateOrder?: DateOrder; locale?: string;
};
/**
export type TerraDateTimePresentationProps = TerraDatePresentationProps & { * The direction to use for direction-aware components.
/** *
* The format to use for displaying time segments. * @defaultValue 'ltr'
* */
* @defaultValue the locale's default time format dir?: Direction;
*/ };
hourCycle?: HourCycle;
export type TerraDatePresentationProps = TerraPresentationProps & {
/** /**
* Preferred time format for time-aware components. * Preferred order for date segments in date-aware components.
* When provided, it overrides `hourCycle`. *
* * @defaultValue 'DMY'
* @defaultValue '24h' */
*/ dateOrder?: DateOrder;
timeFormat?: HourCycle; };
};
export type TerraDateTimePresentationProps = TerraDatePresentationProps & {
/**
* The format to use for displaying time segments.
*
* @defaultValue the locale's default time format
*/
hourCycle?: HourCycle;
/**
* Preferred time format for time-aware components.
* When provided, it overrides `hourCycle`.
*
* @defaultValue '24h'
*/
timeFormat?: HourCycle;
};

@ -0,0 +1,12 @@
import { readableActive, type Active, type Direction } from '$terra/utils';
import { getTerraConfig } from '../config/terra-config.ts';
export function getDirection(): Active<Direction> {
const config = getTerraConfig();
return readableActive(() => config.defaultDir.current ?? 'ltr');
}
export function useDirection(dir?: Active<Direction | undefined>): Active<Direction> {
const defaultDirection = getDirection();
return readableActive(() => dir?.current ?? defaultDirection.current);
}

@ -0,0 +1 @@
export { getDirection, useDirection } from './direction.svelte.ts';

@ -0,0 +1 @@
export * from './exports.ts';

@ -1 +1 @@
export { default as Mounted } from "./mounted.svelte"; export { default as Mounted } from './mounted.svelte';

@ -7,7 +7,7 @@
export * as AspectRatio from './aspect-ratio'; export * as AspectRatio from './aspect-ratio';
export * as Avatar from './avatar'; export * as Avatar from './avatar';
export type { ImageLoadingStatus } from './avatar'; export type { ImageLoadingStatus } from './utils';
export * as Accordion from './accordion'; export * as Accordion from './accordion';
export * as Button from './button'; export * as Button from './button';
export * as Checkbox from './checkbox'; export * as Checkbox from './checkbox';
@ -49,6 +49,7 @@ export { TerraConfig } from './base/config/exports';
export type { export type {
TerraConfigProps, TerraConfigProps,
TerraConfigPropsWithoutChildren, TerraConfigPropsWithoutChildren,
TerraPresentationProps,
TerraDatePresentationProps, TerraDatePresentationProps,
TerraDateTimePresentationProps, TerraDateTimePresentationProps,
} from './base/config/exports'; } from './base/config/exports';
@ -63,9 +64,3 @@ export * as TimeField from './time-field';
export * as TimeRangeField from './time-range-field'; export * as TimeRangeField from './time-range-field';
export * as DateRangeField from './date-range-field'; export * as DateRangeField from './date-range-field';
export * as DateRangePicker from './date-range-picker'; export * as DateRangePicker from './date-range-picker';

Loading…
Cancel
Save

Powered by TurnKey Linux.