@monimesl/dot-renderer-react 0.1.0-alpha.2 → 0.1.0-alpha.4

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/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { createEvent, toList, toText } from "@monimesl/dot-core";
2
- import { Fragment, createContext, createElement, useContext, useMemo, useState, useSyncExternalStore } from "react";
3
- import { jsx } from "react/jsx-runtime";
2
+ import { Children, Fragment, createContext, createElement, isValidElement, useContext, useEffect, useEffectEvent, useMemo, useRef, useState, useSyncExternalStore } from "react";
3
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
4
+ import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp, Banknote, Bell, Calendar, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, CircleAlert, CircleCheck, CircleHelp, Clock, Copy, CreditCard, Download, Droplet, Ellipsis, Eye, EyeOff, File, Gift, Heart, History, House, Image, Info, Landmark, LayoutGrid, List, Lock, LogOut, Mail, MapPin, Minus, Pencil, Phone, Plus, QrCode, Receipt, RefreshCw, Search, Send, Settings, Share2, ShoppingCart, Smartphone, Star, Trash2, TriangleAlert, Upload, User, Users, Wallet, Wifi, X, Zap } from "lucide";
5
+ import { encode } from "uqr";
4
6
  import { createRoot } from "react-dom/client";
5
7
  //#region src/theme.ts
6
8
  const FONT_FAMILY = "system-ui, sans-serif";
@@ -13,7 +15,11 @@ const DEFAULT_COLORS = {
13
15
  text: "#111111",
14
16
  subtle: "#8e8e93",
15
17
  muted: "#e9e9eb",
16
- border: "#e9e9eb"
18
+ border: "#e9e9eb",
19
+ info: "#0a64d6",
20
+ success: "#1a8a4a",
21
+ warning: "#b25e00",
22
+ danger: "#d0312d"
17
23
  },
18
24
  dark: {
19
25
  primary: "#f5f5f5",
@@ -22,7 +28,11 @@ const DEFAULT_COLORS = {
22
28
  text: "#f5f5f5",
23
29
  subtle: "#8e8e93",
24
30
  muted: "#2c2c2e",
25
- border: "#2a2a2c"
31
+ border: "#2a2a2c",
32
+ info: "#4c9dff",
33
+ success: "#3ecf72",
34
+ warning: "#ffb340",
35
+ danger: "#ff6961"
26
36
  }
27
37
  };
28
38
  function resolveTheme(theme, systemScheme) {
@@ -39,7 +49,11 @@ function resolveTheme(theme, systemScheme) {
39
49
  text: pick(colors.text, defaults.text),
40
50
  subtle: defaults.subtle,
41
51
  muted: defaults.muted,
42
- border: defaults.border
52
+ border: defaults.border,
53
+ info: defaults.info,
54
+ success: defaults.success,
55
+ warning: defaults.warning,
56
+ danger: defaults.danger
43
57
  },
44
58
  radius: theme.radius ?? DEFAULT_RADIUS
45
59
  };
@@ -50,7 +64,7 @@ function useTheme() {
50
64
  }
51
65
  //#endregion
52
66
  //#region src/components/tokens.ts
