@azmr/ui 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +171 -0
  2. package/dist/avatar.d.ts +58 -0
  3. package/dist/avatar.js +84 -0
  4. package/dist/breadcrumb.d.ts +47 -0
  5. package/dist/breadcrumb.js +120 -0
  6. package/dist/button-CYt2Zw3D.d.ts +64 -0
  7. package/dist/button.d.ts +54 -0
  8. package/dist/button.js +19 -0
  9. package/dist/chunk-2RIWL363.js +27 -0
  10. package/dist/chunk-2TQSHVXA.js +12 -0
  11. package/dist/chunk-7EOSDFS3.js +10 -0
  12. package/dist/chunk-C2HKIPLQ.js +118 -0
  13. package/dist/chunk-CAJLW6K2.js +16 -0
  14. package/dist/chunk-CZJZYN54.js +36 -0
  15. package/dist/chunk-EE4V6XMB.js +35 -0
  16. package/dist/chunk-EFHRRLAW.js +211 -0
  17. package/dist/chunk-GLB75NT3.js +21 -0
  18. package/dist/chunk-GX2YEJ43.js +36 -0
  19. package/dist/chunk-LTOLV67A.js +19 -0
  20. package/dist/chunk-LZMX27IZ.js +10 -0
  21. package/dist/chunk-NIIDYIZA.js +1 -0
  22. package/dist/chunk-Q4PPM2SU.js +97 -0
  23. package/dist/chunk-WVE2E37B.js +20 -0
  24. package/dist/chunk-XRZHP6BD.js +92 -0
  25. package/dist/disclosure.d.ts +72 -0
  26. package/dist/disclosure.js +221 -0
  27. package/dist/display.d.ts +111 -0
  28. package/dist/display.js +237 -0
  29. package/dist/floating.d.ts +81 -0
  30. package/dist/floating.js +231 -0
  31. package/dist/hooks.d.ts +33 -0
  32. package/dist/hooks.js +55 -0
  33. package/dist/icons/lucide.d.ts +77 -0
  34. package/dist/icons/lucide.js +135 -0
  35. package/dist/icons/mui-map.d.ts +1268 -0
  36. package/dist/icons/mui-map.js +352 -0
  37. package/dist/icons.d.ts +48 -0
  38. package/dist/icons.js +19 -0
  39. package/dist/index.d.ts +238 -3
  40. package/dist/index.js +242 -15
  41. package/dist/input.d.ts +14 -0
  42. package/dist/input.js +8 -0
  43. package/dist/layout/mui-map.d.ts +80 -0
  44. package/dist/layout/mui-map.js +104 -0
  45. package/dist/layout.d.ts +89 -0
  46. package/dist/layout.js +455 -0
  47. package/dist/lib.d.ts +11 -0
  48. package/dist/lib.js +6 -0
  49. package/dist/menu.d.ts +105 -0
  50. package/dist/menu.js +56 -0
  51. package/dist/modal.d.ts +105 -0
  52. package/dist/modal.js +273 -0
  53. package/dist/motion.css +295 -0
  54. package/dist/pagination.d.ts +82 -0
  55. package/dist/pagination.js +211 -0
  56. package/dist/presets.d.ts +97 -0
  57. package/dist/presets.js +94 -0
  58. package/dist/progress.d.ts +70 -0
  59. package/dist/progress.js +138 -0
  60. package/dist/react.d.ts +81 -0
  61. package/dist/react.js +281 -0
  62. package/dist/responsive-YJBnvsjt.d.ts +69 -0
  63. package/dist/scroll-area.d.ts +30 -0
  64. package/dist/scroll-area.js +80 -0
  65. package/dist/shared-Fkx-sjPt.d.ts +15 -0
  66. package/dist/spinner.d.ts +18 -0
  67. package/dist/spinner.js +39 -0
  68. package/dist/split-button.d.ts +32 -0
  69. package/dist/split-button.js +54 -0
  70. package/dist/stepper.d.ts +27 -0
  71. package/dist/stepper.js +104 -0
  72. package/dist/styles.css +24 -0
  73. package/dist/table.d.ts +52 -0
  74. package/dist/table.js +124 -0
  75. package/dist/theme.d.ts +111 -0
  76. package/dist/theme.js +414 -0
  77. package/dist/toast.d.ts +77 -0
  78. package/dist/toast.js +203 -0
  79. package/dist/toggle.d.ts +55 -0
  80. package/dist/toggle.js +88 -0
  81. package/dist/tokens.css +337 -0
  82. package/dist/transition.d.ts +51 -0
  83. package/dist/transition.js +79 -0
  84. package/dist/types-BBudf463.d.ts +39 -0
  85. package/dist/typography/mui-map.d.ts +91 -0
  86. package/dist/typography/mui-map.js +62 -0
  87. package/dist/typography.d.ts +90 -0
  88. package/dist/typography.js +193 -0
  89. package/dist/useSignal-C-3ZGURN.d.ts +21 -0
  90. package/dist/variants-De0c0ucO.d.ts +60 -0
  91. package/package.json +167 -12
