Add terra direction config support

main
dev 6 months ago
parent f1dfc893b4
commit 9f60f035c6

@ -1,17 +1,24 @@
<script lang="ts">
import type { TerraConfigProps } from '../types.ts';
import { useTerraConfig } from '../terra-config.ts';
import { readableActive } from '../../../utils';
let { children, defaultPortalTo, defaultLocale, defaultDateOrder, defaultTimeFormat }: TerraConfigProps = $props();
useTerraConfig({
defaultPortalTo: readableActive(() => defaultPortalTo),
defaultLocale: readableActive(() => defaultLocale),
defaultDateOrder: readableActive(() => defaultDateOrder),
defaultTimeFormat: readableActive(() => defaultTimeFormat),
});
</script>
{@render children?.()}
<script lang="ts">
import type { TerraConfigProps } from '../types.ts';
import { useTerraConfig } from '../terra-config.ts';
import { readableActive } from '../../../utils';
let {
children,
defaultPortalTo,
defaultLocale,
defaultDir,
defaultDateOrder,
defaultTimeFormat,
}: TerraConfigProps = $props();
useTerraConfig({
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 { getTerraConfig, TerraConfigState } from './terra-config.js';
export type {
TerraConfigProps,
TerraConfigPropsWithoutChildren,
TerraDatePresentationProps,
TerraDateTimePresentationProps,
} from './types.ts';
export { default as TerraConfig } from './components/terra-config.svelte';
export { getTerraConfig, TerraConfigState } from './terra-config.js';
export type {
TerraConfigProps,
TerraConfigPropsWithoutChildren,
TerraPresentationProps,
TerraDatePresentationProps,
TerraDateTimePresentationProps,
} from './types.ts';

@ -1,72 +1,86 @@
import { readableActive, type Active, type Getter } from '../../utils';
import { DEFAULT_DATE_ORDER, DEFAULT_TIME_FORMAT, timeFormatToHourCycle, type HourCycle } from '$terra/adapters/dates';
import { type TerraConfigState, getTerraConfig } from './terra-config.ts';
/**
* Creates a generic prop resolver that follows a standard priority chain:
* 1. The getter's prop value (if defined)
* 2. The config default value (if no getter prop value is defined)
* 3. The fallback value (if no config value found)
*/
function createPropResolver<T>(
configOption: (config: TerraConfigState['opts']) => { current: T | undefined },
fallback: T
) {
return (getProp: Getter<T | undefined>): Active<T> => {
const config = getTerraConfig();
return readableActive(() => {
// 1. return the prop's value, if provided
const propValue = getProp();
if (propValue !== undefined) return propValue;
// 2. return the resolved config option value, if available
const option = configOption(config).current;
if (option !== undefined) return option;
// 3. return the fallback if no other value is found
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 portal's `to` value using the prop, the config default, or a fallback.
*
* Default value: `"body"`
*/
export const resolvePortalToProp = createPropResolver((config) => config.defaultPortalTo, "body");
/**
* Resolves a date order using the prop, the config default, or the library fallback.
*/
export const resolveDateOrderProp = createPropResolver((config) => config.defaultDateOrder, DEFAULT_DATE_ORDER);
/**
* Resolves an hour cycle using explicit time/date props, config defaults, or the library fallback.
*/
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);
});
}
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 { type TerraConfigState, getTerraConfig } from './terra-config.ts';
/**
* Creates a generic prop resolver that follows a standard priority chain:
* 1. The getter's prop value (if defined)
* 2. The config default value (if no getter prop value is defined)
* 3. The fallback value (if no config value found)
*/
function createPropResolver<T>(
configOption: (config: TerraConfigState['opts']) => { current: T | undefined },
fallback: T
) {
return (getProp: Getter<T | undefined>): Active<T> => {
const config = getTerraConfig();
return readableActive(() => {
const propValue = getProp();
if (propValue !== undefined) return propValue;
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 direction value using the prop, the config default, or a fallback.
*
* Default value: `"ltr"`
*/
export const resolveDirectionProp = createPropResolver(
(config) => config.defaultDir,
'ltr' as Direction
);
/**
* Resolves a portal's `to` value using the prop, the config default, or a fallback.
*
* Default value: `"body"`
*/
export const resolvePortalToProp = createPropResolver((config) => config.defaultPortalTo, 'body');
/**
* Resolves a date order using the prop, the config default, or the library fallback.
*/
export const resolveDateOrderProp = createPropResolver(
(config) => config.defaultDateOrder,
DEFAULT_DATE_ORDER
);
/**
* Resolves an hour cycle using explicit time/date props, config defaults, or the library fallback.
*/
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 { readableActive, type Active, type ActiveProps } from '../../utils';
import type { TerraConfigPropsWithoutChildren } from './types';
type TerraConfigStateProps = ActiveProps<TerraConfigPropsWithoutChildren>;
export const TerraConfigContext = new Context<TerraConfigState>('TerraConfig');
export function getTerraConfig() {
const fallback = new TerraConfigState(null, {});
return TerraConfigContext.getOr(fallback).opts;
}
export function useTerraConfig(opts: TerraConfigStateProps) {
return TerraConfigContext.set(new TerraConfigState(TerraConfigContext.getOr(null), opts));
}
export class TerraConfigState {
readonly opts: Required<TerraConfigStateProps>;
constructor(parent: TerraConfigState | null, opts: TerraConfigStateProps) {
const resolveConfigOption = createConfigResolver(parent, opts);
this.opts = {
defaultPortalTo: resolveConfigOption((config) => config.defaultPortalTo),
defaultLocale: resolveConfigOption((config) => config.defaultLocale),
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>;
function createConfigResolver(
parent: TerraConfigState | null,
currentOpts: TerraConfigStateProps
): ConfigOptionResolver {
return <T>(getter: ConfigOptionGetter<T>) => {
const configOption = readableActive(() => {
const value = getter(currentOpts)?.current;
if (value !== undefined) return value;
if (parent === null) return undefined;
return getter(parent.opts)?.current;
});
return configOption;
};
}
import { Context } from 'runed';
import { readableActive, type Active, type ActiveProps } from '$terra/utils';
import type { TerraConfigPropsWithoutChildren } from './types';
type TerraConfigStateProps = ActiveProps<TerraConfigPropsWithoutChildren>;
export const TerraConfigContext = new Context<TerraConfigState>('TerraConfig');
export function getTerraConfig() {
const fallback = new TerraConfigState(null, {});
return TerraConfigContext.getOr(fallback).opts;
}
export function useTerraConfig(opts: TerraConfigStateProps) {
return TerraConfigContext.set(new TerraConfigState(TerraConfigContext.getOr(null), opts));
}
export class TerraConfigState {
readonly opts: Required<TerraConfigStateProps>;
constructor(parent: TerraConfigState | null, opts: TerraConfigStateProps) {
const resolveConfigOption = createConfigResolver(parent, opts);
this.opts = {
defaultPortalTo: resolveConfigOption((config) => config.defaultPortalTo),
defaultLocale: resolveConfigOption((config) => config.defaultLocale),
defaultDir: resolveConfigOption((config) => config.defaultDir),
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>;
function createConfigResolver(
parent: TerraConfigState | null,
currentOpts: TerraConfigStateProps
): ConfigOptionResolver {
return <T>(getter: ConfigOptionGetter<T>) => {
const configOption = readableActive(() => {
const value = getter(currentOpts)?.current;
if (value !== undefined) return value;
if (parent === null) return undefined;
return getter(parent.opts)?.current;
});
return configOption;
};
}

@ -1,61 +1,76 @@
import type { WithChildren } from '../../utils';
import type { DateOrder, HourCycle } from '$terra/adapters/dates';
import type { PortalTarget } from '../portal/types.ts';
export type TerraConfigPropsWithoutChildren = {
/**
* The default portal `to`/target to use for the `Portal` components throughout the app.
*/
defaultPortalTo?: PortalTarget;
/**
* The default locale to use for the components that support localization.
*/
defaultLocale?: string;
/**
* The default date part order to use for date-aware components.
*/
defaultDateOrder?: DateOrder;
/**
* The default time format to use for time-aware components.
*/
defaultTimeFormat?: HourCycle;
};
export type TerraConfigProps = WithChildren<TerraConfigPropsWithoutChildren>;
export type TerraDatePresentationProps = {
/**
* The locale to use for formatting date-aware components.
*
* @defaultValue 'en'
*/
locale?: string;
/**
* Preferred order for date segments in date-aware components.
*
* @defaultValue 'DMY'
*/
dateOrder?: DateOrder;
};
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;
};
import type { Direction, WithChildren } from '$terra/utils';
import type { DateOrder, HourCycle } from '$terra/adapters/dates';
import type { PortalTarget } from '../portal/types.ts';
export type TerraConfigPropsWithoutChildren = {
/**
* The default portal `to`/target to use for the `Portal` components throughout the app.
*/
defaultPortalTo?: PortalTarget;
/**
* The default locale to use for the components that support localization.
*/
defaultLocale?: string;
/**
* The default direction to use for direction-aware components.
*
* @defaultValue 'ltr'
*/
defaultDir?: Direction;
/**
* The default date part order to use for date-aware components.
*/
defaultDateOrder?: DateOrder;
/**
* The default time format to use for time-aware components.
*/
defaultTimeFormat?: HourCycle;
};
export type TerraConfigProps = WithChildren<TerraConfigPropsWithoutChildren>;
export type TerraPresentationProps = {
/**
* The locale to use for formatting localized components.
*
* @defaultValue 'en'
*/
locale?: string;
/**
* The direction to use for direction-aware components.
*
* @defaultValue 'ltr'
*/
dir?: Direction;
};
export type TerraDatePresentationProps = TerraPresentationProps & {
/**
* Preferred order for date segments in date-aware components.
*
* @defaultValue 'DMY'
*/
dateOrder?: DateOrder;
};
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 Avatar from './avatar';
export type { ImageLoadingStatus } from './avatar';
export type { ImageLoadingStatus } from './utils';
export * as Accordion from './accordion';
export * as Button from './button';
export * as Checkbox from './checkbox';
@ -49,6 +49,7 @@ export { TerraConfig } from './base/config/exports';
export type {
TerraConfigProps,
TerraConfigPropsWithoutChildren,
TerraPresentationProps,
TerraDatePresentationProps,
TerraDateTimePresentationProps,
} from './base/config/exports';
@ -63,9 +64,3 @@ export * as TimeField from './time-field';
export * as TimeRangeField from './time-range-field';
export * as DateRangeField from './date-range-field';
export * as DateRangePicker from './date-range-picker';

Loading…
Cancel
Save

Powered by TurnKey Linux.