53
- const GAP = {
67
+ const SPACE = {
54
68
  none: 0,
55
69
  small: 4,
56
70
  medium: 8,
@@ -63,22 +77,236 @@ const FONT_SIZE = {
63
77
  };
64
78
  const CARD_RADIUS_SCALE = 1.5;
65
79
  const DISABLED_OPACITY = .6;
80
+ const ICON_SIZE = {
81
+ small: 16,
82
+ medium: 20,
83
+ large: 28
84
+ };
85
+ //#endregion
86
+ //#region src/components/field.tsx
87
+ function Field({ label, hint, error, children }) {
88
+ const { colors } = useTheme();
89
+ const note = error || hint;
90
+ return /* @__PURE__ */ jsxs("label", {
91
+ className: "dot-field",
92
+ style: {
93
+ display: "flex",
94
+ flexDirection: "column",
95
+ gap: SPACE.small
96
+ },
97
+ children: [
98
+ label ? /* @__PURE__ */ jsx(FieldLabel, { children: label }) : null,
99
+ children,
100
+ note ? /* @__PURE__ */ jsx("span", {
101
+ style: {
102
+ fontSize: FONT_SIZE.caption,
103
+ color: error ? colors.danger : colors.subtle
104
+ },
105
+ children: note
106
+ }) : null
107
+ ]
108
+ });
109
+ }
110
+ function FieldLabel({ children }) {
111
+ return /* @__PURE__ */ jsx("span", {
112
+ style: {
113
+ fontSize: FONT_SIZE.caption,
114
+ fontWeight: 500
115
+ },
116
+ children
117
+ });
118
+ }
119
+ //#endregion
120
+ //#region src/components/field-box.ts
121
+ function useFieldBox(error, disabled) {
122
+ const { colors, radius } = useTheme();
123
+ const [focused, setFocused] = useState(false);
124
+ let borderColor = colors.border;
125
+ if (error) borderColor = colors.danger;
126
+ else if (focused) borderColor = colors.primary;
127
+ return {
128
+ style: {
129
+ border: `1px solid ${borderColor}`,
130
+ borderRadius: radius,
131
+ background: colors.surface,
132
+ color: colors.text,
133
+ fontSize: 16,
134
+ opacity: disabled ? DISABLED_OPACITY : 1
135
+ },
136
+ onFocus: () => setFocused(true),
137
+ onBlur: () => setFocused(false)
138
+ };
139
+ }
140
+ //#endregion
141
+ //#region src/components/typed-text.ts
142
+ function useTypedText(value, sendsChanges, show = (text) => text) {
143
+ const [text, setText] = useState(() => show(value ?? ""));
144
+ const [awaitingEcho, setAwaitingEcho] = useState([]);
145
+ const [previousValue, setPreviousValue] = useState(value);
146
+ if (value !== previousValue) {
147
+ setPreviousValue(value);
148
+ const echo = awaitingEcho.indexOf(value ?? "");
149
+ if (echo >= 0) setAwaitingEcho(awaitingEcho.slice(echo + 1));
150
+ else {
151
+ setAwaitingEcho([]);
152
+ setText(show(value ?? ""));
153
+ }
154
+ }
155
+ const type = (next, sent = next) => {
156
+ setText(next);
157
+ if (sendsChanges && value !== void 0) setAwaitingEcho([...awaitingEcho, sent]);
158
+ };
159
+ return [text, type];
160
+ }
161
+ //#endregion
162
+ //#region src/components/amount-input.tsx
163
+ const AMOUNT_FONT_SIZE = 28;
164
+ const AmountInput = ({ value, currency, decimals = 0, label, placeholder = "0", hint, error, disabled, onChange }) => {
165
+ const { colors } = useTheme();
166
+ const places = Math.max(0, Math.round(decimals));
167
+ const [text, typeText] = useTypedText(amountText(value), onChange !== void 0, (sent) => sent === "" ? "" : formatAmount(Number(sent), places));
168
+ const box = useFieldBox(error, disabled);
169
+ return /* @__PURE__ */ jsx(Field, {
170
+ label,
171
+ hint,
172
+ error,
173
+ children: /* @__PURE__ */ jsxs("span", {
174
+ className: "dot-amount-input",
175
+ style: {
176
+ ...box.style,
177
+ display: "flex",
178
+ alignItems: "baseline",
179
+ gap: SPACE.medium,
180
+ padding: "10px 14px"
181
+ },
182
+ children: [currency ? /* @__PURE__ */ jsx("span", {
183
+ style: {
184
+ color: colors.subtle,
185
+ fontSize: 20,
186
+ fontWeight: 500
187
+ },
188
+ children: currency
189
+ }) : null, /* @__PURE__ */ jsx("input", {
190
+ value: text,
191
+ placeholder,
192
+ inputMode: places > 0 ? "decimal" : "numeric",
193
+ disabled,
194
+ "aria-invalid": error ? true : void 0,
195
+ onFocus: box.onFocus,
196
+ onBlur: box.onBlur,
197
+ onChange: (event) => {
198
+ const typed = cleanAmount(event.currentTarget.value, places);
199
+ const amount = parseAmount(typed);
200
+ typeText(typed, amount === null ? "" : String(amount));
201
+ onChange?.(createEvent("change", { value: amount }));
202
+ },
203
+ style: {
204
+ flexGrow: 1,
205
+ minWidth: 0,
206
+ padding: 0,
207
+ border: "none",
208
+ outline: "none",
209
+ background: "transparent",
210
+ color: "inherit",
211
+ font: "inherit",
212
+ fontSize: AMOUNT_FONT_SIZE,
213
+ fontWeight: 600
214
+ }
215
+ })]
216
+ })
217
+ });
218
+ };
219
+ function amountText(value) {
220
+ if (value === void 0) return;
221
+ return Number.isFinite(value) ? String(value) : "";
222
+ }
223
+ function cleanAmount(typed, places) {
224
+ const [whole = "", ...fractions] = typed.replace(/[^\d.]/g, "").split(".");
225
+ const digits = whole.replace(/^0+/, "") || whole.slice(0, 1);
226
+ if (places === 0 || fractions.length === 0) return groupThousands(digits);
227
+ const fraction = fractions.join("").slice(0, places);
228
+ return `${groupThousands(digits) || "0"}.${fraction}`;
229
+ }
230
+ function parseAmount(text) {
231
+ const plain = text.replaceAll(",", "");
232
+ return plain === "" ? null : Number(plain);
233
+ }
234
+ function formatAmount(amount, places) {
235
+ const [whole = "", fraction] = Math.abs(amount).toFixed(places).split(".");
236
+ const grouped = groupThousands(whole);
237
+ return fraction === void 0 ? grouped : `${grouped}.${fraction}`;
238
+ }
239
+ function groupThousands(digits) {
240
+ const groups = [];
241
+ for (let end = digits.length; end > 0; end -= 3) groups.unshift(digits.slice(Math.max(0, end - 3), end));
242
+ return groups.join(",");
243
+ }
244
+ //#endregion
245
+ //#region src/components/spinner.tsx
246
+ function LoadingIcon({ size }) {
247
+ return /* @__PURE__ */ jsxs("svg", {
248
+ className: "dot-spinner",
249
+ width: size,
250
+ height: size,
251
+ viewBox: "0 0 24 24",
252
+ fill: "none",
253
+ stroke: "currentColor",
254
+ strokeWidth: 3,
255
+ role: "progressbar",
256
+ "aria-label": "Loading",
257
+ style: { flexShrink: 0 },
258
+ children: [/* @__PURE__ */ jsx("circle", {
259
+ cx: "12",
260
+ cy: "12",
261
+ r: "9",
262
+ strokeOpacity: .25
263
+ }), /* @__PURE__ */ jsx("path", {
264
+ d: "M21 12a9 9 0 0 0-9-9",
265
+ strokeLinecap: "round",
266
+ children: /* @__PURE__ */ jsx("animateTransform", {
267
+ attributeName: "transform",
268
+ type: "rotate",
269
+ from: "0 12 12",
270
+ to: "360 12 12",
271
+ dur: "0.8s",
272
+ repeatCount: "indefinite"
273
+ })
274
+ })]
275
+ });
276
+ }
277
+ const Spinner = ({ size = "medium" }) => {
278
+ const { colors } = useTheme();
279
+ return /* @__PURE__ */ jsx("span", {
280
+ style: {
281
+ display: "inline-flex",
282
+ alignSelf: "center",
283
+ color: colors.subtle
284
+ },
285
+ children: /* @__PURE__ */ jsx(LoadingIcon, { size: ICON_SIZE[size] })
286
+ });
287
+ };
66
288
  //#endregion
67
289
  //#region src/components/button.tsx
68
- const Button = ({ variant = "primary", disabled, onClick, children }) => {
290
+ const Button = ({ variant = "primary", disabled, loading, onClick, children }) => {
69
291
  const { colors, radius } = useTheme();
70
292
  const filled = variant === "primary" && !disabled;
71
293
  let color = colors.text;
72
294
  if (filled) color = colors.surface;
73
295
  else if (disabled) color = colors.subtle;
74
- return /* @__PURE__ */ jsx("button", {
296
+ return /* @__PURE__ */ jsxs("button", {
75
297
  type: "button",
76
298
  className: "dot-button",
77
299
  "data-variant": variant,
78
300
  disabled,
79
- onClick: onClick ? () => onClick(createEvent("click")) : void 0,
301
+ "aria-busy": loading,
302
+ onClick: onClick && !loading ? () => onClick(createEvent("click")) : void 0,
80
303
  style: {
81
- padding: "10px 16px",
304
+ display: "inline-flex",
305
+ alignItems: "center",
306
+ justifyContent: "center",
307
+ gap: SPACE.medium,
308
+ minHeight: 48,
309
+ padding: "12px 20px",
82
310
  border: "none",
83
311
  borderRadius: radius,
84
312
  background: filled ? colors.primary : colors.muted,
@@ -87,22 +315,788 @@ const Button = ({ variant = "primary", disabled, onClick, children }) => {
87
315
  fontSize: FONT_SIZE.body,
88
316
  fontWeight: 600,
89
317
  opacity: disabled ? DISABLED_OPACITY : 1,
318
+ cursor: disabled || loading ? "default" : "pointer"
319
+ },
320
+ children: [loading ? /* @__PURE__ */ jsx(LoadingIcon, { size: ICON_SIZE.small }) : null, children]
321
+ });
322
+ };
323
+ //#endregion
324
+ //#region src/components/carousel.tsx
325
+ const SLIDE_DURATION = 350;
326
+ const SLIDE_EASING = "cubic-bezier(0.22, 0.61, 0.36, 1)";
327
+ const SWIPE_DISTANCE = .2;
328
+ const SWIPE_SPEED = .4;
329
+ const EDGE_RESISTANCE = .3;
330
+ const TAP_SLOP = 6;
331
+ const Carousel = ({ autoplay = false, interval = 4e3, loop = false, indicator = "dots", gap = "none", effect = "slide", index, onChange, children }) => {
332
+ const { colors } = useTheme();
333
+ const slides = slidesOf(children);
334
+ const count = slides.length;
335
+ const looping = loop && count > 1;
336
+ const first = looping ? 1 : 0;
337
+ const last = looping ? count + 1 : count - 1;
338
+ const [position, setPosition] = useState(() => first + slideIndex(index ?? 0, count));
339
+ const [animated, setAnimated] = useState(true);
340
+ const [drag, setDrag] = useState(0);
341
+ const [dragging, setDragging] = useState(false);
342
+ const [width, setWidth] = useState(0);
343
+ const [previousIndex, setPreviousIndex] = useState(index);
344
+ const viewport = useRef(null);
345
+ const gesture = useRef(null);
346
+ const swiped = useRef(false);
347
+ if (index !== previousIndex) {
348
+ setPreviousIndex(index);
349
+ if (index !== void 0) {
350
+ setAnimated(true);
351
+ setPosition(first + slideIndex(index, count));
352
+ }
353
+ }
354
+ const shown = clamp(position, 0, last);
355
+ const current = count === 0 ? 0 : wrap(shown - first, count);
356
+ const moveTo = (target) => {
357
+ const next = clamp(target, 0, last);
358
+ setAnimated(true);
359
+ setPosition(next);
360
+ const nextIndex = wrap(next - first, count);
361
+ if (nextIndex !== current) onChange?.(createEvent("change", { index: nextIndex }));
362
+ };
363
+ const step = (direction) => {
364
+ const beyondEnd = current + direction < 0 || current + direction >= count;
365
+ moveTo(!looping && beyondEnd ? shown : shown + direction);
366
+ };
367
+ const advance = useEffectEvent(() => {
368
+ if (!looping && current === count - 1) moveTo(first);
369
+ else moveTo(shown + 1);
370
+ });
371
+ useEffect(() => {
372
+ if (!autoplay || count < 2 || dragging) return;
373
+ const timer = setTimeout(advance, interval);
374
+ return () => clearTimeout(timer);
375
+ }, [
376
+ autoplay,
377
+ count,
378
+ dragging,
379
+ interval,
380
+ current
381
+ ]);
382
+ useEffect(() => {
383
+ if (!looping || position !== 0 && position !== count + 1) return;
384
+ const timer = setTimeout(() => {
385
+ setAnimated(false);
386
+ setPosition(position === 0 ? count : 1);
387
+ }, SLIDE_DURATION);
388
+ return () => clearTimeout(timer);
389
+ }, [
390
+ looping,
391
+ position,
392
+ count
393
+ ]);
394
+ const gestureOf = (event) => {
395
+ const touch = gesture.current;
396
+ return touch?.pointerId === event.pointerId ? touch : null;
397
+ };
398
+ const endGesture = () => {
399
+ gesture.current = null;
400
+ setDragging(false);
401
+ setDrag(0);
402
+ setAnimated(true);
403
+ };
404
+ const onPointerDown = (event) => {
405
+ if (count < 2 || !event.isPrimary) return;
406
+ gesture.current = {
407
+ pointerId: event.pointerId,
408
+ startX: event.clientX,
409
+ startY: event.clientY,
410
+ startTime: event.timeStamp,
411
+ swiping: false
412
+ };
413
+ swiped.current = false;
414
+ setDragging(true);
415
+ setWidth(viewport.current?.clientWidth ?? 0);
416
+ };
417
+ const onPointerMove = (event) => {
418
+ const touch = gestureOf(event);
419
+ if (!touch) return;
420
+ const dx = event.clientX - touch.startX;
421
+ const dy = event.clientY - touch.startY;
422
+ if (!touch.swiping) {
423
+ if (Math.abs(dx) < TAP_SLOP && Math.abs(dy) < TAP_SLOP) return;
424
+ if (Math.abs(dy) > Math.abs(dx)) {
425
+ endGesture();
426
+ return;
427
+ }
428
+ touch.swiping = true;
429
+ swiped.current = true;
430
+ event.currentTarget.setPointerCapture?.(event.pointerId);
431
+ setAnimated(false);
432
+ }
433
+ const pastEnd = !looping && (current === 0 && dx > 0 || current === count - 1 && dx < 0);
434
+ setDrag(pastEnd ? dx * EDGE_RESISTANCE : dx);
435
+ };
436
+ const onPointerUp = (event) => {
437
+ const touch = gestureOf(event);
438
+ if (!touch) return;
439
+ endGesture();
440
+ if (!touch.swiping) return;
441
+ const dx = event.clientX - touch.startX;
442
+ const speed = Math.abs(dx) / Math.max(1, event.timeStamp - touch.startTime);
443
+ if (Math.abs(dx) > width * SWIPE_DISTANCE || speed > SWIPE_SPEED) step(dx < 0 ? 1 : -1);
444
+ else moveTo(shown);
445
+ };
446
+ const gapWidth = SPACE[gap];
447
+ const transition = animated ? `transform ${SLIDE_DURATION}ms ${SLIDE_EASING}` : "none";
448
+ const swipedSlides = width > 0 ? drag / (width + gapWidth) : 0;
449
+ const track = looping ? [
450
+ {
451
+ ...slides[count - 1],
452
+ key: "copy-of-last"
453
+ },
454
+ ...slides,
455
+ {
456
+ ...slides[0],
457
+ key: "copy-of-first"
458
+ }
459
+ ] : slides;
460
+ return /* @__PURE__ */ jsxs("div", {
461
+ className: "dot-carousel",
462
+ role: "region",
463
+ "aria-roledescription": "carousel",
464
+ style: {
465
+ display: "flex",
466
+ flexDirection: "column",
467
+ gap: SPACE.medium,
468
+ minWidth: 0
469
+ },
470
+ children: [/* @__PURE__ */ jsx("div", {
471
+ ref: viewport,
472
+ onPointerDown,
473
+ onPointerMove,
474
+ onPointerUp,
475
+ onPointerCancel: endGesture,
476
+ onDragStart: (event) => event.preventDefault(),
477
+ onClickCapture: (event) => {
478
+ if (swiped.current) {
479
+ swiped.current = false;
480
+ event.stopPropagation();
481
+ }
482
+ },
483
+ style: {
484
+ overflow: "hidden",
485
+ touchAction: "pan-y",
486
+ userSelect: dragging ? "none" : void 0
487
+ },
488
+ children: /* @__PURE__ */ jsx("div", {
489
+ style: {
490
+ display: "flex",
491
+ gap: gapWidth,
492
+ transform: `translateX(calc(${-shown} * (100% + ${gapWidth}px) + ${drag}px))`,
493
+ transition
494
+ },
495
+ children: track.map((slide, trackPosition) => {
496
+ const visible = trackPosition === shown;
497
+ const distance = Math.abs(trackPosition - shown + swipedSlides);
498
+ const scale = 1 - Math.min(distance, 1) * .15000000000000002;
499
+ return /* @__PURE__ */ jsx("div", {
500
+ role: "group",
501
+ "aria-roledescription": "slide",
502
+ "aria-label": `${wrap(trackPosition - first, count) + 1} of ${count}`,
503
+ "aria-hidden": !visible,
504
+ inert: !visible,
505
+ style: {
506
+ display: "grid",
507
+ flex: "0 0 100%",
508
+ minWidth: 0,
509
+ transform: effect === "scale" ? `scale(${scale})` : void 0,
510
+ transition: effect === "scale" ? transition : void 0
511
+ },
512
+ children: slide.node
513
+ }, slide.key);
514
+ })
515
+ })
516
+ }), indicator === "dots" && count > 1 ? /* @__PURE__ */ jsx("div", {
517
+ style: {
518
+ display: "flex",
519
+ justifyContent: "center"
520
+ },
521
+ children: slides.map((slide, slideIndex) => {
522
+ const active = slideIndex === current;
523
+ return /* @__PURE__ */ jsx("button", {
524
+ type: "button",
525
+ "aria-label": `Slide ${slideIndex + 1} of ${count}`,
526
+ "aria-current": active,
527
+ onClick: () => moveTo(first + slideIndex),
528
+ style: {
529
+ padding: "6px 3px",
530
+ border: "none",
531
+ background: "none",
532
+ cursor: "pointer"
533
+ },
534
+ children: /* @__PURE__ */ jsx("span", { style: {
535
+ display: "block",
536
+ width: active ? 18 : 6,
537
+ height: 6,
538
+ borderRadius: 3,
539
+ background: active ? colors.primary : colors.subtle,
540
+ transition: "width 0.3s, background 0.3s"
541
+ } })
542
+ }, slide.key);
543
+ })
544
+ }) : null]
545
+ });
546
+ };
547
+ function slidesOf(children, keyPrefix = "") {
548
+ const slides = [];
549
+ for (const child of Children.toArray(children)) {
550
+ if (!isValidElement(child)) continue;
551
+ const key = `${keyPrefix}${String(child.key)}`;
552
+ if (child.type === Fragment) slides.push(...slidesOf(child.props.children, `${key}/`));
553
+ else slides.push({
554
+ key,
555
+ node: child
556
+ });
557
+ }
558
+ return slides;
559
+ }
560
+ function clamp(value, min, max) {
561
+ return Math.max(min, Math.min(value, max));
562
+ }
563
+ function slideIndex(index, count) {
564
+ return clamp(Math.round(index), 0, count - 1);
565
+ }
566
+ function wrap(value, count) {
567
+ return (value % count + count) % count;
568
+ }
569
+ //#endregion
570
+ //#region src/components/tones.ts
571
+ const STATUS_ICONS = {
572
+ default: "info",
573
+ info: "info",
574
+ success: "success",
575
+ warning: "warning",
576
+ danger: "error"
577
+ };
578
+ function toneColor(colors, tone) {
579
+ switch (tone) {
580
+ case "default": return colors.text;
581
+ case "muted": return colors.subtle;
582
+ default: return colors[tone];
583
+ }
584
+ }
585
+ function toneBackground(colors, tone) {
586
+ if (tone === "default" || tone === "muted") return colors.muted;
587
+ return withOpacity(colors[tone], .14);
588
+ }
589
+ function withOpacity(color, opacity) {
590
+ return `${color}${Math.round(opacity * 255).toString(16).padStart(2, "0")}`;
591
+ }
592
+ //#endregion
593
+ //#region src/components/icon.tsx
594
+ const ICONS = {
595
+ "arrow-down": ArrowDown,
596
+ "arrow-left": ArrowLeft,
597
+ "arrow-right": ArrowRight,
598
+ "arrow-up": ArrowUp,
599
+ bank: Landmark,
600
+ bell: Bell,
601
+ bolt: Zap,
602
+ calendar: Calendar,
603
+ card: CreditCard,
604
+ cart: ShoppingCart,
605
+ cash: Banknote,
606
+ check: Check,
607
+ "chevron-down": ChevronDown,
608
+ "chevron-left": ChevronLeft,
609
+ "chevron-right": ChevronRight,
610
+ "chevron-up": ChevronUp,
611
+ clock: Clock,
612
+ close: X,
613
+ copy: Copy,
614
+ download: Download,
615
+ edit: Pencil,
616
+ error: CircleAlert,
617
+ eye: Eye,
618
+ "eye-off": EyeOff,
619
+ file: File,
620
+ gift: Gift,
621
+ grid: LayoutGrid,
622
+ heart: Heart,
623
+ help: CircleHelp,
624
+ history: History,
625
+ home: House,
626
+ image: Image,
627
+ info: Info,
628
+ list: List,
629
+ location: MapPin,
630
+ lock: Lock,
631
+ logout: LogOut,
632
+ mail: Mail,
633
+ minus: Minus,
634
+ mobile: Smartphone,
635
+ more: Ellipsis,
636
+ phone: Phone,
637
+ plus: Plus,
638
+ "qr-code": QrCode,
639
+ receipt: Receipt,
640
+ refresh: RefreshCw,
641
+ search: Search,
642
+ send: Send,
643
+ settings: Settings,
644
+ share: Share2,
645
+ star: Star,
646
+ success: CircleCheck,
647
+ trash: Trash2,
648
+ upload: Upload,
649
+ user: User,
650
+ users: Users,
651
+ wallet: Wallet,
652
+ warning: TriangleAlert,
653
+ water: Droplet,
654
+ wifi: Wifi
655
+ };
656
+ function IconGraphic({ name, size, color }) {
657
+ return /* @__PURE__ */ jsx("svg", {
658
+ className: "dot-icon",
659
+ width: size,
660
+ height: size,
661
+ viewBox: "0 0 24 24",
662
+ fill: "none",
663
+ stroke: color,
664
+ strokeWidth: 2,
665
+ strokeLinecap: "round",
666
+ strokeLinejoin: "round",
667
+ "aria-hidden": "true",
668
+ style: {
669
+ display: "inline-block",
670
+ verticalAlign: "middle",
671
+ flexShrink: 0
672
+ },
673
+ children: ICONS[name].map(([tag, attributes], index) => createElement(tag, {
674
+ key: index,
675
+ ...attributes
676
+ }))
677
+ });
678
+ }
679
+ const Icon = ({ name, size = "medium", tone = "default" }) => {
680
+ const { colors } = useTheme();
681
+ if (!name) return null;
682
+ return /* @__PURE__ */ jsx(IconGraphic, {
683
+ name,
684
+ size: ICON_SIZE[size],
685
+ color: toneColor(colors, tone)
686
+ });
687
+ };
688
+ //#endregion
689
+ //#region src/components/chip.tsx
690
+ const Chip = ({ label, icon, selected = false, disabled, onTap }) => {
691
+ const { colors } = useTheme();
692
+ const color = selected ? colors.surface : colors.text;
693
+ return /* @__PURE__ */ jsxs("button", {
694
+ type: "button",
695
+ className: "dot-chip",
696
+ "aria-pressed": selected,
697
+ disabled,
698
+ onClick: onTap ? () => onTap(createEvent("tap")) : void 0,
699
+ style: {
700
+ display: "inline-flex",
701
+ alignItems: "center",
702
+ gap: 6,
703
+ flexShrink: 0,
704
+ minHeight: 34,
705
+ padding: "0 14px",
706
+ borderRadius: 17,
707
+ border: `1px solid ${selected ? colors.primary : colors.border}`,
708
+ background: selected ? colors.primary : colors.surface,
709
+ color,
710
+ font: "inherit",
711
+ fontSize: FONT_SIZE.caption,
712
+ fontWeight: 500,
713
+ whiteSpace: "nowrap",
714
+ opacity: disabled ? DISABLED_OPACITY : 1,
90
715
  cursor: disabled ? "default" : "pointer"
91
716
  },
717
+ children: [icon ? /* @__PURE__ */ jsx(IconGraphic, {
718
+ name: icon,
719
+ size: ICON_SIZE.small,
720
+ color
721
+ }) : null, label]
722
+ });
723
+ };
724
+ //#endregion
725
+ //#region src/components/code-input.tsx
726
+ const CodeInput = ({ value, length = 6, label, hint, error, secure, disabled, onChange, onComplete }) => {
727
+ const { colors, radius } = useTheme();
728
+ const [text, typeText] = useTypedText(value, onChange !== void 0);
729
+ const [focused, setFocused] = useState(false);
730
+ const count = Math.max(1, Math.round(length));
731
+ const digits = text.slice(0, count);
732
+ const next = Math.min(digits.length, count - 1);
733
+ const boxes = Array.from({ length: count }, (_, index) => {
734
+ const digit = digits[index];
735
+ let borderColor = colors.border;
736
+ if (error) borderColor = colors.danger;
737
+ else if (focused && index === next) borderColor = colors.primary;
738
+ return /* @__PURE__ */ jsx("span", {
739
+ style: {
740
+ display: "flex",
741
+ alignItems: "center",
742
+ justifyContent: "center",
743
+ flex: "1 1 0",
744
+ maxWidth: 52,
745
+ height: 52,
746
+ border: `1.5px solid ${borderColor}`,
747
+ borderRadius: radius,
748
+ background: colors.surface,
749
+ fontSize: 22,
750
+ fontWeight: 600
751
+ },
752
+ children: digit !== void 0 && secure ? /* @__PURE__ */ jsx("span", {
753
+ "aria-hidden": "true",
754
+ style: {
755
+ width: 12,
756
+ height: 12,
757
+ borderRadius: "50%",
758
+ background: colors.text
759
+ }
760
+ }) : digit
761
+ }, index);
762
+ });
763
+ return /* @__PURE__ */ jsx(Field, {
764
+ label,
765
+ hint,
766
+ error,
767
+ children: /* @__PURE__ */ jsxs("span", {
768
+ className: "dot-code-input",
769
+ style: {
770
+ position: "relative",
771
+ display: "flex",
772
+ gap: SPACE.medium,
773
+ color: colors.text,
774
+ opacity: disabled ? DISABLED_OPACITY : 1
775
+ },
776
+ children: [boxes, /* @__PURE__ */ jsx("input", {
777
+ value: digits,
778
+ inputMode: "numeric",
779
+ autoComplete: "one-time-code",
780
+ maxLength: count,
781
+ disabled,
782
+ "aria-invalid": error ? true : void 0,
783
+ onFocus: () => setFocused(true),
784
+ onBlur: () => setFocused(false),
785
+ onSelect: (event) => {
786
+ const input = event.currentTarget;
787
+ const end = input.value.length;
788
+ if (input.selectionStart !== end || input.selectionEnd !== end) input.setSelectionRange(end, end);
789
+ },
790
+ onChange: (event) => {
791
+ const typed = event.currentTarget.value.replace(/\D/g, "").slice(0, count);
792
+ if (typed === digits) return;
793
+ typeText(typed);
794
+ onChange?.(createEvent("change", { value: typed }));
795
+ if (typed.length === count) onComplete?.(createEvent("complete", { value: typed }));
796
+ },
797
+ style: {
798
+ position: "absolute",
799
+ inset: 0,
800
+ width: "100%",
801
+ height: "100%",
802
+ padding: 0,
803
+ border: "none",
804
+ opacity: 0,
805
+ fontSize: 16,
806
+ cursor: disabled ? "default" : "text"
807
+ }
808
+ })]
809
+ })
810
+ });
811
+ };
812
+ //#endregion
813
+ //#region src/components/divider.tsx
814
+ const Divider = () => {
815
+ const { colors } = useTheme();
816
+ return /* @__PURE__ */ jsx("div", {
817
+ className: "dot-divider",
818
+ role: "separator",
819
+ style: {
820
+ height: 1,
821
+ alignSelf: "stretch",
822
+ flexShrink: 0,
823
+ background: colors.border
824
+ }
825
+ });
826
+ };
827
+ //#endregion
828
+ //#region src/components/empty-state.tsx
829
+ const EmptyState = ({ icon, title, text, children }) => {
830
+ const { colors } = useTheme();
831
+ return /* @__PURE__ */ jsxs("div", {
832
+ className: "dot-empty-state",
833
+ style: {
834
+ display: "flex",
835
+ flexDirection: "column",
836
+ alignItems: "center",
837
+ gap: SPACE.medium,
838
+ padding: "32px 16px",
839
+ textAlign: "center",
840
+ color: colors.text
841
+ },
842
+ children: [
843
+ icon ? /* @__PURE__ */ jsx("span", {
844
+ style: {
845
+ display: "flex",
846
+ alignItems: "center",
847
+ justifyContent: "center",
848
+ width: 64,
849
+ height: 64,
850
+ borderRadius: "50%",
851
+ background: colors.muted
852
+ },
853
+ children: /* @__PURE__ */ jsx(IconGraphic, {
854
+ name: icon,
855
+ size: ICON_SIZE.large,
856
+ color: colors.subtle
857
+ })
858
+ }) : null,
859
+ title ? /* @__PURE__ */ jsx("span", {
860
+ style: {
861
+ fontSize: 17,
862
+ fontWeight: 600
863
+ },
864
+ children: title
865
+ }) : null,
866
+ text ? /* @__PURE__ */ jsx("span", {
867
+ style: {
868
+ maxWidth: 300,
869
+ fontSize: FONT_SIZE.body,
870
+ color: colors.subtle
871
+ },
872
+ children: text
873
+ }) : null,
874
+ children ? /* @__PURE__ */ jsx("div", {
875
+ style: {
876
+ display: "flex",
877
+ gap: SPACE.medium,
878
+ paddingTop: SPACE.small
879
+ },
880
+ children
881
+ }) : null
882
+ ]
883
+ });
884
+ };
885
+ //#endregion
886
+ //#region src/components/feedback.tsx
887
+ const ProgressBar = ({ value = 0 }) => {
888
+ const { colors } = useTheme();
889
+ const percent = Math.min(100, Math.max(0, value));
890
+ return /* @__PURE__ */ jsx("div", {
891
+ className: "dot-progress-bar",
892
+ role: "progressbar",
893
+ "aria-valuemin": 0,
894
+ "aria-valuemax": 100,
895
+ "aria-valuenow": percent,
896
+ style: {
897
+ height: 6,
898
+ borderRadius: 3,
899
+ background: colors.muted,
900
+ overflow: "hidden"
901
+ },
902
+ children: /* @__PURE__ */ jsx("div", { style: {
903
+ width: `${percent}%`,
904
+ height: "100%",
905
+ background: colors.primary,
906
+ transition: "width 0.2s"
907
+ } })
908
+ });
909
+ };
910
+ const Notice = ({ tone = "default", title, children }) => {
911
+ const { colors, radius } = useTheme();
912
+ return /* @__PURE__ */ jsxs("div", {
913
+ className: "dot-notice",
914
+ role: tone === "danger" ? "alert" : "status",
915
+ style: {
916
+ display: "flex",
917
+ gap: SPACE.medium,
918
+ padding: "12px 14px",
919
+ borderRadius: radius,
920
+ background: toneBackground(colors, tone),
921
+ color: colors.text,
922
+ fontSize: FONT_SIZE.body
923
+ },
924
+ children: [/* @__PURE__ */ jsx(IconGraphic, {
925
+ name: STATUS_ICONS[tone],
926
+ size: ICON_SIZE.medium,
927
+ color: toneColor(colors, tone)
928
+ }), /* @__PURE__ */ jsxs("div", {
929
+ style: {
930
+ display: "flex",
931
+ flexDirection: "column",
932
+ gap: 2
933
+ },
934
+ children: [title ? /* @__PURE__ */ jsx("span", {
935
+ style: { fontWeight: 600 },
936
+ children: title
937
+ }) : null, children ? /* @__PURE__ */ jsx("span", { children }) : null]
938
+ })]
939
+ });
940
+ };
941
+ const Badge = ({ tone = "default", children }) => {
942
+ const { colors } = useTheme();
943
+ return /* @__PURE__ */ jsx("span", {
944
+ className: "dot-badge",
945
+ style: {
946
+ display: "inline-flex",
947
+ alignItems: "center",
948
+ width: "fit-content",
949
+ padding: "2px 8px",
950
+ borderRadius: 999,
951
+ background: toneBackground(colors, tone),
952
+ color: toneColor(colors, tone),
953
+ fontSize: FONT_SIZE.caption,
954
+ fontWeight: 600,
955
+ whiteSpace: "nowrap"
956
+ },
957
+ children
958
+ });
959
+ };
960
+ //#endregion
961
+ //#region src/components/footer.tsx
962
+ const Footer = ({ children }) => {
963
+ const { colors } = useTheme();
964
+ return /* @__PURE__ */ jsx("div", {
965
+ className: "dot-footer",
966
+ style: {
967
+ position: "sticky",
968
+ bottom: 0,
969
+ marginTop: "auto",
970
+ display: "flex",
971
+ flexDirection: "column",
972
+ gap: SPACE.medium,
973
+ paddingTop: SPACE.medium,
974
+ paddingBottom: SPACE.medium,
975
+ background: colors.background
976
+ },
92
977
  children
93
978
  });
94
979
  };
95
980
  //#endregion
981
+ //#region src/components/pill.tsx
982
+ function Pill({ background, color, children }) {
983
+ return /* @__PURE__ */ jsx("span", {
984
+ style: {
985
+ minWidth: 18,
986
+ padding: "1px 6px",
987
+ borderRadius: 9,
988
+ background,
989
+ color,
990
+ fontSize: 11,
991
+ fontWeight: 600,
992
+ lineHeight: "16px",
993
+ textAlign: "center",
994
+ whiteSpace: "nowrap"
995
+ },
996
+ children
997
+ });
998
+ }
999
+ //#endregion
1000
+ //#region src/components/grid.tsx
1001
+ const TILE_SIZE = 52;
1002
+ const Grid = ({ columns = 4, gap = "medium", children }) => /* @__PURE__ */ jsx("div", {
1003
+ className: "dot-grid",
1004
+ style: {
1005
+ display: "grid",
1006
+ gridTemplateColumns: `repeat(${Math.max(1, Math.round(columns))}, minmax(0, 1fr))`,
1007
+ gap: SPACE[gap]
1008
+ },
1009
+ children
1010
+ });
1011
+ const GridItem = ({ label, icon, image, badge, onTap }) => {
1012
+ const { colors, radius } = useTheme();
1013
+ const tileRadius = radius * CARD_RADIUS_SCALE;
1014
+ let picture = null;
1015
+ if (image) picture = /* @__PURE__ */ jsx("img", {
1016
+ src: image,
1017
+ alt: "",
1018
+ width: TILE_SIZE,
1019
+ height: TILE_SIZE,
1020
+ style: {
1021
+ display: "block",
1022
+ borderRadius: tileRadius,
1023
+ objectFit: "cover"
1024
+ }
1025
+ });
1026
+ else if (icon) picture = /* @__PURE__ */ jsx(IconGraphic, {
1027
+ name: icon,
1028
+ size: 24,
1029
+ color: colors.primary
1030
+ });
1031
+ return /* @__PURE__ */ jsxs("div", {
1032
+ className: "dot-grid-item",
1033
+ role: onTap ? "button" : void 0,
1034
+ onClick: onTap ? () => onTap(createEvent("tap")) : void 0,
1035
+ style: {
1036
+ display: "flex",
1037
+ flexDirection: "column",
1038
+ alignItems: "center",
1039
+ gap: 6,
1040
+ padding: `${SPACE.small}px 0`,
1041
+ cursor: onTap ? "pointer" : void 0
1042
+ },
1043
+ children: [/* @__PURE__ */ jsxs("span", {
1044
+ style: {
1045
+ position: "relative",
1046
+ display: "flex",
1047
+ alignItems: "center",
1048
+ justifyContent: "center",
1049
+ width: TILE_SIZE,
1050
+ height: TILE_SIZE,
1051
+ borderRadius: tileRadius,
1052
+ background: colors.muted
1053
+ },
1054
+ children: [picture, badge ? /* @__PURE__ */ jsx("span", {
1055
+ style: {
1056
+ position: "absolute",
1057
+ top: -6,
1058
+ right: -10
1059
+ },
1060
+ children: /* @__PURE__ */ jsx(Pill, {
1061
+ background: colors.danger,
1062
+ color: "#ffffff",
1063
+ children: badge
1064
+ })
1065
+ }) : null]
1066
+ }), label ? /* @__PURE__ */ jsx("span", {
1067
+ style: {
1068
+ display: "-webkit-box",
1069
+ WebkitLineClamp: 2,
1070
+ WebkitBoxOrient: "vertical",
1071
+ overflow: "hidden",
1072
+ width: "100%",
1073
+ textAlign: "center",
1074
+ fontSize: FONT_SIZE.caption,
1075
+ color: colors.text
1076
+ },
1077
+ children: label
1078
+ }) : null]
1079
+ });
1080
+ };
1081
+ //#endregion
96
1082
  //#region src/components/image.tsx
97
- const Image = ({ src, alt, width, height, onLoad, onError }) => {
1083
+ const Image$1 = ({ src, alt, width, height, fit = "cover", shape = "rounded", onLoad, onError }) => {
98
1084
  const { radius } = useTheme();
1085
+ const corners = {
1086
+ rounded: radius,
1087
+ square: 0,
1088
+ circle: "50%"
1089
+ }[shape];
99
1090
  return /* @__PURE__ */ jsx("img", {
100
1091
  className: "dot-image",
101
1092
  src,
102
1093
  alt: alt ?? "",
103
1094
  width,
104
1095
  height,
105
- style: { borderRadius: radius },
1096
+ style: {
1097
+ borderRadius: corners,
1098
+ objectFit: fit
1099
+ },
106
1100
  onLoad: onLoad ? (event) => {
107
1101
  const { naturalWidth, naturalHeight } = event.currentTarget;
108
1102
  onLoad(createEvent("load", {
@@ -137,50 +1131,530 @@ const HTML_TYPES = {
137
1131
  inputMode: "text"
138
1132
  }
139
1133
  };
140
- const Input = ({ value, placeholder, label, type = "text", maxLength, disabled, onChange, onSubmit }) => {
1134
+ const Input = ({ value, placeholder, label, hint, error, prefix, suffix, type = "text", maxLength, disabled, onChange, onSubmit }) => {
1135
+ const { colors } = useTheme();
1136
+ const [text, typeText] = useTypedText(value, onChange !== void 0);
1137
+ const box = useFieldBox(error, disabled);
1138
+ return /* @__PURE__ */ jsx(Field, {
1139
+ label,
1140
+ hint,
1141
+ error,
1142
+ children: /* @__PURE__ */ jsxs("span", {
1143
+ className: "dot-input",
1144
+ style: {
1145
+ ...box.style,
1146
+ display: "flex",
1147
+ alignItems: "center",
1148
+ gap: SPACE.medium,
1149
+ padding: "0 14px"
1150
+ },
1151
+ children: [
1152
+ prefix ? /* @__PURE__ */ jsx("span", {
1153
+ style: { color: colors.subtle },
1154
+ children: prefix
1155
+ }) : null,
1156
+ /* @__PURE__ */ jsx("input", {
1157
+ ...HTML_TYPES[type],
1158
+ value: text,
1159
+ placeholder,
1160
+ maxLength,
1161
+ disabled,
1162
+ "aria-invalid": error ? true : void 0,
1163
+ onFocus: box.onFocus,
1164
+ onBlur: box.onBlur,
1165
+ onChange: (event) => {
1166
+ const next = event.currentTarget.value;
1167
+ typeText(next);
1168
+ onChange?.(createEvent("change", { value: next }));
1169
+ },
1170
+ onKeyDown: (event) => {
1171
+ if (event.key === "Enter" && onSubmit) onSubmit(createEvent("submit", { value: text }));
1172
+ },
1173
+ style: {
1174
+ flexGrow: 1,
1175
+ minWidth: 0,
1176
+ padding: "12px 0",
1177
+ border: "none",
1178
+ outline: "none",
1179
+ background: "transparent",
1180
+ color: "inherit",
1181
+ font: "inherit"
1182
+ }
1183
+ }),
1184
+ suffix ? /* @__PURE__ */ jsx("span", {
1185
+ style: { color: colors.subtle },
1186
+ children: suffix
1187
+ }) : null
1188
+ ]
1189
+ })
1190
+ });
1191
+ };
1192
+ //#endregion
1193
+ //#region src/components/list.tsx
1194
+ const List$1 = ({ children }) => {
141
1195
  const { colors, radius } = useTheme();
142
- const [text, setText] = useState(value ?? "");
143
- const [awaitingEcho, setAwaitingEcho] = useState([]);
144
- const [previousValue, setPreviousValue] = useState(value);
145
- if (value !== previousValue) {
146
- setPreviousValue(value);
147
- const echo = awaitingEcho.indexOf(value ?? "");
148
- if (echo >= 0) setAwaitingEcho(awaitingEcho.slice(echo + 1));
149
- else {
150
- setAwaitingEcho([]);
151
- setText(value ?? "");
1196
+ return /* @__PURE__ */ jsx("div", {
1197
+ className: "dot-list",
1198
+ style: {
1199
+ display: "flex",
1200
+ flexDirection: "column",
1201
+ gap: 1,
1202
+ overflow: "hidden",
1203
+ borderRadius: radius * CARD_RADIUS_SCALE,
1204
+ border: `1px solid ${colors.border}`,
1205
+ background: colors.border
1206
+ },
1207
+ children
1208
+ });
1209
+ };
1210
+ const ListItem = ({ title, subtitle, value, icon, image, onTap, children }) => {
1211
+ const { colors, radius } = useTheme();
1212
+ let leading = null;
1213
+ if (image) leading = /* @__PURE__ */ jsx("img", {
1214
+ src: image,
1215
+ alt: "",
1216
+ width: 40,
1217
+ height: 40,
1218
+ style: {
1219
+ borderRadius: radius,
1220
+ objectFit: "cover",
1221
+ flexShrink: 0
152
1222
  }
153
- }
154
- return /* @__PURE__ */ jsx("input", {
155
- className: "dot-input",
156
- ...HTML_TYPES[type],
157
- value: text,
158
- placeholder,
159
- "aria-label": label,
160
- maxLength,
161
- disabled,
162
- onChange: (event) => {
163
- const next = event.currentTarget.value;
164
- setText(next);
165
- if (onChange && value !== void 0) setAwaitingEcho([...awaitingEcho, next]);
166
- onChange?.(createEvent("change", { value: next }));
1223
+ });
1224
+ else if (icon) leading = /* @__PURE__ */ jsx("span", {
1225
+ style: {
1226
+ display: "flex",
1227
+ alignItems: "center",
1228
+ justifyContent: "center",
1229
+ width: 36,
1230
+ height: 36,
1231
+ borderRadius: "50%",
1232
+ background: colors.muted,
1233
+ flexShrink: 0
167
1234
  },
168
- onKeyDown: (event) => {
169
- if (event.key === "Enter" && onSubmit) onSubmit(createEvent("submit", { value: text }));
1235
+ children: /* @__PURE__ */ jsx(IconGraphic, {
1236
+ name: icon,
1237
+ size: ICON_SIZE.medium,
1238
+ color: colors.text
1239
+ })
1240
+ });
1241
+ let trailing = children;
1242
+ if (!trailing && value) trailing = /* @__PURE__ */ jsx("span", {
1243
+ style: { color: colors.subtle },
1244
+ children: value
1245
+ });
1246
+ return /* @__PURE__ */ jsxs("div", {
1247
+ className: "dot-list-item",
1248
+ role: onTap ? "button" : void 0,
1249
+ onClick: onTap ? () => onTap(createEvent("tap")) : void 0,
1250
+ style: {
1251
+ display: "flex",
1252
+ alignItems: "center",
1253
+ gap: 12,
1254
+ padding: "12px 16px",
1255
+ background: colors.surface,
1256
+ fontSize: FONT_SIZE.body,
1257
+ cursor: onTap ? "pointer" : void 0
170
1258
  },
1259
+ children: [
1260
+ leading,
1261
+ /* @__PURE__ */ jsxs("span", {
1262
+ style: {
1263
+ display: "flex",
1264
+ flexDirection: "column",
1265
+ flexGrow: 1,
1266
+ minWidth: 0
1267
+ },
1268
+ children: [title ? /* @__PURE__ */ jsx("span", {
1269
+ style: { fontWeight: 500 },
1270
+ children: title
1271
+ }) : null, subtitle ? /* @__PURE__ */ jsx("span", {
1272
+ style: {
1273
+ fontSize: FONT_SIZE.caption,
1274
+ color: colors.subtle
1275
+ },
1276
+ children: subtitle
1277
+ }) : null]
1278
+ }),
1279
+ trailing ? /* @__PURE__ */ jsx("span", {
1280
+ style: {
1281
+ display: "flex",
1282
+ alignItems: "center",
1283
+ gap: SPACE.small,
1284
+ flexShrink: 0
1285
+ },
1286
+ children: trailing
1287
+ }) : null,
1288
+ onTap ? /* @__PURE__ */ jsx(IconGraphic, {
1289
+ name: "chevron-right",
1290
+ size: ICON_SIZE.small,
1291
+ color: colors.subtle
1292
+ }) : null
1293
+ ]
1294
+ });
1295
+ };
1296
+ //#endregion
1297
+ //#region src/components/qr-code.tsx
1298
+ const DARK = "#000000";
1299
+ const LIGHT = "#ffffff";
1300
+ const QUIET_ZONE = 2;
1301
+ const QRCode = ({ value, size = 200, label }) => {
1302
+ const { radius } = useTheme();
1303
+ const drawing = useMemo(() => value ? drawCode(value) : null, [value]);
1304
+ return /* @__PURE__ */ jsx("svg", {
1305
+ className: "dot-qr-code",
1306
+ role: "img",
1307
+ "aria-label": label ?? value,
1308
+ width: size,
1309
+ height: size,
1310
+ viewBox: drawing ? `0 0 ${drawing.size} ${drawing.size}` : "0 0 1 1",
1311
+ shapeRendering: "crispEdges",
171
1312
  style: {
172
- width: "100%",
173
- boxSizing: "border-box",
174
- padding: "12px 14px",
175
- border: `1px solid ${colors.border}`,
1313
+ display: "block",
1314
+ flexShrink: 0,
176
1315
  borderRadius: radius,
177
- background: colors.surface,
1316
+ background: LIGHT
1317
+ },
1318
+ children: drawing ? /* @__PURE__ */ jsx("path", {
1319
+ d: drawing.path,
1320
+ fill: DARK
1321
+ }) : null
1322
+ });
1323
+ };
1324
+ function drawCode(value) {
1325
+ let modules;
1326
+ try {
1327
+ modules = encode(value, {
1328
+ ecc: "M",
1329
+ border: QUIET_ZONE
1330
+ }).data;
1331
+ } catch {
1332
+ return null;
1333
+ }
1334
+ const squares = [];
1335
+ modules.forEach((row, y) => {
1336
+ row.forEach((dark, x) => {
1337
+ if (dark) squares.push(`M${x} ${y}h1v1h-1z`);
1338
+ });
1339
+ });
1340
+ return {
1341
+ size: modules.length,
1342
+ path: squares.join("")
1343
+ };
1344
+ }
1345
+ //#endregion
1346
+ //#region src/components/animate.ts
1347
+ function animate(element, keyframes, options) {
1348
+ if (!element || typeof element.animate !== "function") return;
1349
+ return element.animate(keyframes, options);
1350
+ }
1351
+ //#endregion
1352
+ //#region src/components/sheet.tsx
1353
+ const SLIDE_UP = [{ transform: "translateY(100%)" }, { transform: "translateY(0)" }];
1354
+ function SheetPanel({ open, title, onClose, children }) {
1355
+ const { colors, radius } = useTheme();
1356
+ const panel = useRef(null);
1357
+ useEffect(() => {
1358
+ if (open) animate(panel.current, SLIDE_UP, {
1359
+ duration: 200,
1360
+ easing: "ease-out"
1361
+ });
1362
+ }, [open]);
1363
+ if (!open) return null;
1364
+ const corner = radius * CARD_RADIUS_SCALE;
1365
+ return /* @__PURE__ */ jsx("div", {
1366
+ className: "dot-sheet",
1367
+ onClick: onClose,
1368
+ style: {
1369
+ position: "fixed",
1370
+ inset: 0,
1371
+ zIndex: 100,
1372
+ display: "flex",
1373
+ flexDirection: "column",
1374
+ justifyContent: "flex-end",
1375
+ background: "rgba(0, 0, 0, 0.4)"
1376
+ },
1377
+ children: /* @__PURE__ */ jsxs("div", {
1378
+ ref: panel,
1379
+ role: "dialog",
1380
+ "aria-modal": "true",
1381
+ "aria-label": title,
1382
+ onClick: (event) => event.stopPropagation(),
1383
+ style: {
1384
+ display: "flex",
1385
+ flexDirection: "column",
1386
+ gap: SPACE.medium,
1387
+ maxHeight: "85vh",
1388
+ overflowY: "auto",
1389
+ padding: `${SPACE.large}px ${SPACE.large}px 24px`,
1390
+ borderTopLeftRadius: corner,
1391
+ borderTopRightRadius: corner,
1392
+ background: colors.surface,
1393
+ color: colors.text
1394
+ },
1395
+ children: [title ? /* @__PURE__ */ jsx("span", {
1396
+ style: {
1397
+ fontSize: FONT_SIZE.body,
1398
+ fontWeight: 600,
1399
+ textAlign: "center"
1400
+ },
1401
+ children: title
1402
+ }) : null, children]
1403
+ })
1404
+ });
1405
+ }
1406
+ const Sheet = ({ open = false, title, onClose, children }) => /* @__PURE__ */ jsx(SheetPanel, {
1407
+ open,
1408
+ title,
1409
+ onClose: () => onClose?.(createEvent("close")),
1410
+ children
1411
+ });
1412
+ //#endregion
1413
+ //#region src/components/select.tsx
1414
+ const OptionPlaceContext = createContext({
1415
+ inField: false,
1416
+ value: void 0,
1417
+ choose: void 0
1418
+ });
1419
+ const Select = ({ value, label, placeholder, hint, error, disabled, onChange, children }) => {
1420
+ const { colors } = useTheme();
1421
+ const [open, setOpen] = useState(false);
1422
+ const box = useFieldBox(error, disabled);
1423
+ const choose = (next) => {
1424
+ setOpen(false);
1425
+ onChange?.(createEvent("change", { value: next }));
1426
+ };
1427
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Field, {
1428
+ label,
1429
+ hint,
1430
+ error,
1431
+ children: /* @__PURE__ */ jsxs("button", {
1432
+ type: "button",
1433
+ className: "dot-select",
1434
+ disabled,
1435
+ onClick: () => setOpen(true),
1436
+ onFocus: box.onFocus,
1437
+ onBlur: box.onBlur,
1438
+ style: {
1439
+ font: "inherit",
1440
+ ...box.style,
1441
+ display: "flex",
1442
+ alignItems: "center",
1443
+ gap: SPACE.medium,
1444
+ width: "100%",
1445
+ padding: "12px 14px",
1446
+ textAlign: "start",
1447
+ cursor: disabled ? "default" : "pointer"
1448
+ },
1449
+ children: [/* @__PURE__ */ jsx("span", {
1450
+ style: { flexGrow: 1 },
1451
+ children: value ? /* @__PURE__ */ jsx(OptionPlaceContext.Provider, {
1452
+ value: {
1453
+ inField: true,
1454
+ value,
1455
+ choose: void 0
1456
+ },
1457
+ children
1458
+ }) : /* @__PURE__ */ jsx("span", {
1459
+ style: { color: colors.subtle },
1460
+ children: placeholder
1461
+ })
1462
+ }), /* @__PURE__ */ jsx(IconGraphic, {
1463
+ name: "chevron-down",
1464
+ size: ICON_SIZE.small,
1465
+ color: colors.subtle
1466
+ })]
1467
+ })
1468
+ }), /* @__PURE__ */ jsx(SheetPanel, {
1469
+ open,
1470
+ title: label,
1471
+ onClose: () => setOpen(false),
1472
+ children: /* @__PURE__ */ jsx("div", {
1473
+ role: "listbox",
1474
+ style: {
1475
+ display: "flex",
1476
+ flexDirection: "column"
1477
+ },
1478
+ children: /* @__PURE__ */ jsx(OptionPlaceContext.Provider, {
1479
+ value: {
1480
+ inField: false,
1481
+ value,
1482
+ choose
1483
+ },
1484
+ children
1485
+ })
1486
+ })
1487
+ })] });
1488
+ };
1489
+ const Option = ({ value, label }) => {
1490
+ const { colors } = useTheme();
1491
+ const place = useContext(OptionPlaceContext);
1492
+ const chosen = value !== void 0 && value === place.value;
1493
+ if (place.inField) return chosen ? /* @__PURE__ */ jsx(Fragment$1, { children: label }) : null;
1494
+ const { choose } = place;
1495
+ return /* @__PURE__ */ jsxs("button", {
1496
+ type: "button",
1497
+ className: "dot-option",
1498
+ role: "option",
1499
+ "aria-selected": chosen,
1500
+ onClick: choose && value !== void 0 ? () => choose(value) : void 0,
1501
+ style: {
1502
+ display: "flex",
1503
+ alignItems: "center",
1504
+ justifyContent: "space-between",
1505
+ padding: "14px 4px",
1506
+ border: "none",
1507
+ borderBottom: `1px solid ${colors.border}`,
1508
+ background: "transparent",
178
1509
  color: colors.text,
179
- outlineColor: colors.primary,
180
1510
  font: "inherit",
181
- fontSize: 16,
182
- opacity: disabled ? DISABLED_OPACITY : 1
183
- }
1511
+ fontSize: FONT_SIZE.body,
1512
+ fontWeight: chosen ? 600 : 400,
1513
+ textAlign: "start",
1514
+ cursor: "pointer"
1515
+ },
1516
+ children: [/* @__PURE__ */ jsx("span", { children: label }), chosen ? /* @__PURE__ */ jsx(IconGraphic, {
1517
+ name: "check",
1518
+ size: ICON_SIZE.medium,
1519
+ color: colors.primary
1520
+ }) : null]
1521
+ });
1522
+ };
1523
+ //#endregion
1524
+ //#region src/components/skeleton.tsx
1525
+ const PULSE = [
1526
+ { opacity: 1 },
1527
+ { opacity: .4 },
1528
+ { opacity: 1 }
1529
+ ];
1530
+ const LINE_HEIGHT = 12;
1531
+ const Skeleton = ({ shape = "text", lines = 1, height }) => {
1532
+ const { colors, radius } = useTheme();
1533
+ const placeholder = useRef(null);
1534
+ useEffect(() => {
1535
+ const pulse = animate(placeholder.current, PULSE, {
1536
+ duration: 1200,
1537
+ iterations: Infinity
1538
+ });
1539
+ return () => pulse?.cancel();
1540
+ }, []);
1541
+ let content;
1542
+ if (shape === "circle") {
1543
+ const size = height ?? 40;
1544
+ content = /* @__PURE__ */ jsx("div", { style: {
1545
+ width: size,
1546
+ height: size,
1547
+ borderRadius: "50%",
1548
+ background: colors.muted
1549
+ } });
1550
+ } else if (shape === "box") content = /* @__PURE__ */ jsx("div", { style: {
1551
+ height: height ?? 80,
1552
+ borderRadius: radius,
1553
+ background: colors.muted
1554
+ } });
1555
+ else {
1556
+ const count = Math.max(1, lines);
1557
+ content = Array.from({ length: count }, (_, index) => {
1558
+ const last = count > 1 && index === count - 1;
1559
+ return /* @__PURE__ */ jsx("div", { style: {
1560
+ width: last ? "60%" : "100%",
1561
+ height: LINE_HEIGHT,
1562
+ borderRadius: LINE_HEIGHT / 2,
1563
+ background: colors.muted
1564
+ } }, index);
1565
+ });
1566
+ }
1567
+ return /* @__PURE__ */ jsx("div", {
1568
+ ref: placeholder,
1569
+ className: "dot-skeleton",
1570
+ "aria-busy": "true",
1571
+ style: {
1572
+ display: "flex",
1573
+ flexDirection: "column",
1574
+ gap: SPACE.medium,
1575
+ width: shape === "circle" ? void 0 : "100%",
1576
+ flexShrink: shape === "circle" ? 0 : 1
1577
+ },
1578
+ children: content
1579
+ });
1580
+ };
1581
+ //#endregion
1582
+ //#region src/components/tabs.tsx
1583
+ const TabsContext = createContext({
1584
+ value: void 0,
1585
+ segmented: false,
1586
+ choose: void 0
1587
+ });
1588
+ const Tabs = ({ value, variant = "underline", onChange, children }) => {
1589
+ const { colors, radius } = useTheme();
1590
+ const segmented = variant === "segmented";
1591
+ const choose = onChange ? (next) => {
1592
+ if (next !== value) onChange(createEvent("change", { value: next }));
1593
+ } : void 0;
1594
+ return /* @__PURE__ */ jsx("div", {
1595
+ className: "dot-tabs",
1596
+ role: "tablist",
1597
+ style: {
1598
+ display: "flex",
1599
+ overflowX: "auto",
1600
+ ...segmented ? {
1601
+ gap: 2,
1602
+ padding: 3,
1603
+ borderRadius: radius,
1604
+ background: colors.muted
1605
+ } : { borderBottom: `1px solid ${colors.border}` }
1606
+ },
1607
+ children: /* @__PURE__ */ jsx(TabsContext.Provider, {
1608
+ value: {
1609
+ value,
1610
+ segmented,
1611
+ choose
1612
+ },
1613
+ children
1614
+ })
1615
+ });
1616
+ };
1617
+ const Tab = ({ value, label, badge }) => {
1618
+ const { colors, radius } = useTheme();
1619
+ const { value: chosen, segmented, choose } = useContext(TabsContext);
1620
+ const selected = value !== void 0 && value === chosen;
1621
+ let color = colors.subtle;
1622
+ if (selected) color = segmented ? colors.text : colors.primary;
1623
+ return /* @__PURE__ */ jsxs("button", {
1624
+ type: "button",
1625
+ role: "tab",
1626
+ "aria-selected": selected,
1627
+ onClick: choose && value !== void 0 ? () => choose(value) : void 0,
1628
+ style: {
1629
+ display: "flex",
1630
+ alignItems: "center",
1631
+ justifyContent: "center",
1632
+ gap: 6,
1633
+ flex: "1 0 auto",
1634
+ minHeight: segmented ? 36 : 44,
1635
+ padding: "0 12px",
1636
+ border: "none",
1637
+ font: "inherit",
1638
+ fontSize: FONT_SIZE.body,
1639
+ fontWeight: selected ? 600 : 500,
1640
+ whiteSpace: "nowrap",
1641
+ color,
1642
+ cursor: "pointer",
1643
+ ...segmented ? {
1644
+ borderRadius: radius - 2,
1645
+ background: selected ? colors.surface : "transparent",
1646
+ boxShadow: selected ? "0 1px 3px rgba(0, 0, 0, 0.15)" : "none"
1647
+ } : {
1648
+ marginBottom: -1,
1649
+ background: "transparent",
1650
+ borderBottom: `2px solid ${selected ? colors.primary : "transparent"}`
1651
+ }
1652
+ },
1653
+ children: [label, badge ? /* @__PURE__ */ jsx(Pill, {
1654
+ background: colors.primary,
1655
+ color: colors.surface,
1656
+ children: badge
1657
+ }) : null]
184
1658
  });
185
1659
  };
186
1660
  //#endregion
@@ -193,11 +1667,265 @@ const VARIANTS = {
193
1667
  fontWeight: 600
194
1668
  }
195
1669
  };
196
- const Text = ({ variant = "body", children }) => /* @__PURE__ */ jsx("span", {
197
- className: "dot-text",
198
- style: VARIANTS[variant],
199
- children
1670
+ const WEIGHTS = {
1671
+ regular: 400,
1672
+ medium: 500,
1673
+ bold: 600
1674
+ };
1675
+ const Text = ({ variant = "body", tone, weight, align, maxLines, children }) => {
1676
+ const { colors } = useTheme();
1677
+ const style = {
1678
+ ...VARIANTS[variant],
1679
+ textAlign: align
1680
+ };
1681
+ if (tone) style.color = toneColor(colors, tone);
1682
+ if (weight) style.fontWeight = WEIGHTS[weight];
1683
+ if (maxLines) Object.assign(style, {
1684
+ display: "-webkit-box",
1685
+ WebkitLineClamp: maxLines,
1686
+ WebkitBoxOrient: "vertical",
1687
+ overflow: "hidden"
1688
+ });
1689
+ return /* @__PURE__ */ jsx("span", {
1690
+ className: "dot-text",
1691
+ style,
1692
+ children
1693
+ });
1694
+ };
1695
+ //#endregion
1696
+ //#region src/components/text-area.tsx
1697
+ const TextArea = ({ value, placeholder, label, hint, error, rows = 3, maxLength, disabled, onChange }) => {
1698
+ const [text, typeText] = useTypedText(value, onChange !== void 0);
1699
+ const box = useFieldBox(error, disabled);
1700
+ return /* @__PURE__ */ jsx(Field, {
1701
+ label,
1702
+ hint,
1703
+ error,
1704
+ children: /* @__PURE__ */ jsx("textarea", {
1705
+ className: "dot-text-area",
1706
+ value: text,
1707
+ placeholder,
1708
+ rows,
1709
+ maxLength,
1710
+ disabled,
1711
+ "aria-invalid": error ? true : void 0,
1712
+ onFocus: box.onFocus,
1713
+ onBlur: box.onBlur,
1714
+ onChange: (event) => {
1715
+ const next = event.currentTarget.value;
1716
+ typeText(next);
1717
+ onChange?.(createEvent("change", { value: next }));
1718
+ },
1719
+ style: {
1720
+ font: "inherit",
1721
+ ...box.style,
1722
+ boxSizing: "border-box",
1723
+ width: "100%",
1724
+ padding: "12px 14px",
1725
+ outline: "none",
1726
+ resize: "none"
1727
+ }
1728
+ })
1729
+ });
1730
+ };
1731
+ //#endregion
1732
+ //#region src/components/toast.tsx
1733
+ const Toast = ({ message, tone = "default", duration = 2500, onHide }) => {
1734
+ const { colors, radius } = useTheme();
1735
+ const [hidden, setHidden] = useState(false);
1736
+ const [previousMessage, setPreviousMessage] = useState(message);
1737
+ if (message !== previousMessage) {
1738
+ setPreviousMessage(message);
1739
+ setHidden(false);
1740
+ }
1741
+ const hide = useEffectEvent(() => {
1742
+ setHidden(true);
1743
+ onHide?.(createEvent("hide"));
1744
+ });
1745
+ useEffect(() => {
1746
+ if (!message) return;
1747
+ const timer = setTimeout(hide, duration);
1748
+ return () => clearTimeout(timer);
1749
+ }, [message, duration]);
1750
+ if (!message || hidden) return null;
1751
+ return /* @__PURE__ */ jsxs("div", {
1752
+ className: "dot-toast",
1753
+ role: "status",
1754
+ style: {
1755
+ position: "fixed",
1756
+ top: SPACE.large,
1757
+ left: "50%",
1758
+ transform: "translateX(-50%)",
1759
+ zIndex: 110,
1760
+ display: "flex",
1761
+ alignItems: "center",
1762
+ gap: SPACE.medium,
1763
+ maxWidth: "calc(100% - 32px)",
1764
+ width: "max-content",
1765
+ padding: "10px 16px",
1766
+ borderRadius: radius,
1767
+ background: colors.primary,
1768
+ color: colors.surface,
1769
+ fontSize: FONT_SIZE.body,
1770
+ boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2)"
1771
+ },
1772
+ children: [tone === "default" ? null : /* @__PURE__ */ jsx(IconGraphic, {
1773
+ name: STATUS_ICONS[tone],
1774
+ size: ICON_SIZE.medium,
1775
+ color: toneColor(colors, tone)
1776
+ }), /* @__PURE__ */ jsx("span", { children: message })]
1777
+ });
1778
+ };
1779
+ //#endregion
1780
+ //#region src/components/toggles.tsx
1781
+ function rowStyle(colors, disabled) {
1782
+ return {
1783
+ display: "flex",
1784
+ alignItems: "center",
1785
+ gap: 12,
1786
+ width: "100%",
1787
+ padding: "6px 0",
1788
+ border: "none",
1789
+ background: "transparent",
1790
+ color: colors.text,
1791
+ font: "inherit",
1792
+ fontSize: FONT_SIZE.body,
1793
+ textAlign: "start",
1794
+ opacity: disabled ? DISABLED_OPACITY : 1,
1795
+ cursor: disabled ? "default" : "pointer"
1796
+ };
1797
+ }
1798
+ function changeTo(checked, onChange) {
1799
+ return onChange ? () => onChange(createEvent("change", { checked })) : void 0;
1800
+ }
1801
+ function ChoiceMark({ round, checked, children }) {
1802
+ const { colors } = useTheme();
1803
+ const filled = checked && !round;
1804
+ return /* @__PURE__ */ jsx("span", {
1805
+ style: {
1806
+ display: "flex",
1807
+ alignItems: "center",
1808
+ justifyContent: "center",
1809
+ flexShrink: 0,
1810
+ width: 22,
1811
+ height: 22,
1812
+ boxSizing: "border-box",
1813
+ borderRadius: round ? "50%" : 6,
1814
+ border: `2px solid ${checked ? colors.primary : colors.subtle}`,
1815
+ background: filled ? colors.primary : "transparent"
1816
+ },
1817
+ children
1818
+ });
1819
+ }
1820
+ const Switch = ({ checked = false, label, disabled, onChange }) => {
1821
+ const { colors } = useTheme();
1822
+ return /* @__PURE__ */ jsxs("button", {
1823
+ type: "button",
1824
+ className: "dot-switch",
1825
+ role: "switch",
1826
+ "aria-checked": checked,
1827
+ disabled,
1828
+ onClick: changeTo(!checked, onChange),
1829
+ style: {
1830
+ ...rowStyle(colors, disabled),
1831
+ justifyContent: "space-between"
1832
+ },
1833
+ children: [label ? /* @__PURE__ */ jsx("span", { children: label }) : null, /* @__PURE__ */ jsx("span", {
1834
+ style: {
1835
+ position: "relative",
1836
+ flexShrink: 0,
1837
+ width: 44,
1838
+ height: 26,
1839
+ borderRadius: 13,
1840
+ background: checked ? colors.primary : colors.subtle,
1841
+ transition: "background 0.2s"
1842
+ },
1843
+ children: /* @__PURE__ */ jsx("span", { style: {
1844
+ position: "absolute",
1845
+ top: 2,
1846
+ left: checked ? 20 : 2,
1847
+ width: 22,
1848
+ height: 22,
1849
+ borderRadius: "50%",
1850
+ background: colors.surface,
1851
+ transition: "left 0.2s"
1852
+ } })
1853
+ })]
1854
+ });
1855
+ };
1856
+ const Checkbox = ({ checked = false, label, disabled, onChange }) => {
1857
+ const { colors } = useTheme();
1858
+ return /* @__PURE__ */ jsxs("button", {
1859
+ type: "button",
1860
+ className: "dot-checkbox",
1861
+ role: "checkbox",
1862
+ "aria-checked": checked,
1863
+ disabled,
1864
+ onClick: changeTo(!checked, onChange),
1865
+ style: rowStyle(colors, disabled),
1866
+ children: [/* @__PURE__ */ jsx(ChoiceMark, {
1867
+ round: false,
1868
+ checked,
1869
+ children: checked ? /* @__PURE__ */ jsx(IconGraphic, {
1870
+ name: "check",
1871
+ size: ICON_SIZE.small,
1872
+ color: colors.surface
1873
+ }) : null
1874
+ }), label ? /* @__PURE__ */ jsx("span", { children: label }) : null]
1875
+ });
1876
+ };
1877
+ const RadioGroupContext = createContext({
1878
+ value: void 0,
1879
+ disabled: true,
1880
+ choose: void 0
200
1881
  });
1882
+ const RadioGroup = ({ value, label, disabled = false, onChange, children }) => {
1883
+ const choose = onChange ? (next) => onChange(createEvent("change", { value: next })) : void 0;
1884
+ return /* @__PURE__ */ jsxs("div", {
1885
+ className: "dot-radio-group",
1886
+ role: "radiogroup",
1887
+ "aria-label": label,
1888
+ style: {
1889
+ display: "flex",
1890
+ flexDirection: "column",
1891
+ gap: SPACE.small
1892
+ },
1893
+ children: [label ? /* @__PURE__ */ jsx(FieldLabel, { children: label }) : null, /* @__PURE__ */ jsx(RadioGroupContext.Provider, {
1894
+ value: {
1895
+ value,
1896
+ disabled,
1897
+ choose
1898
+ },
1899
+ children
1900
+ })]
1901
+ });
1902
+ };
1903
+ const Radio = ({ value, label, disabled }) => {
1904
+ const { colors } = useTheme();
1905
+ const group = useContext(RadioGroupContext);
1906
+ const selected = value !== void 0 && value === group.value;
1907
+ const inactive = disabled || group.disabled;
1908
+ const { choose } = group;
1909
+ return /* @__PURE__ */ jsxs("button", {
1910
+ type: "button",
1911
+ className: "dot-radio",
1912
+ role: "radio",
1913
+ "aria-checked": selected,
1914
+ disabled: inactive,
1915
+ onClick: choose && value !== void 0 ? () => choose(value) : void 0,
1916
+ style: rowStyle(colors, inactive),
1917
+ children: [/* @__PURE__ */ jsx(ChoiceMark, {
1918
+ round: true,
1919
+ checked: selected,
1920
+ children: selected ? /* @__PURE__ */ jsx("span", { style: {
1921
+ width: 10,
1922
+ height: 10,
1923
+ borderRadius: "50%",
1924
+ background: colors.primary
1925
+ } }) : null
1926
+ }), label ? /* @__PURE__ */ jsx("span", { children: label }) : null]
1927
+ });
1928
+ };
201
1929
  //#endregion
202
1930
  //#region src/components/view.tsx
203
1931
  const ALIGN = {
@@ -206,18 +1934,32 @@ const ALIGN = {
206
1934
  center: "center",
207
1935
  end: "flex-end"
208
1936
  };
209
- const View = ({ direction = "column", gap = "medium", align = "stretch", variant = "plain", onTap, children }) => {
1937
+ const JUSTIFY = {
1938
+ start: "flex-start",
1939
+ center: "center",
1940
+ end: "flex-end",
1941
+ between: "space-between"
1942
+ };
1943
+ const View = ({ direction = "column", gap = "medium", align = "stretch", justify = "start", wrap = false, grow = false, padding, paddingTop, paddingStart, paddingEnd, paddingBottom, variant = "plain", onTap, children }) => {
210
1944
  const { colors, radius } = useTheme();
211
1945
  const card = variant === "card";
1946
+ const allSides = padding ?? (card ? "large" : "none");
212
1947
  return /* @__PURE__ */ jsx("div", {
213
1948
  className: "dot-view",
214
1949
  onClick: onTap ? () => onTap(createEvent("tap")) : void 0,
215
1950
  style: {
216
1951
  display: "flex",
217
1952
  flexDirection: direction,
218
- gap: GAP[gap],
1953
+ gap: SPACE[gap],
219
1954
  alignItems: ALIGN[align],
220
- padding: card ? 16 : void 0,
1955
+ justifyContent: JUSTIFY[justify],
1956
+ flexWrap: wrap ? "wrap" : void 0,
1957
+ flexGrow: grow ? 1 : void 0,
1958
+ minWidth: grow ? 0 : void 0,
1959
+ paddingTop: SPACE[paddingTop ?? allSides],
1960
+ paddingInlineStart: SPACE[paddingStart ?? allSides],
1961
+ paddingInlineEnd: SPACE[paddingEnd ?? allSides],
1962
+ paddingBottom: SPACE[paddingBottom ?? allSides],
221
1963
  borderRadius: card ? radius * CARD_RADIUS_SCALE : void 0,
222
1964
  background: card ? colors.surface : void 0,
223
1965
  border: card ? `1px solid ${colors.border}` : void 0,
@@ -229,10 +1971,39 @@ const View = ({ direction = "column", gap = "medium", align = "stretch", variant
229
1971
  //#endregion
230
1972
  //#region src/components/index.ts
231
1973
  const builtins = {
1974
+ AmountInput,
1975
+ Badge,
232
1976
  Button,
233
- Image,
1977
+ Carousel,
1978
+ Checkbox,
1979
+ Chip,
1980
+ CodeInput,
1981
+ Divider,
1982
+ EmptyState,
1983
+ Footer,
1984
+ Grid,
1985
+ GridItem,
1986
+ Icon,
1987
+ Image: Image$1,
234
1988
  Input,
1989
+ List: List$1,
1990
+ ListItem,
1991
+ Notice,
1992
+ Option,
1993
+ ProgressBar,
1994
+ QRCode,
1995
+ Radio,
1996
+ RadioGroup,
1997
+ Select,
1998
+ Sheet,
1999
+ Skeleton,
2000
+ Spinner,
2001
+ Switch,
2002
+ Tab,
2003
+ Tabs,
235
2004
  Text,
2005
+ TextArea,
2006
+ Toast,
236
2007
  View
237
2008
  };
238
2009
  //#endregion
@@ -246,11 +2017,11 @@ function PageRoot({ page, components }) {
246
2017
  return createElement(component, props, ...children);
247
2018
  },
248
2019
  text: toText,
249
- on: (handler) => page.on(handler),
2020
+ on: (handler, data) => page.on(handler, data),
250
2021
  each: (items, render, key) => createElement(Fragment, null, toList(items).map((item, index) => createElement(Fragment, { key: key ? String(key(item, index)) : index }, ...render(item, index))))
251
2022
  }), [page, components]);
252
2023
  if (!state) return null;
253
- return createElement(Fragment, null, ...page.viewRenderer(state, context));
2024
+ return page.viewRenderer(state, context);
254
2025
  }
255
2026
  //#endregion
256
2027
  //#region src/root.tsx
@@ -279,11 +2050,8 @@ function DefaultSurface({ children }) {
279
2050
  className: "dot-surface",
280
2051
  style: {
281
2052
  minHeight: "100vh",
282
- boxSizing: "border-box",
283
- padding: 16,
284
2053
  display: "flex",
285
2054
  flexDirection: "column",
286
- gap: 12,
287
2055
  background: colors.background,
288
2056
  color: colors.text,
289
2057
  fontFamily: FONT_FAMILY