@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.
- package/README.md +171 -0
- package/dist/avatar.d.ts +58 -0
- package/dist/avatar.js +84 -0
- package/dist/breadcrumb.d.ts +47 -0
- package/dist/breadcrumb.js +120 -0
- package/dist/button-CYt2Zw3D.d.ts +64 -0
- package/dist/button.d.ts +54 -0
- package/dist/button.js +19 -0
- package/dist/chunk-2RIWL363.js +27 -0
- package/dist/chunk-2TQSHVXA.js +12 -0
- package/dist/chunk-7EOSDFS3.js +10 -0
- package/dist/chunk-C2HKIPLQ.js +118 -0
- package/dist/chunk-CAJLW6K2.js +16 -0
- package/dist/chunk-CZJZYN54.js +36 -0
- package/dist/chunk-EE4V6XMB.js +35 -0
- package/dist/chunk-EFHRRLAW.js +211 -0
- package/dist/chunk-GLB75NT3.js +21 -0
- package/dist/chunk-GX2YEJ43.js +36 -0
- package/dist/chunk-LTOLV67A.js +19 -0
- package/dist/chunk-LZMX27IZ.js +10 -0
- package/dist/chunk-NIIDYIZA.js +1 -0
- package/dist/chunk-Q4PPM2SU.js +97 -0
- package/dist/chunk-WVE2E37B.js +20 -0
- package/dist/chunk-XRZHP6BD.js +92 -0
- package/dist/disclosure.d.ts +72 -0
- package/dist/disclosure.js +221 -0
- package/dist/display.d.ts +111 -0
- package/dist/display.js +237 -0
- package/dist/floating.d.ts +81 -0
- package/dist/floating.js +231 -0
- package/dist/hooks.d.ts +33 -0
- package/dist/hooks.js +55 -0
- package/dist/icons/lucide.d.ts +77 -0
- package/dist/icons/lucide.js +135 -0
- package/dist/icons/mui-map.d.ts +1268 -0
- package/dist/icons/mui-map.js +352 -0
- package/dist/icons.d.ts +48 -0
- package/dist/icons.js +19 -0
- package/dist/index.d.ts +238 -3
- package/dist/index.js +242 -15
- package/dist/input.d.ts +14 -0
- package/dist/input.js +8 -0
- package/dist/layout/mui-map.d.ts +80 -0
- package/dist/layout/mui-map.js +104 -0
- package/dist/layout.d.ts +89 -0
- package/dist/layout.js +455 -0
- package/dist/lib.d.ts +11 -0
- package/dist/lib.js +6 -0
- package/dist/menu.d.ts +105 -0
- package/dist/menu.js +56 -0
- package/dist/modal.d.ts +105 -0
- package/dist/modal.js +273 -0
- package/dist/motion.css +295 -0
- package/dist/pagination.d.ts +82 -0
- package/dist/pagination.js +211 -0
- package/dist/presets.d.ts +97 -0
- package/dist/presets.js +94 -0
- package/dist/progress.d.ts +70 -0
- package/dist/progress.js +138 -0
- package/dist/react.d.ts +81 -0
- package/dist/react.js +281 -0
- package/dist/responsive-YJBnvsjt.d.ts +69 -0
- package/dist/scroll-area.d.ts +30 -0
- package/dist/scroll-area.js +80 -0
- package/dist/shared-Fkx-sjPt.d.ts +15 -0
- package/dist/spinner.d.ts +18 -0
- package/dist/spinner.js +39 -0
- package/dist/split-button.d.ts +32 -0
- package/dist/split-button.js +54 -0
- package/dist/stepper.d.ts +27 -0
- package/dist/stepper.js +104 -0
- package/dist/styles.css +24 -0
- package/dist/table.d.ts +52 -0
- package/dist/table.js +124 -0
- package/dist/theme.d.ts +111 -0
- package/dist/theme.js +414 -0
- package/dist/toast.d.ts +77 -0
- package/dist/toast.js +203 -0
- package/dist/toggle.d.ts +55 -0
- package/dist/toggle.js +88 -0
- package/dist/tokens.css +337 -0
- package/dist/transition.d.ts +51 -0
- package/dist/transition.js +79 -0
- package/dist/types-BBudf463.d.ts +39 -0
- package/dist/typography/mui-map.d.ts +91 -0
- package/dist/typography/mui-map.js +62 -0
- package/dist/typography.d.ts +90 -0
- package/dist/typography.js +193 -0
- package/dist/useSignal-C-3ZGURN.d.ts +21 -0
- package/dist/variants-De0c0ucO.d.ts +60 -0
- package/package.json +167 -12
package/dist/presets.js
ADDED
|
@@ -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 };
|
package/dist/progress.js
ADDED
|
@@ -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
|
+
};
|
package/dist/react.d.ts
ADDED
|
@@ -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
|
+
};
|