@@ -0,0 +1,94 @@
1
+ import {
2
+ motionPresets
3
+ } from "./chunk-GLB75NT3.js";
4
+ import {
5
+ cn
6
+ } from "./chunk-7EOSDFS3.js";
7
+
8
+ // src/motion/presets/animated-check.tsx
9
+ import { forwardRef } from "react";
10
+ import { jsx } from "react/jsx-runtime";
11
+ var AnimatedCheck = forwardRef(function AnimatedCheck2({ size = 24, label, className, ...props }, ref) {
12
+ return (
13
+ // biome-ignore lint/a11y/noSvgWithoutTitle: decorative (aria-hidden) by default, or role="img" with an aria-label when `label` is set
14
+ /* @__PURE__ */ jsx(
15
+ "svg",
16
+ {
17
+ ref,
18
+ viewBox: "0 0 24 24",
19
+ width: size,
20
+ height: size,
21
+ fill: "none",
22
+ className: cn(motionPresets.checkDraw, className),
23
+ ...label ? { role: "img", "aria-label": label } : { "aria-hidden": true },
24
+ ...props,
25
+ children: /* @__PURE__ */ jsx(
26
+ "path",
27
+ {
28
+ d: "M5 13l4 4L19 7",
29
+ pathLength: 24,
30
+ stroke: "currentColor",
31
+ strokeWidth: 2,
32
+ strokeLinecap: "round",
33
+ strokeLinejoin: "round"
34
+ }
35
+ )
36
+ }
37
+ )
38
+ );
39
+ });
40
+
41
+ // src/motion/presets/collapse.tsx
42
+ import { forwardRef as forwardRef2, version } from "react";
43
+ import { jsx as jsx2 } from "react/jsx-runtime";
44
+ function inertProps(reactVersion = version) {
45
+ const major = Number.parseInt(reactVersion, 10);
46
+ return major < 19 ? { inert: "" } : { inert: true };
47
+ }
48
+ var Collapse = forwardRef2(function Collapse2({ open, className, children, ...props }, ref) {
49
+ return /* @__PURE__ */ jsx2(
50
+ "div",
51
+ {
52
+ ref,
53
+ "data-state": open ? "open" : "closed",
54
+ ...open ? {} : { ...inertProps(), "aria-hidden": true },
55
+ className: cn(motionPresets.collapse.root, className),
56
+ ...props,
57
+ children: /* @__PURE__ */ jsx2("div", { className: motionPresets.collapse.inner, children })
58
+ }
59
+ );
60
+ });
61
+
62
+ // src/motion/presets/stagger.tsx
63
+ import {
64
+ Children,
65
+ cloneElement,
66
+ createElement,
67
+ isValidElement
68
+ } from "react";
69
+ function Stagger({ as = "div", max = 12, className, children, ...props }) {
70
+ let index = 0;
71
+ const items = Children.toArray(children).map((child) => {
72
+ if (!isValidElement(child)) return child;
73
+ const element = child;
74
+ const style = {
75
+ ...element.props.style,
76
+ "--stagger-index": Math.min(index++, max)
77
+ };
78
+ return cloneElement(element, {
79
+ className: cn(element.props.className, motionPresets.stagger.item),
80
+ style
81
+ });
82
+ });
83
+ return createElement(
84
+ as,
85
+ { className: cn(motionPresets.stagger.container, className), ...props },
86
+ items
87
+ );
88
+ }
89
+ export {
90
+ AnimatedCheck,
91
+ Collapse,
92
+ Stagger,
93
+ motionPresets
94
+ };
@@ -0,0 +1,70 @@
1
+ import { ForwardRefExoticComponent, ComponentPropsWithoutRef, RefAttributes } from 'react';
2
+ import { Progress as Progress$1 } from '@base-ui/react/progress';
3
+
4
+ /**
5
+ * The indicator colour per tone. `token` names the design token the colour comes from; a test
6
+ * checks it has 3:1 contrast against both the track and the page in light and dark. Warning uses
7
+ * `warning-text`, not the yellow `warning` surface colour, which is far too faint on a light track.
8
+ */
9
+ declare const progressTones: {
10
+ readonly default: {
11
+ readonly token: "primary";
12
+ readonly indicator: "bg-primary";
13
+ readonly ring: "text-primary";
14
+ };
15
+ readonly success: {
16
+ readonly token: "success";
17
+ readonly indicator: "bg-success";
18
+ readonly ring: "text-success";
19
+ };
20
+ readonly warning: {
21
+ readonly token: "warning-text";
22
+ readonly indicator: "bg-warning-text";
23
+ readonly ring: "text-warning-text";
24
+ };
25
+ readonly destructive: {
26
+ readonly token: "destructive";
27
+ readonly indicator: "bg-destructive";
28
+ readonly ring: "text-destructive";
29
+ };
30
+ readonly info: {
31
+ readonly token: "info";
32
+ readonly indicator: "bg-info";
33
+ readonly ring: "text-info";
34
+ };
35
+ };
36
+ type ProgressTone = keyof typeof progressTones;
37
+ declare const LINEAR_HEIGHT: {
38
+ readonly sm: "h-1.5";
39
+ readonly md: "h-2";
40
+ readonly lg: "h-3";
41
+ };
42
+ interface ProgressOwnProps {
43
+ /** The current value, or `null` when the amount is not known (indeterminate). Default range is 0 to 100. */
44
+ value: number | null;
45
+ min?: number;
46
+ max?: number;
47
+ /** `linear` (default) is a bar; `circular` is a ring. */
48
+ variant?: "linear" | "circular";
49
+ size?: keyof typeof LINEAR_HEIGHT;
50
+ tone?: ProgressTone;
51
+ /** Show the percentage as text next to (linear) or inside (circular) the indicator. */
52
+ showValue?: boolean;
53
+ /** The text a screen reader announces, instead of the default percentage. */
54
+ valueText?: (value: number | null) => string;
55
+ className?: string;
56
+ }
57
+ /** A progress bar needs a name: a label, or the id of the heading that describes it. */
58
+ type Named = {
59
+ "aria-label": string;
60
+ } | {
61
+ "aria-labelledby": string;
62
+ };
63
+ type ProgressProps = ProgressOwnProps & Named & Omit<ComponentPropsWithoutRef<typeof Progress$1.Root>, keyof ProgressOwnProps | "aria-label" | "aria-labelledby" | "getAriaValueText" | "children">;
64
+ /**
65
+ * A linear or circular progress indicator with a required accessible name. `value={null}` is
66
+ * indeterminate. Built on Base UI, so the progressbar semantics and `aria-valuenow` are native.
67
+ */
68
+ declare const Progress: ForwardRefExoticComponent<ProgressProps & RefAttributes<HTMLDivElement>>;
69
+
70
+ export { Progress, type ProgressOwnProps, type ProgressProps, type ProgressTone, progressTones };
@@ -0,0 +1,138 @@
1
+ "use client";
2
+ import {
3
+ cn
4
+ } from "./chunk-2TQSHVXA.js";
5
+
6
+ // src/components/progress/progress.tsx
7
+ import {
8
+ forwardRef
9
+ } from "react";
10
+
11
+ // src/primitives/progress.ts
12
+ import { Progress } from "@base-ui/react/progress";
13
+
14
+ // src/components/progress/progress.tsx
15
+ import { jsx, jsxs } from "react/jsx-runtime";
16
+ var progressTones = {
17
+ default: { token: "primary", indicator: "bg-primary", ring: "text-primary" },
18
+ success: { token: "success", indicator: "bg-success", ring: "text-success" },
19
+ warning: { token: "warning-text", indicator: "bg-warning-text", ring: "text-warning-text" },
20
+ destructive: { token: "destructive", indicator: "bg-destructive", ring: "text-destructive" },
21
+ info: { token: "info", indicator: "bg-info", ring: "text-info" }
22
+ };
23
+ var LINEAR_HEIGHT = { sm: "h-1.5", md: "h-2", lg: "h-3" };
24
+ var CIRCULAR_SIZE = { sm: "size-6", md: "size-10", lg: "size-16" };
25
+ var RADIUS = 16;
26
+ var CIRCUMFERENCE = Number((2 * Math.PI * RADIUS).toFixed(2));
27
+ var ProgressImpl = forwardRef(function Progress2({
28
+ value,
29
+ min = 0,
30
+ max = 100,
31
+ variant = "linear",
32
+ size = "md",
33
+ tone = "default",
34
+ showValue = false,
35
+ valueText,
36
+ className,
37
+ ...props
38
+ }, ref) {
39
+ const current = value === null || !Number.isFinite(value) ? null : Math.min(max, Math.max(min, value));
40
+ const indeterminate = current === null;
41
+ const percent = indeterminate ? 0 : (current - min) / (max - min || 1) * 100;
42
+ const palette = progressTones[tone];
43
+ const text = indeterminate ? "" : `${Math.round(percent)}%`;
44
+ const root = {
45
+ ref,
46
+ value: current,
47
+ min,
48
+ max,
49
+ getAriaValueText: valueText ? (_formatted, v) => valueText(v) : void 0,
50
+ ...props
51
+ };
52
+ if (variant === "circular") {
53
+ const filled = Number((CIRCUMFERENCE * (indeterminate ? 25 : percent) / 100).toFixed(2));
54
+ return /* @__PURE__ */ jsxs(
55
+ Progress.Root,
56
+ {
57
+ ...root,
58
+ className: cn(
59
+ "relative inline-flex shrink-0 items-center justify-center",
60
+ CIRCULAR_SIZE[size],
61
+ className
62
+ ),
63
+ children: [
64
+ /* @__PURE__ */ jsxs(
65
+ "svg",
66
+ {
67
+ viewBox: "0 0 36 36",
68
+ fill: "none",
69
+ "aria-hidden": "true",
70
+ "data-motion": indeterminate ? "essential" : void 0,
71
+ className: cn("size-full -rotate-90", palette.ring, indeterminate && "animate-spin"),
72
+ children: [
73
+ /* @__PURE__ */ jsx("circle", { cx: "18", cy: "18", r: RADIUS, strokeWidth: "4", className: "stroke-muted" }),
74
+ /* @__PURE__ */ jsx(
75
+ "circle",
76
+ {
77
+ "data-arc": "",
78
+ cx: "18",
79
+ cy: "18",
80
+ r: RADIUS,
81
+ stroke: "currentColor",
82
+ strokeWidth: "4",
83
+ strokeLinecap: "round",
84
+ strokeDasharray: `${filled} ${CIRCUMFERENCE}`
85
+ }
86
+ )
87
+ ]
88
+ }
89
+ ),
90
+ showValue && !indeterminate && /* @__PURE__ */ jsx(
91
+ "span",
92
+ {
93
+ "data-progress-value": "",
94
+ "aria-hidden": "true",
95
+ className: "absolute text-xs font-medium tabular-nums",
96
+ children: text
97
+ }
98
+ )
99
+ ]
100
+ }
101
+ );
102
+ }
103
+ return /* @__PURE__ */ jsxs(Progress.Root, { ...root, className: cn("flex w-full items-center gap-3", className), children: [
104
+ /* @__PURE__ */ jsx(
105
+ Progress.Track,
106
+ {
107
+ className: cn("relative w-full overflow-hidden rounded-full bg-muted", LINEAR_HEIGHT[size]),
108
+ children: /* @__PURE__ */ jsx(
109
+ Progress.Indicator,
110
+ {
111
+ "data-motion": indeterminate ? "essential" : void 0,
112
+ className: cn(
113
+ "h-full rounded-full transition-[width] duration-(--motion-duration-base) ease-standard",
114
+ palette.indicator,
115
+ // Unknown amount: a partial bar that pulses. Under reduced motion it keeps pulsing
116
+ // (opacity, not movement) because a still full-width bar would read as complete.
117
+ indeterminate && "w-2/5 animate-pulse"
118
+ )
119
+ }
120
+ )
121
+ }
122
+ ),
123
+ showValue && !indeterminate && /* @__PURE__ */ jsx(
124
+ "span",
125
+ {
126
+ "data-progress-value": "",
127
+ "aria-hidden": "true",
128
+ className: "min-w-10 text-right text-sm tabular-nums text-muted-foreground",
129
+ children: text
130
+ }
131
+ )
132
+ ] });
133
+ });
134
+ var Progress3 = ProgressImpl;
135
+ export {
136
+ Progress3 as Progress,
137
+ progressTones
138
+ };
@@ -0,0 +1,81 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ interface LocaleMessages {
5
+ close: string;
6
+ cancel: string;
7
+ confirm: string;
8
+ loading: string;
9
+ noResults: string;
10
+ clear: string;
11
+ search: string;
12
+ previousPage: string;
13
+ nextPage: string;
14
+ }
15
+ type WeekStart = 0 | 1 | 2 | 3 | 4 | 5 | 6;
16
+ interface LocaleOptions {
17
+ /** BCP 47 tag. Default `en-NZ`. */
18
+ locale?: string;
19
+ /** ISO 4217 code. Default `NZD`. */
20
+ currency?: string;
21
+ /** 0 = Sunday ... 6 = Saturday. Default 1 (Monday). */
22
+ weekStartsOn?: WeekStart;
23
+ /** IANA zone used to display instants. Default: the runtime's zone. */
24
+ timeZone?: string;
25
+ messages?: Partial<LocaleMessages>;
26
+ }
27
+ type DateInput = Date | number | string | null | undefined;
28
+ interface Locale {
29
+ readonly locale: string;
30
+ readonly currency: string;
31
+ readonly weekStartsOn: WeekStart;
32
+ readonly timeZone: string | undefined;
33
+ readonly messages: LocaleMessages;
34
+ formatNumber(value: number | null | undefined, options?: Intl.NumberFormatOptions): string;
35
+ formatCurrency(value: number | null | undefined, options?: Intl.NumberFormatOptions): string;
36
+ /** Date-only strings (`2026-10-15`) print that calendar day in every time zone. */
37
+ formatDate(value: DateInput, options?: Intl.DateTimeFormatOptions): string;
38
+ formatDateTime(value: DateInput, options?: Intl.DateTimeFormatOptions): string;
39
+ }
40
+
41
+ type LocaleProviderProps = LocaleOptions & {
42
+ children: ReactNode;
43
+ };
44
+ /** Provide locale, currency, week start, time zone and component strings to everything below. */
45
+ declare function LocaleProvider({ children, locale, currency, weekStartsOn, timeZone, messages, }: LocaleProviderProps): react.JSX.Element;
46
+ declare function useLocale(): Locale;
47
+
48
+ declare const THEME_PREFERENCES: readonly ["light", "dark", "system"];
49
+ type ThemePreference = (typeof THEME_PREFERENCES)[number];
50
+ declare const DENSITIES: readonly ["compact", "comfortable", "spacious"];
51
+ type Density = (typeof DENSITIES)[number];
52
+
53
+ interface ThemeContextValue {
54
+ /** The stored preference: `light`, `dark` or `system`. */
55
+ theme: ThemePreference;
56
+ /** What is actually showing: `system` resolved against the operating system. */
57
+ resolvedTheme: "light" | "dark";
58
+ setTheme: (theme: ThemePreference) => void;
59
+ density: Density;
60
+ setDensity: (density: Density) => void;
61
+ }
62
+ interface ThemeProviderProps {
63
+ children: ReactNode;
64
+ /** Initial preference, normally read from the cookie on the server. Default `system`. */
65
+ defaultTheme?: ThemePreference;
66
+ /** Initial density, normally read from the cookie on the server. Default `comfortable`. */
67
+ defaultDensity?: Density;
68
+ /** Remember changes in a cookie so the server can render them next time. Default true. */
69
+ persist?: boolean;
70
+ /** Element that receives `data-theme` / `data-density`. Default `<html>`. */
71
+ target?: HTMLElement;
72
+ }
73
+ /**
74
+ * Applies `data-theme` and `data-density` to `<html>`, tracks the operating system while the
75
+ * preference is `system`, and remembers changes in a cookie. The server should render the same
76
+ * attributes with `htmlAttributes.fromCookies(...)` so there is no flash on first paint.
77
+ */
78
+ declare function ThemeProvider({ children, defaultTheme, defaultDensity, persist, target, }: ThemeProviderProps): react.JSX.Element;
79
+ declare function useTheme(): ThemeContextValue;
80
+
81
+ export { LocaleProvider, type LocaleProviderProps, type ThemeContextValue, ThemeProvider, type ThemeProviderProps, useLocale, useTheme };
package/dist/react.js ADDED
@@ -0,0 +1,281 @@
1
+ "use client";
2
+
3
+ // src/react/LocaleProvider.tsx
4
+ import { createContext, useContext, useMemo } from "react";
5
+
6
+ // src/theme/locale.ts
7
+ var DEFAULT_MESSAGES = Object.freeze({
8
+ close: "Close",
9
+ cancel: "Cancel",
10
+ confirm: "Confirm",
11
+ loading: "Loading",
12
+ noResults: "No results found",
13
+ clear: "Clear",
14
+ search: "Search",
15
+ previousPage: "Previous page",
16
+ nextPage: "Next page"
17
+ });
18
+ var DATE_ONLY = /^(\d{4})-(\d{2})-(\d{2})$/;
19
+ var FIELD_OPTIONS = [
20
+ "weekday",
21
+ "era",
22
+ "year",
23
+ "month",
24
+ "day",
25
+ "dayPeriod",
26
+ "hour",
27
+ "minute",
28
+ "second",
29
+ "fractionalSecondDigits",
30
+ "timeZoneName"
31
+ ];
32
+ function toInstant(value) {
33
+ if (value === null || value === void 0 || value === "") return void 0;
34
+ if (typeof value === "string") {
35
+ const match = DATE_ONLY.exec(value);
36
+ if (match) {
37
+ const date2 = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3])));
38
+ return Number.isNaN(date2.getTime()) ? void 0 : { date: date2, calendarDay: true };
39
+ }
40
+ }
41
+ const date = value instanceof Date ? value : new Date(value);
42
+ return Number.isNaN(date.getTime()) ? void 0 : { date, calendarDay: false };
43
+ }
44
+ function createLocale(options = {}) {
45
+ const locale = options.locale ?? "en-NZ";
46
+ const currency = options.currency ?? "NZD";
47
+ const weekStartsOn = options.weekStartsOn ?? 1;
48
+ const timeZone = options.timeZone;
49
+ try {
50
+ Intl.getCanonicalLocales(locale);
51
+ } catch {
52
+ throw new Error(`createLocale: invalid locale ${JSON.stringify(locale)}`);
53
+ }
54
+ if (!/^[A-Z]{3}$/.test(currency)) {
55
+ throw new Error(
56
+ `createLocale: currency must be an ISO 4217 code like "NZD", got ${JSON.stringify(currency)}`
57
+ );
58
+ }
59
+ if (timeZone !== void 0) {
60
+ try {
61
+ new Intl.DateTimeFormat(locale, { timeZone });
62
+ } catch {
63
+ throw new Error(`createLocale: unknown time zone ${JSON.stringify(timeZone)}`);
64
+ }
65
+ }
66
+ if (!Number.isInteger(weekStartsOn) || weekStartsOn < 0 || weekStartsOn > 6) {
67
+ throw new Error(
68
+ `createLocale: weekStartsOn must be an integer from 0 to 6, got ${weekStartsOn}`
69
+ );
70
+ }
71
+ const numberFormats = /* @__PURE__ */ new Map();
72
+ const dateFormats = /* @__PURE__ */ new Map();
73
+ const numberFormat = (opts) => {
74
+ const key = JSON.stringify(opts);
75
+ let format = numberFormats.get(key);
76
+ if (!format) {
77
+ format = new Intl.NumberFormat(locale, opts);
78
+ numberFormats.set(key, format);
79
+ }
80
+ return format;
81
+ };
82
+ const dateFormat = (opts) => {
83
+ const key = JSON.stringify(opts);
84
+ let format = dateFormats.get(key);
85
+ if (!format) {
86
+ format = new Intl.DateTimeFormat(locale, opts);
87
+ dateFormats.set(key, format);
88
+ }
89
+ return format;
90
+ };
91
+ const formatDateWith = (defaults) => (value, opts = {}) => {
92
+ const instant = toInstant(value);
93
+ if (!instant) return "";
94
+ const zone = instant.calendarDay ? "UTC" : opts.timeZone ?? timeZone;
95
+ const customised = FIELD_OPTIONS.some((key) => key in opts) || "dateStyle" in opts || "timeStyle" in opts;
96
+ return dateFormat({
97
+ ...customised ? {} : defaults,
98
+ ...opts,
99
+ ...zone ? { timeZone: zone } : {}
100
+ }).format(instant.date);
101
+ };
102
+ const isNumber = (value) => typeof value === "number" && Number.isFinite(value);
103
+ return {
104
+ locale,
105
+ currency,
106
+ weekStartsOn,
107
+ timeZone,
108
+ messages: { ...DEFAULT_MESSAGES, ...options.messages },
109
+ formatNumber: (value, opts = {}) => isNumber(value) ? numberFormat(opts).format(value) : "",
110
+ formatCurrency: (value, opts = {}) => isNumber(value) ? numberFormat({ style: "currency", currency, ...opts }).format(value) : "",
111
+ formatDate: formatDateWith({ dateStyle: "medium" }),
112
+ formatDateTime: formatDateWith({ dateStyle: "medium", timeStyle: "short" })
113
+ };
114
+ }
115
+
116
+ // src/react/LocaleProvider.tsx
117
+ import { jsx } from "react/jsx-runtime";
118
+ var LocaleContext = createContext(createLocale());
119
+ function LocaleProvider({
120
+ children,
121
+ locale,
122
+ currency,
123
+ weekStartsOn,
124
+ timeZone,
125
+ messages
126
+ }) {
127
+ const messagesKey = JSON.stringify(messages ?? {});
128
+ const value = useMemo(
129
+ () => createLocale({
130
+ locale,
131
+ currency,
132
+ weekStartsOn,
133
+ timeZone,
134
+ messages: JSON.parse(messagesKey)
135
+ }),
136
+ [locale, currency, weekStartsOn, timeZone, messagesKey]
137
+ );
138
+ return /* @__PURE__ */ jsx(LocaleContext.Provider, { value, children });
139
+ }
140
+ function useLocale() {
141
+ return useContext(LocaleContext);
142
+ }
143
+
144
+ // src/react/ThemeProvider.tsx
145
+ import {
146
+ createContext as createContext2,
147
+ useCallback,
148
+ useContext as useContext2,
149
+ useEffect,
150
+ useMemo as useMemo2,
151
+ useState,
152
+ useSyncExternalStore
153
+ } from "react";
154
+
155
+ // src/theme/preferences.ts
156
+ var THEME_PREFERENCES = ["light", "dark", "system"];
157
+ var DENSITIES = ["compact", "comfortable", "spacious"];
158
+ var THEME_COOKIE = "azmr-theme";
159
+ var DENSITY_COOKIE = "azmr-density";
160
+ var ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;
161
+ function parseThemePreference(value, fallback = "system") {
162
+ return typeof value === "string" && THEME_PREFERENCES.includes(value) ? value : fallback;
163
+ }
164
+ function parseDensity(value, fallback = "comfortable") {
165
+ return typeof value === "string" && DENSITIES.includes(value) ? value : fallback;
166
+ }
167
+ function readCookie(header, name) {
168
+ if (!header) return void 0;
169
+ for (const part of header.split(";")) {
170
+ const trimmed = part.trim();
171
+ const eq = trimmed.indexOf("=");
172
+ if (eq === -1 || trimmed.slice(0, eq) !== name) continue;
173
+ const raw = trimmed.slice(eq + 1);
174
+ try {
175
+ return decodeURIComponent(raw);
176
+ } catch {
177
+ return raw;
178
+ }
179
+ }
180
+ return void 0;
181
+ }
182
+ var COOKIE_NAME = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
183
+ var COOKIE_VALUE = /^[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]*$/;
184
+ function serializeCookie(name, value, options = {}) {
185
+ if (!COOKIE_NAME.test(name)) throw new Error(`Invalid cookie name: ${JSON.stringify(name)}`);
186
+ if (!COOKIE_VALUE.test(value)) throw new Error(`Invalid cookie value for "${name}"`);
187
+ const secure = options.secure ? "; Secure" : "";
188
+ return `${name}=${value}; Path=/; Max-Age=${ONE_YEAR_SECONDS}; SameSite=Lax${secure}`;
189
+ }
190
+ function buildHtmlAttributes({ theme, density }) {
191
+ const attributes = {};
192
+ if (theme === "light" || theme === "dark") attributes["data-theme"] = theme;
193
+ if (density === "compact" || density === "spacious") attributes["data-density"] = density;
194
+ return attributes;
195
+ }
196
+ var htmlAttributes = Object.assign(buildHtmlAttributes, {
197
+ fromCookies(header) {
198
+ return buildHtmlAttributes({
199
+ theme: parseThemePreference(readCookie(header, THEME_COOKIE)),
200
+ density: parseDensity(readCookie(header, DENSITY_COOKIE))
201
+ });
202
+ }
203
+ });
204
+
205
+ // src/react/ThemeProvider.tsx
206
+ import { jsx as jsx2 } from "react/jsx-runtime";
207
+ var DARK_QUERY = "(prefers-color-scheme: dark)";
208
+ function subscribeToSystem(onChange) {
209
+ const query = typeof window === "undefined" ? void 0 : window.matchMedia?.(DARK_QUERY);
210
+ if (!query) return () => {
211
+ };
212
+ query.addEventListener("change", onChange);
213
+ return () => query.removeEventListener("change", onChange);
214
+ }
215
+ var systemPrefersDark = () => window.matchMedia?.(DARK_QUERY).matches ?? false;
216
+ var serverSystemPrefersDark = () => false;
217
+ function writeCookie(name, value) {
218
+ document.cookie = serializeCookie(name, value, { secure: window.location.protocol === "https:" });
219
+ }
220
+ var ThemeContext = createContext2(void 0);
221
+ function ThemeProvider({
222
+ children,
223
+ defaultTheme = "system",
224
+ defaultDensity = "comfortable",
225
+ persist = true,
226
+ target
227
+ }) {
228
+ const [theme, setThemeState] = useState(
229
+ () => parseThemePreference(defaultTheme)
230
+ );
231
+ const [density, setDensityState] = useState(() => parseDensity(defaultDensity));
232
+ const systemDark = useSyncExternalStore(
233
+ subscribeToSystem,
234
+ systemPrefersDark,
235
+ serverSystemPrefersDark
236
+ );
237
+ const resolvedTheme = theme === "system" ? systemDark ? "dark" : "light" : theme;
238
+ useEffect(() => {
239
+ const element = target ?? document.documentElement;
240
+ if (theme === "system") element.removeAttribute("data-theme");
241
+ else element.setAttribute("data-theme", theme);
242
+ if (density === "comfortable") element.removeAttribute("data-density");
243
+ else element.setAttribute("data-density", density);
244
+ return () => {
245
+ element.removeAttribute("data-theme");
246
+ element.removeAttribute("data-density");
247
+ };
248
+ }, [theme, density, target]);
249
+ const setTheme = useCallback(
250
+ (next) => {
251
+ const safe = parseThemePreference(next);
252
+ setThemeState(safe);
253
+ if (persist) writeCookie(THEME_COOKIE, safe);
254
+ },
255
+ [persist]
256
+ );
257
+ const setDensity = useCallback(
258
+ (next) => {
259
+ const safe = parseDensity(next);
260
+ setDensityState(safe);
261
+ if (persist) writeCookie(DENSITY_COOKIE, safe);
262
+ },
263
+ [persist]
264
+ );
265
+ const value = useMemo2(
266
+ () => ({ theme, resolvedTheme, setTheme, density, setDensity }),
267
+ [theme, resolvedTheme, setTheme, density, setDensity]
268
+ );
269
+ return /* @__PURE__ */ jsx2(ThemeContext.Provider, { value, children });
270
+ }
271
+ function useTheme() {
272
+ const context = useContext2(ThemeContext);
273
+ if (!context) throw new Error("useTheme must be used within a <ThemeProvider>");
274
+ return context;
275
+ }
276
+ export {
277
+ LocaleProvider,
278
+ ThemeProvider,
279
+ useLocale,
280
+ useTheme
281
+ };