@patternmode/status 0.1.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 ADDED
@@ -0,0 +1,29 @@
1
+ # @patternmode/status
2
+
3
+ Animated discrete progress marks for React.
4
+
5
+ ```tsx
6
+ import { StatusMark } from "@patternmode/status";
7
+ import "@patternmode/status/styles.css";
8
+
9
+ export function Example() {
10
+ return (
11
+ <div>
12
+ <StatusMark value={75} label="Almost complete" color="#315c4b" trackColor="#edeae2" />
13
+ <StatusMark status="null" label="Not measured yet" />
14
+ </div>
15
+ );
16
+ }
17
+ ```
18
+
19
+ `StatusMark` is for compact, discrete progress communication: null, empty,
20
+ quarter, half, three-quarter, and full. Numeric `value` props are clamped from
21
+ `0` to `100` and snapped to the nearest quarter step.
22
+
23
+ Use `status="null"` when progress is explicitly not yet known or measured. Null
24
+ progress is distinct from `status="empty"` or `value={0}`, which represent known
25
+ zero progress.
26
+
27
+ The default `variant="fill"` renders a filled progress mark. Use
28
+ `variant="border"` for an outline progress arc. `color` controls active progress,
29
+ while `trackColor` controls inactive and placeholder structure.
@@ -0,0 +1,3 @@
1
+ export { StatusMark } from "./status-mark";
2
+ export { STATUS_MARK_MOTIONS, STATUS_MARK_PROGRESS_STEPS, STATUS_MARK_PROGRESS_VALUES, STATUS_MARK_STATUSES, STATUS_MARK_TONES, STATUS_MARK_VARIANTS, resolveStatusProgress, type ResolvedStatusProgress, type StatusMarkMotion, type StatusMarkProps, type StatusMarkProgressStep, type StatusMarkProgressValue, type StatusMarkStatus, type StatusMarkTone, type StatusMarkVariant, } from "./status-mark-types";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EACL,mBAAmB,EACnB,0BAA0B,EAC1B,2BAA2B,EAC3B,oBAAoB,EACpB,iBAAiB,EACjB,oBAAoB,EACpB,qBAAqB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC5B,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,iBAAiB,GACvB,MAAM,qBAAqB,CAAC"}
package/dist/index.mjs ADDED
@@ -0,0 +1,269 @@
1
+ import { getSizeVariableStyle, joinClassNames } from "@patternmode/system";
2
+ import { useEffect, useReducer, useRef } from "react";
3
+ import { LazyMotion, domMax, m, useReducedMotion } from "motion/react";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/status-mark-types.ts
6
+ const STATUS_MARK_PROGRESS_STEPS = [
7
+ "null",
8
+ "empty",
9
+ "quarter",
10
+ "half",
11
+ "three-quarter",
12
+ "full"
13
+ ];
14
+ const STATUS_MARK_STATUSES = STATUS_MARK_PROGRESS_STEPS;
15
+ const STATUS_MARK_PROGRESS_VALUES = [
16
+ 0,
17
+ 25,
18
+ 50,
19
+ 75,
20
+ 100
21
+ ];
22
+ const STATUS_MARK_TONES = [
23
+ "neutral",
24
+ "accent",
25
+ "muted"
26
+ ];
27
+ const STATUS_MARK_VARIANTS = ["fill", "border"];
28
+ const STATUS_MARK_MOTIONS = [
29
+ "smooth",
30
+ "snap",
31
+ "reduced"
32
+ ];
33
+ const STEP_PROGRESS = {
34
+ empty: 0,
35
+ full: 100,
36
+ half: 50,
37
+ quarter: 25,
38
+ "three-quarter": 75
39
+ };
40
+ const PROGRESS_STEP = {
41
+ 0: "empty",
42
+ 100: "full",
43
+ 25: "quarter",
44
+ 50: "half",
45
+ 75: "three-quarter"
46
+ };
47
+ const snapProgress = (value) => {
48
+ if (value === void 0 || Number.isNaN(value)) return 0;
49
+ return Math.round(Math.min(100, Math.max(0, value)) / 25) * 25;
50
+ };
51
+ /** Resolves named or numeric progress input into a discrete StatusMark step. */
52
+ const resolveStatusProgress = ({ status, value }) => {
53
+ if (status === "null") return {
54
+ progress: null,
55
+ status
56
+ };
57
+ if (status) return {
58
+ progress: STEP_PROGRESS[status],
59
+ status
60
+ };
61
+ const progress = snapProgress(value);
62
+ return {
63
+ progress,
64
+ status: PROGRESS_STEP[progress]
65
+ };
66
+ };
67
+ //#endregion
68
+ //#region src/status-mark.tsx
69
+ const STATUS_MARK_RADIUS = 8;
70
+ const BORDERLESS_STATUS_MARK_RADIUS = 8.9;
71
+ const STATUS_MARK_CENTER = 12;
72
+ const STATUS_MARK_START_ANGLE = -90;
73
+ const STATUS_MARK_FULL_PROGRESS = 100;
74
+ const STATUS_SNAP_DURATION_MS = 160;
75
+ const STATUS_SMOOTH_DURATION_MS = 280;
76
+ const getTransition = (motion, reducedMotion) => {
77
+ if (motion === false || motion === "reduced" || reducedMotion) return { duration: .01 };
78
+ if (motion === "snap") return {
79
+ duration: .16,
80
+ ease: [
81
+ .22,
82
+ 1,
83
+ .36,
84
+ 1
85
+ ]
86
+ };
87
+ return {
88
+ duration: .28,
89
+ ease: [
90
+ .4,
91
+ 0,
92
+ .2,
93
+ 1
94
+ ]
95
+ };
96
+ };
97
+ const getFillMotionDuration = (motion, reducedMotion) => {
98
+ if (motion === false || motion === "reduced" || reducedMotion) return 0;
99
+ return motion === "snap" ? STATUS_SNAP_DURATION_MS : STATUS_SMOOTH_DURATION_MS;
100
+ };
101
+ const easeFillProgress = (progress) => {
102
+ if (progress < .5) return 4 * progress * progress * progress;
103
+ return 1 - (-2 * progress + 2) ** 3 / 2;
104
+ };
105
+ const setProgress = (_current, next) => next;
106
+ const getFillPath = (progress, radius) => {
107
+ if (progress <= 0) return `M${STATUS_MARK_CENTER} ${STATUS_MARK_CENTER}`;
108
+ if (progress >= 99.9) {
109
+ const diameter = radius * 2;
110
+ return [
111
+ `M${STATUS_MARK_CENTER - radius} ${STATUS_MARK_CENTER}`,
112
+ `a${radius} ${radius} 0 1 0 ${diameter} 0`,
113
+ `a${radius} ${radius} 0 1 0 -${diameter} 0`
114
+ ].join(" ");
115
+ }
116
+ const radians = (STATUS_MARK_START_ANGLE + 360 * progress / STATUS_MARK_FULL_PROGRESS) * Math.PI / 180;
117
+ const x = Number((STATUS_MARK_CENTER + radius * Math.cos(radians)).toFixed(3));
118
+ const y = Number((STATUS_MARK_CENTER + radius * Math.sin(radians)).toFixed(3));
119
+ const largeArcFlag = progress > 50 ? 1 : 0;
120
+ return [
121
+ `M${STATUS_MARK_CENTER} ${STATUS_MARK_CENTER}`,
122
+ `L${STATUS_MARK_CENTER} ${STATUS_MARK_CENTER - radius}`,
123
+ `A${radius} ${radius} 0 ${largeArcFlag} 1 ${x} ${y}`,
124
+ "Z"
125
+ ].join(" ");
126
+ };
127
+ const getRootStyle = ({ color, size = "base", style, trackColor }) => {
128
+ const rootStyle = {
129
+ ...getSizeVariableStyle(size, "--patternmode-status-size"),
130
+ ...style
131
+ };
132
+ if (color) rootStyle["--patternmode-status-color"] = color;
133
+ if (trackColor) rootStyle["--patternmode-status-track"] = trackColor;
134
+ return rootStyle;
135
+ };
136
+ const StatusFillSweep = ({ hasReducedMotion, motion, progress, radius }) => {
137
+ const animationFrameRef = useRef(null);
138
+ const renderedProgressRef = useRef(progress);
139
+ const [renderedProgress, setRenderedProgress] = useReducer(setProgress, progress);
140
+ useEffect(() => {
141
+ const duration = getFillMotionDuration(motion, hasReducedMotion);
142
+ const startProgress = renderedProgressRef.current;
143
+ const progressDelta = progress - startProgress;
144
+ if (animationFrameRef.current !== null) cancelAnimationFrame(animationFrameRef.current);
145
+ if (duration === 0 || progressDelta === 0) {
146
+ renderedProgressRef.current = progress;
147
+ setRenderedProgress(progress);
148
+ return;
149
+ }
150
+ const startTime = performance.now();
151
+ const tick = (now) => {
152
+ const elapsed = Math.min((now - startTime) / duration, 1);
153
+ const nextProgress = startProgress + progressDelta * easeFillProgress(elapsed);
154
+ renderedProgressRef.current = nextProgress;
155
+ setRenderedProgress(nextProgress);
156
+ if (elapsed < 1) {
157
+ animationFrameRef.current = requestAnimationFrame(tick);
158
+ return;
159
+ }
160
+ renderedProgressRef.current = progress;
161
+ animationFrameRef.current = null;
162
+ };
163
+ animationFrameRef.current = requestAnimationFrame(tick);
164
+ return () => {
165
+ if (animationFrameRef.current !== null) cancelAnimationFrame(animationFrameRef.current);
166
+ };
167
+ }, [
168
+ hasReducedMotion,
169
+ motion,
170
+ progress
171
+ ]);
172
+ return /* @__PURE__ */ jsx("path", {
173
+ className: "patternmode-status-mark__fill-sweep",
174
+ d: getFillPath(renderedProgress, radius),
175
+ "data-testid": "status-mark-fill-sweep"
176
+ });
177
+ };
178
+ const StatusArc = ({ hasReducedMotion, motion, progress }) => /* @__PURE__ */ jsx(m.circle, {
179
+ animate: {
180
+ opacity: progress > 0 ? 1 : 0,
181
+ pathLength: progress / 100
182
+ },
183
+ className: "patternmode-status-mark__arc",
184
+ cx: "12",
185
+ cy: "12",
186
+ initial: false,
187
+ pathLength: "1",
188
+ r: "8",
189
+ transition: getTransition(motion, hasReducedMotion)
190
+ });
191
+ const StatusMarkSvg = ({ hasReducedMotion, motion, state, variant }) => /* @__PURE__ */ jsx("svg", {
192
+ "aria-hidden": "true",
193
+ className: "patternmode-status-mark__svg",
194
+ fill: "none",
195
+ viewBox: "0 0 24 24",
196
+ children: state.status === "null" ? /* @__PURE__ */ jsx("circle", {
197
+ className: "patternmode-status-mark__track patternmode-status-mark__track--null",
198
+ cx: "12",
199
+ cy: "12",
200
+ "data-testid": "status-mark-null",
201
+ r: STATUS_MARK_RADIUS
202
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
203
+ variant === "fill" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("circle", {
204
+ className: "patternmode-status-mark__disc",
205
+ cx: "12",
206
+ cy: "12",
207
+ "data-testid": "status-mark-fill",
208
+ r: BORDERLESS_STATUS_MARK_RADIUS
209
+ }), /* @__PURE__ */ jsx(StatusFillSweep, {
210
+ hasReducedMotion,
211
+ motion,
212
+ progress: state.progress,
213
+ radius: BORDERLESS_STATUS_MARK_RADIUS
214
+ })] }) : null,
215
+ variant === "border" ? /* @__PURE__ */ jsx("circle", {
216
+ className: "patternmode-status-mark__track",
217
+ cx: "12",
218
+ cy: "12",
219
+ "data-testid": "status-mark-border",
220
+ r: STATUS_MARK_RADIUS
221
+ }) : null,
222
+ variant === "border" ? /* @__PURE__ */ jsx(StatusArc, {
223
+ hasReducedMotion,
224
+ motion,
225
+ progress: state.progress
226
+ }) : null
227
+ ] })
228
+ });
229
+ const StatusMark = ({ className, color, label, motion = "smooth", size = "base", status, style, tone = "neutral", trackColor, value, variant = "fill", ...props }) => {
230
+ const reducedMotion = useReducedMotion();
231
+ const hasReducedMotion = Boolean(reducedMotion);
232
+ const state = resolveStatusProgress({
233
+ status,
234
+ value
235
+ });
236
+ const rootStyle = getRootStyle({
237
+ color,
238
+ size,
239
+ style,
240
+ trackColor
241
+ });
242
+ return /* @__PURE__ */ jsx("span", {
243
+ ...props,
244
+ "aria-hidden": label ? void 0 : true,
245
+ "aria-label": label,
246
+ className: joinClassNames("patternmode-status-mark", className),
247
+ "data-motion": motion === false ? "false" : motion,
248
+ "data-progress": state.progress ?? "null",
249
+ "data-slot": "status-mark",
250
+ "data-status": state.status,
251
+ "data-tone": tone,
252
+ "data-variant": variant,
253
+ role: label ? "img" : void 0,
254
+ style: rootStyle,
255
+ children: /* @__PURE__ */ jsx(LazyMotion, {
256
+ features: domMax,
257
+ children: /* @__PURE__ */ jsx(StatusMarkSvg, {
258
+ hasReducedMotion,
259
+ motion,
260
+ state,
261
+ variant
262
+ })
263
+ })
264
+ });
265
+ };
266
+ //#endregion
267
+ export { STATUS_MARK_MOTIONS, STATUS_MARK_PROGRESS_STEPS, STATUS_MARK_PROGRESS_VALUES, STATUS_MARK_STATUSES, STATUS_MARK_TONES, STATUS_MARK_VARIANTS, StatusMark, resolveStatusProgress };
268
+
269
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/status-mark-types.ts","../src/status-mark.tsx"],"sourcesContent":["import type { HTMLAttributes } from \"react\";\n\nexport const STATUS_MARK_PROGRESS_STEPS = [\n \"null\",\n \"empty\",\n \"quarter\",\n \"half\",\n \"three-quarter\",\n \"full\",\n] as const;\n\nexport const STATUS_MARK_STATUSES = STATUS_MARK_PROGRESS_STEPS;\nexport const STATUS_MARK_PROGRESS_VALUES = [0, 25, 50, 75, 100] as const;\nexport const STATUS_MARK_TONES = [\"neutral\", \"accent\", \"muted\"] as const;\nexport const STATUS_MARK_VARIANTS = [\"fill\", \"border\"] as const;\nexport const STATUS_MARK_MOTIONS = [\"smooth\", \"snap\", \"reduced\"] as const;\n\nexport type StatusMarkMotion = (typeof STATUS_MARK_MOTIONS)[number] | false;\nexport type StatusMarkProgressStep = (typeof STATUS_MARK_PROGRESS_STEPS)[number];\nexport type StatusMarkProgressValue = (typeof STATUS_MARK_PROGRESS_VALUES)[number];\nexport type StatusMarkStatus = StatusMarkProgressStep;\nexport type StatusMarkTone = (typeof STATUS_MARK_TONES)[number];\nexport type StatusMarkVariant = (typeof STATUS_MARK_VARIANTS)[number];\n\nexport type ResolvedStatusProgress =\n | {\n progress: null;\n status: \"null\";\n }\n | {\n progress: StatusMarkProgressValue;\n status: Exclude<StatusMarkProgressStep, \"null\">;\n };\n\nexport interface StatusMarkProps extends Omit<HTMLAttributes<HTMLSpanElement>, \"children\"> {\n /**\n * Active progress color. Overrides the selected visual `tone` when supplied.\n */\n color?: string;\n /**\n * Accessible label for the visual state. Omit only when adjacent text already\n * names the same progress and the mark should be decorative.\n */\n label?: string;\n /**\n * Transition preset for movement between progress steps.\n *\n * Default `\"smooth\"`.\n */\n motion?: StatusMarkMotion;\n /**\n * Named discrete progress step. Use `\"null\"` only when progress is explicitly\n * not yet known or measured.\n */\n status?: StatusMarkProgressStep;\n /**\n * Size token used for the mark dimensions.\n *\n * Default `\"base\"`.\n */\n size?: \"2xl\" | \"2xs\" | \"3xl\" | \"base\" | \"lg\" | \"sm\" | \"xl\" | \"xs\";\n /**\n * Visual emphasis treatment. Tone changes color only; progress shape carries\n * meaning.\n *\n * Default `\"neutral\"`.\n */\n tone?: StatusMarkTone;\n /**\n * Color used for inactive track or placeholder structure.\n */\n trackColor?: string;\n /**\n * Numeric progress input. Values are clamped from 0 to 100 and snapped to the\n * nearest discrete visual step: 0, 25, 50, 75, or 100.\n */\n value?: number;\n /**\n * Visual treatment for known progress. Null progress renders the same dashed\n * placeholder in every variant.\n *\n * Default `\"fill\"`.\n */\n variant?: StatusMarkVariant;\n}\n\nconst STEP_PROGRESS: Record<Exclude<StatusMarkProgressStep, \"null\">, StatusMarkProgressValue> = {\n empty: 0,\n full: 100,\n half: 50,\n quarter: 25,\n \"three-quarter\": 75,\n};\n\nconst PROGRESS_STEP: Record<StatusMarkProgressValue, Exclude<StatusMarkProgressStep, \"null\">> = {\n 0: \"empty\",\n 100: \"full\",\n 25: \"quarter\",\n 50: \"half\",\n 75: \"three-quarter\",\n};\n\nconst snapProgress = (value: number | undefined): StatusMarkProgressValue => {\n if (value === undefined || Number.isNaN(value)) {\n return 0;\n }\n\n const clamped = Math.min(100, Math.max(0, value));\n return (Math.round(clamped / 25) * 25) as StatusMarkProgressValue;\n};\n\n/** Resolves named or numeric progress input into a discrete StatusMark step. */\nexport const resolveStatusProgress = ({\n status,\n value,\n}: Pick<StatusMarkProps, \"status\" | \"value\">): ResolvedStatusProgress => {\n if (status === \"null\") {\n return {\n progress: null,\n status,\n };\n }\n\n if (status) {\n return {\n progress: STEP_PROGRESS[status],\n status,\n };\n }\n\n const progress = snapProgress(value);\n return {\n progress,\n status: PROGRESS_STEP[progress],\n };\n};\n","\"use client\";\n\nimport { getSizeVariableStyle, joinClassNames } from \"@patternmode/system\";\nimport { useEffect, useReducer, useRef } from \"react\";\nimport type { CSSProperties } from \"react\";\nimport { domMax, LazyMotion, m, useReducedMotion } from \"motion/react\";\nimport type { Transition } from \"motion/react\";\n\nimport { resolveStatusProgress } from \"./status-mark-types\";\nimport type {\n ResolvedStatusProgress,\n StatusMarkMotion,\n StatusMarkProgressValue,\n StatusMarkProps,\n} from \"./status-mark-types\";\n\ntype StatusMarkStyle = CSSProperties & Record<`--${string}`, string | number | undefined>;\ninterface MotionPartProps {\n hasReducedMotion: boolean;\n motion: StatusMarkMotion;\n}\n\nconst STATUS_MARK_RADIUS = 8;\nconst STATUS_MARK_STROKE_WIDTH = 1.8;\nconst BORDERLESS_STATUS_MARK_RADIUS = STATUS_MARK_RADIUS + STATUS_MARK_STROKE_WIDTH / 2;\nconst STATUS_MARK_CENTER = 12;\nconst STATUS_MARK_START_ANGLE = -90;\nconst STATUS_MARK_FULL_PROGRESS = 100;\nconst STATUS_SNAP_DURATION_MS = 160;\nconst STATUS_SMOOTH_DURATION_MS = 280;\n\nconst getTransition = (motion: StatusMarkMotion, reducedMotion: boolean): Transition => {\n if (motion === false || motion === \"reduced\" || reducedMotion) {\n return { duration: 0.01 };\n }\n\n if (motion === \"snap\") {\n return { duration: 0.16, ease: [0.22, 1, 0.36, 1] as const };\n }\n\n return { duration: 0.28, ease: [0.4, 0, 0.2, 1] as const };\n};\n\nconst getFillMotionDuration = (motion: StatusMarkMotion, reducedMotion: boolean) => {\n if (motion === false || motion === \"reduced\" || reducedMotion) {\n return 0;\n }\n\n return motion === \"snap\" ? STATUS_SNAP_DURATION_MS : STATUS_SMOOTH_DURATION_MS;\n};\n\nconst easeFillProgress = (progress: number) => {\n if (progress < 0.5) {\n return 4 * progress * progress * progress;\n }\n\n return 1 - (-2 * progress + 2) ** 3 / 2;\n};\n\nconst setProgress = (_current: number, next: number) => next;\n\nconst getFillPath = (progress: number, radius: number) => {\n if (progress <= 0) {\n return `M${STATUS_MARK_CENTER} ${STATUS_MARK_CENTER}`;\n }\n\n if (progress >= 99.9) {\n const diameter = radius * 2;\n\n return [\n `M${STATUS_MARK_CENTER - radius} ${STATUS_MARK_CENTER}`,\n `a${radius} ${radius} 0 1 0 ${diameter} 0`,\n `a${radius} ${radius} 0 1 0 -${diameter} 0`,\n ].join(\" \");\n }\n\n const endAngle = STATUS_MARK_START_ANGLE + (360 * progress) / STATUS_MARK_FULL_PROGRESS;\n const radians = (endAngle * Math.PI) / 180;\n const x = Number((STATUS_MARK_CENTER + radius * Math.cos(radians)).toFixed(3));\n const y = Number((STATUS_MARK_CENTER + radius * Math.sin(radians)).toFixed(3));\n const largeArcFlag = progress > 50 ? 1 : 0;\n\n return [\n `M${STATUS_MARK_CENTER} ${STATUS_MARK_CENTER}`,\n `L${STATUS_MARK_CENTER} ${STATUS_MARK_CENTER - radius}`,\n `A${radius} ${radius} 0 ${largeArcFlag} 1 ${x} ${y}`,\n \"Z\",\n ].join(\" \");\n};\n\nconst getRootStyle = ({\n color,\n size = \"base\",\n style,\n trackColor,\n}: Pick<StatusMarkProps, \"color\" | \"size\" | \"style\" | \"trackColor\">) => {\n const rootStyle: StatusMarkStyle = {\n ...getSizeVariableStyle(size, \"--patternmode-status-size\"),\n ...style,\n };\n\n if (color) {\n rootStyle[\"--patternmode-status-color\"] = color;\n }\n\n if (trackColor) {\n rootStyle[\"--patternmode-status-track\"] = trackColor;\n }\n\n return rootStyle;\n};\n\nconst StatusFillSweep = ({\n hasReducedMotion,\n motion,\n progress,\n radius,\n}: MotionPartProps & { progress: StatusMarkProgressValue; radius: number }) => {\n const animationFrameRef = useRef<number | null>(null);\n const renderedProgressRef = useRef<number>(progress);\n const [renderedProgress, setRenderedProgress] = useReducer(setProgress, progress);\n\n useEffect(() => {\n const duration = getFillMotionDuration(motion, hasReducedMotion);\n const startProgress = renderedProgressRef.current;\n const progressDelta = progress - startProgress;\n\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n\n if (duration === 0 || progressDelta === 0) {\n renderedProgressRef.current = progress;\n setRenderedProgress(progress);\n return;\n }\n\n const startTime = performance.now();\n\n const tick = (now: number) => {\n const elapsed = Math.min((now - startTime) / duration, 1);\n const nextProgress = startProgress + progressDelta * easeFillProgress(elapsed);\n\n renderedProgressRef.current = nextProgress;\n setRenderedProgress(nextProgress);\n\n if (elapsed < 1) {\n animationFrameRef.current = requestAnimationFrame(tick);\n return;\n }\n\n renderedProgressRef.current = progress;\n animationFrameRef.current = null;\n };\n\n animationFrameRef.current = requestAnimationFrame(tick);\n\n return () => {\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n };\n }, [hasReducedMotion, motion, progress]);\n\n return (\n <path\n className=\"patternmode-status-mark__fill-sweep\"\n d={getFillPath(renderedProgress, radius)}\n data-testid=\"status-mark-fill-sweep\"\n />\n );\n};\n\nconst StatusArc = ({\n hasReducedMotion,\n motion,\n progress,\n}: MotionPartProps & { progress: StatusMarkProgressValue }) => (\n <m.circle\n animate={{ opacity: progress > 0 ? 1 : 0, pathLength: progress / 100 }}\n className=\"patternmode-status-mark__arc\"\n cx=\"12\"\n cy=\"12\"\n initial={false}\n pathLength=\"1\"\n r=\"8\"\n transition={getTransition(motion, hasReducedMotion)}\n />\n);\n\nconst StatusMarkSvg = ({\n hasReducedMotion,\n motion,\n state,\n variant,\n}: MotionPartProps & {\n state: ResolvedStatusProgress;\n variant: NonNullable<StatusMarkProps[\"variant\"]>;\n}) => (\n <svg aria-hidden=\"true\" className=\"patternmode-status-mark__svg\" fill=\"none\" viewBox=\"0 0 24 24\">\n {state.status === \"null\" ? (\n <circle\n className=\"patternmode-status-mark__track patternmode-status-mark__track--null\"\n cx=\"12\"\n cy=\"12\"\n data-testid=\"status-mark-null\"\n r={STATUS_MARK_RADIUS}\n />\n ) : (\n <>\n {variant === \"fill\" ? (\n <>\n <circle\n className=\"patternmode-status-mark__disc\"\n cx=\"12\"\n cy=\"12\"\n data-testid=\"status-mark-fill\"\n r={BORDERLESS_STATUS_MARK_RADIUS}\n />\n <StatusFillSweep\n hasReducedMotion={hasReducedMotion}\n motion={motion}\n progress={state.progress}\n radius={BORDERLESS_STATUS_MARK_RADIUS}\n />\n </>\n ) : null}\n {variant === \"border\" ? (\n <circle\n className=\"patternmode-status-mark__track\"\n cx=\"12\"\n cy=\"12\"\n data-testid=\"status-mark-border\"\n r={STATUS_MARK_RADIUS}\n />\n ) : null}\n {variant === \"border\" ? (\n <StatusArc\n hasReducedMotion={hasReducedMotion}\n motion={motion}\n progress={state.progress}\n />\n ) : null}\n </>\n )}\n </svg>\n);\n\nexport const StatusMark = ({\n className,\n color,\n label,\n motion = \"smooth\",\n size = \"base\",\n status,\n style,\n tone = \"neutral\",\n trackColor,\n value,\n variant = \"fill\",\n ...props\n}: StatusMarkProps) => {\n const reducedMotion = useReducedMotion();\n const hasReducedMotion = Boolean(reducedMotion);\n const state = resolveStatusProgress({ status, value });\n const rootStyle = getRootStyle({ color, size, style, trackColor });\n\n return (\n <span\n {...props}\n aria-hidden={label ? undefined : true}\n aria-label={label}\n className={joinClassNames(\"patternmode-status-mark\", className)}\n data-motion={motion === false ? \"false\" : motion}\n data-progress={state.progress ?? \"null\"}\n data-slot=\"status-mark\"\n data-status={state.status}\n data-tone={tone}\n data-variant={variant}\n role={label ? \"img\" : undefined}\n style={rootStyle}\n >\n <LazyMotion features={domMax}>\n <StatusMarkSvg\n hasReducedMotion={hasReducedMotion}\n motion={motion}\n state={state}\n variant={variant}\n />\n </LazyMotion>\n </span>\n );\n};\n"],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ import type { HTMLAttributes } from "react";
2
+ export declare const STATUS_MARK_PROGRESS_STEPS: readonly ["null", "empty", "quarter", "half", "three-quarter", "full"];
3
+ export declare const STATUS_MARK_STATUSES: readonly ["null", "empty", "quarter", "half", "three-quarter", "full"];
4
+ export declare const STATUS_MARK_PROGRESS_VALUES: readonly [0, 25, 50, 75, 100];
5
+ export declare const STATUS_MARK_TONES: readonly ["neutral", "accent", "muted"];
6
+ export declare const STATUS_MARK_VARIANTS: readonly ["fill", "border"];
7
+ export declare const STATUS_MARK_MOTIONS: readonly ["smooth", "snap", "reduced"];
8
+ export type StatusMarkMotion = (typeof STATUS_MARK_MOTIONS)[number] | false;
9
+ export type StatusMarkProgressStep = (typeof STATUS_MARK_PROGRESS_STEPS)[number];
10
+ export type StatusMarkProgressValue = (typeof STATUS_MARK_PROGRESS_VALUES)[number];
11
+ export type StatusMarkStatus = StatusMarkProgressStep;
12
+ export type StatusMarkTone = (typeof STATUS_MARK_TONES)[number];
13
+ export type StatusMarkVariant = (typeof STATUS_MARK_VARIANTS)[number];
14
+ export type ResolvedStatusProgress = {
15
+ progress: null;
16
+ status: "null";
17
+ } | {
18
+ progress: StatusMarkProgressValue;
19
+ status: Exclude<StatusMarkProgressStep, "null">;
20
+ };
21
+ export interface StatusMarkProps extends Omit<HTMLAttributes<HTMLSpanElement>, "children"> {
22
+ /**
23
+ * Active progress color. Overrides the selected visual `tone` when supplied.
24
+ */
25
+ color?: string;
26
+ /**
27
+ * Accessible label for the visual state. Omit only when adjacent text already
28
+ * names the same progress and the mark should be decorative.
29
+ */
30
+ label?: string;
31
+ /**
32
+ * Transition preset for movement between progress steps.
33
+ *
34
+ * Default `"smooth"`.
35
+ */
36
+ motion?: StatusMarkMotion;
37
+ /**
38
+ * Named discrete progress step. Use `"null"` only when progress is explicitly
39
+ * not yet known or measured.
40
+ */
41
+ status?: StatusMarkProgressStep;
42
+ /**
43
+ * Size token used for the mark dimensions.
44
+ *
45
+ * Default `"base"`.
46
+ */
47
+ size?: "2xl" | "2xs" | "3xl" | "base" | "lg" | "sm" | "xl" | "xs";
48
+ /**
49
+ * Visual emphasis treatment. Tone changes color only; progress shape carries
50
+ * meaning.
51
+ *
52
+ * Default `"neutral"`.
53
+ */
54
+ tone?: StatusMarkTone;
55
+ /**
56
+ * Color used for inactive track or placeholder structure.
57
+ */
58
+ trackColor?: string;
59
+ /**
60
+ * Numeric progress input. Values are clamped from 0 to 100 and snapped to the
61
+ * nearest discrete visual step: 0, 25, 50, 75, or 100.
62
+ */
63
+ value?: number;
64
+ /**
65
+ * Visual treatment for known progress. Null progress renders the same dashed
66
+ * placeholder in every variant.
67
+ *
68
+ * Default `"fill"`.
69
+ */
70
+ variant?: StatusMarkVariant;
71
+ }
72
+ /** Resolves named or numeric progress input into a discrete StatusMark step. */
73
+ export declare const resolveStatusProgress: ({ status, value, }: Pick<StatusMarkProps, "status" | "value">) => ResolvedStatusProgress;
74
+ //# sourceMappingURL=status-mark-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-mark-types.d.ts","sourceRoot":"","sources":["../src/status-mark-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAE5C,eAAO,MAAM,0BAA0B,wEAO7B,CAAC;AAEX,eAAO,MAAM,oBAAoB,wEAA6B,CAAC;AAC/D,eAAO,MAAM,2BAA2B,+BAAgC,CAAC;AACzE,eAAO,MAAM,iBAAiB,yCAA0C,CAAC;AACzE,eAAO,MAAM,oBAAoB,6BAA8B,CAAC;AAChE,eAAO,MAAM,mBAAmB,wCAAyC,CAAC;AAE1E,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;AAC5E,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAC;AACjF,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AACnF,MAAM,MAAM,gBAAgB,GAAG,sBAAsB,CAAC;AACtD,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAChE,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,MAAM,MAAM,sBAAsB,GAC9B;IACE,QAAQ,EAAE,IAAI,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,QAAQ,EAAE,uBAAuB,CAAC;IAClC,MAAM,EAAE,OAAO,CAAC,sBAAsB,EAAE,MAAM,CAAC,CAAC;CACjD,CAAC;AAEN,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,EAAE,UAAU,CAAC;IACxF;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;;;OAGG;IACH,MAAM,CAAC,EAAE,sBAAsB,CAAC;IAChC;;;;OAIG;IACH,IAAI,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAClE;;;;;OAKG;IACH,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC;CAC7B;AA2BD,gFAAgF;AAChF,eAAO,MAAM,qBAAqB,GAAI,oBAGnC,IAAI,CAAC,eAAe,EAAE,QAAQ,GAAG,OAAO,CAAC,KAAG,sBAoB9C,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { StatusMarkProps } from "./status-mark-types";
2
+ export declare const StatusMark: ({ className, color, label, motion, size, status, style, tone, trackColor, value, variant, ...props }: StatusMarkProps) => import("react/jsx-runtime").JSX.Element;
3
+ //# sourceMappingURL=status-mark.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-mark.d.ts","sourceRoot":"","sources":["../src/status-mark.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAIV,eAAe,EAChB,MAAM,qBAAqB,CAAC;AA0O7B,eAAO,MAAM,UAAU,GAAI,sGAaxB,eAAe,4CA+BjB,CAAC"}
@@ -0,0 +1 @@
1
+ @layer components{.patternmode-status-mark{--patternmode-status-size:2rem;--patternmode-status-color:oklch(29% .018 92);--patternmode-status-track:oklch(90% .012 88);color:var(--patternmode-status-color);height:var(--patternmode-status-size);vertical-align:middle;width:var(--patternmode-status-size);justify-content:center;align-items:center;line-height:0;display:inline-flex;position:relative}.patternmode-status-mark[data-tone=accent]{--patternmode-status-color:var(--accent,#315c4b);--patternmode-status-track:color-mix(in oklch, var(--patternmode-status-color) 12%, white)}.patternmode-status-mark[data-tone=muted]{--patternmode-status-color:var(--muted,#77756d);--patternmode-status-track:color-mix(in oklch, var(--patternmode-status-color) 12%, white)}.patternmode-status-mark__svg{width:100%;height:100%;display:block;overflow:visible}.patternmode-status-mark__track,.patternmode-status-mark__arc{stroke-linecap:round;stroke-linejoin:round;stroke-width:1.8px}.patternmode-status-mark__disc{fill:var(--patternmode-status-track)}.patternmode-status-mark__fill-sweep{fill:currentColor}.patternmode-status-mark__track{fill:none;stroke:var(--patternmode-status-track)}.patternmode-status-mark__track--null{stroke-dasharray:2 2.5}.patternmode-status-mark__arc{stroke:currentColor;transform-origin:12px 12px;transform:rotate(-90deg)}}
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@patternmode/status",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Animated discrete status marks for Patternmode interfaces.",
6
+ "keywords": [
7
+ "animation",
8
+ "component",
9
+ "indicator",
10
+ "patternmode",
11
+ "progress",
12
+ "react",
13
+ "status"
14
+ ],
15
+ "homepage": "https://github.com/howells/patternmode/tree/main/packages/status#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/howells/patternmode/issues"
18
+ },
19
+ "license": "MIT",
20
+ "author": "Daniel Howells",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+ssh://git@github.com/howells/patternmode.git",
24
+ "directory": "packages/status"
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "type": "module",
30
+ "sideEffects": [
31
+ "**/*.css"
32
+ ],
33
+ "exports": {
34
+ ".": {
35
+ "types": "./dist/index.d.ts",
36
+ "default": "./dist/index.mjs"
37
+ },
38
+ "./styles.css": "./dist/styles.css",
39
+ "./package.json": "./package.json"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "scripts": {
45
+ "build": "tsdown && tsc --emitDeclarationOnly --declaration --declarationMap --outDir dist --noEmit false && pnpm build:styles",
46
+ "build:styles": "tailwindcss -i ./src/styles.css -o ./dist/styles.css --minify",
47
+ "clean": "rm -rf dist .turbo",
48
+ "dev": "concurrently \"pnpm dev:js\" \"pnpm dev:types\" \"pnpm dev:styles\"",
49
+ "dev:js": "tsdown --watch",
50
+ "dev:styles": "tailwindcss -i ./src/styles.css -o ./dist/styles.css --watch",
51
+ "dev:types": "tsc --emitDeclarationOnly --declaration --declarationMap --outDir dist --noEmit false --watch --preserveWatchOutput",
52
+ "lint": "howells-ox-check .",
53
+ "lint:fix": "howells-ox-fix .",
54
+ "prepack": "pnpm build",
55
+ "test": "vitest run",
56
+ "typecheck": "tsc --noEmit"
57
+ },
58
+ "dependencies": {
59
+ "@patternmode/system": "workspace:*",
60
+ "motion": "^12.38.0"
61
+ },
62
+ "devDependencies": {
63
+ "@howells/lint": "^0.2.1",
64
+ "@howells/typescript-config": "^0.1.2",
65
+ "@tailwindcss/cli": "^4.3.0",
66
+ "@testing-library/jest-dom": "^6.9.1",
67
+ "@testing-library/react": "^16.3.2",
68
+ "@types/react": "^19.2.14",
69
+ "@types/react-dom": "^19.2.3",
70
+ "concurrently": "^9.2.1",
71
+ "jsdom": "^29.1.1",
72
+ "react": "^19.2.3",
73
+ "react-dom": "^19.2.3",
74
+ "tailwindcss": "^4.3.0",
75
+ "tsdown": "^0.22.0",
76
+ "typescript": "^6.0.3",
77
+ "vitest": "^4.1.6"
78
+ },
79
+ "peerDependencies": {
80
+ "react": "^19.0.0",
81
+ "react-dom": "^19.0.0"
82
+ },
83
+ "engines": {
84
+ "node": ">=20"
85
+ }
86
+ }