@monimesl/dot-renderer-react 0.1.0-alpha.9 → 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +24 -20
  2. package/dist/color.js +25 -36
  3. package/dist/components/amount-input.js +26 -21
  4. package/dist/components/avatar.d.ts +6 -0
  5. package/dist/components/avatar.js +68 -0
  6. package/dist/components/box.js +56 -0
  7. package/dist/components/carousel.js +70 -66
  8. package/dist/components/code-input.js +6 -7
  9. package/dist/components/disclosure.d.ts +6 -0
  10. package/dist/components/disclosure.js +76 -0
  11. package/dist/components/empty-state.js +2 -4
  12. package/dist/components/feedback.js +39 -23
  13. package/dist/components/footer.js +3 -2
  14. package/dist/components/grid.js +4 -5
  15. package/dist/components/https.js +11 -0
  16. package/dist/components/icon-artwork.js +139 -70
  17. package/dist/components/icon-button.js +28 -0
  18. package/dist/components/icon-svg.js +27 -0
  19. package/dist/components/icon.js +5 -20
  20. package/dist/components/index.d.ts +9 -1
  21. package/dist/components/index.js +22 -2
  22. package/dist/components/input.js +24 -2
  23. package/dist/components/link.js +1 -8
  24. package/dist/components/list.js +17 -17
  25. package/dist/components/numbers.js +9 -0
  26. package/dist/components/option.js +4 -3
  27. package/dist/components/padding.js +18 -7
  28. package/dist/components/prose-context.js +5 -0
  29. package/dist/components/prose.d.ts +9 -0
  30. package/dist/components/prose.js +67 -0
  31. package/dist/components/rich-text.d.ts +6 -0
  32. package/dist/components/rich-text.js +73 -0
  33. package/dist/components/scroll.js +27 -25
  34. package/dist/components/select.js +4 -3
  35. package/dist/components/sheet.js +9 -8
  36. package/dist/components/slider.d.ts +6 -0
  37. package/dist/components/slider.js +57 -0
  38. package/dist/components/stack.d.ts +6 -0
  39. package/dist/components/stack.js +25 -0
  40. package/dist/components/stepper.d.ts +6 -0
  41. package/dist/components/stepper.js +92 -0
  42. package/dist/components/steps.d.ts +7 -0
  43. package/dist/components/steps.js +110 -0
  44. package/dist/components/tabs.js +23 -19
  45. package/dist/components/text.js +74 -13
  46. package/dist/components/toast.js +4 -4
  47. package/dist/components/toggles.js +6 -7
  48. package/dist/components/tokens.js +14 -2
  49. package/dist/components/tones.js +13 -7
  50. package/dist/components/typed-text.js +2 -2
  51. package/dist/components/view.js +19 -9
  52. package/dist/index.d.ts +9 -1
  53. package/dist/index.js +10 -2
  54. package/dist/markdown.js +255 -0
  55. package/dist/page-root.js +1 -1
  56. package/dist/pull-to-refresh.js +120 -0
  57. package/dist/root.js +31 -11
  58. package/dist/styles.css +1 -95
  59. package/dist/theme.d.ts +3 -2
  60. package/dist/theme.js +60 -15
  61. package/package.json +8 -7
@@ -1,5 +1,6 @@
1
1
  import { useTheme } from "../theme.js";
2
2
  import { SPACE } from "./tokens.js";
3
+ import { clamp } from "./numbers.js";
3
4
  import { paddingStyle } from "./padding.js";
4
5
  import { createEvent } from "@monimesl/dot-core";
5
6
  import { Children, Fragment, isValidElement, useEffect, useEffectEvent, useRef, useState } from "react";
@@ -16,40 +17,41 @@ const Carousel$1 = ({ autoplay = false, interval = 4e3, loop = false, indicator
16
17
  const slides = slidesOf(children);
17
18
  const count = slides.length;
18
19
  const looping = loop && count > 1;
19
- const first = looping ? 1 : 0;
20
- const last = looping ? count + 1 : count - 1;
21
- const [position, setPosition] = useState(() => first + slideIndex(index ?? 0, count));
20
+ const firstPosition = looping ? 1 : 0;
21
+ const lastPosition = looping ? count + 1 : count - 1;
22
+ const [position, setPosition] = useState(() => firstPosition + slideIndex(index ?? 0, count));
22
23
  const [animated, setAnimated] = useState(true);
23
- const [drag, setDrag] = useState(0);
24
+ const [dragOffset, setDragOffset] = useState(0);
24
25
  const [dragging, setDragging] = useState(false);
25
- const [width, setWidth] = useState(0);
26
+ const [viewportWidth, setViewportWidth] = useState(0);
26
27
  const [previousIndex, setPreviousIndex] = useState(index);
27
- const viewport = useRef(null);
28
- const gesture = useRef(null);
29
- const swiped = useRef(false);
28
+ const viewportRef = useRef(null);
29
+ const gestureRef = useRef(null);
30
+ const ignoreNextClick = useRef(false);
30
31
  if (index !== previousIndex) {
31
32
  setPreviousIndex(index);
32
33
  if (index !== void 0) {
33
34
  setAnimated(true);
34
- setPosition(first + slideIndex(index, count));
35
+ setPosition(firstPosition + slideIndex(index, count));
35
36
  }
36
37
  }
37
- const shown = clamp(position, 0, last);
38
- const current = count === 0 ? 0 : wrap(shown - first, count);
39
- const moveTo = (target) => {
40
- const next = clamp(target, 0, last);
38
+ const shownPosition = clamp(position, 0, lastPosition);
39
+ const currentIndex = count === 0 ? 0 : wrapIndex(shownPosition - firstPosition, count);
40
+ const moveTo = (targetPosition) => {
41
+ const nextPosition = clamp(targetPosition, 0, lastPosition);
41
42
  setAnimated(true);
42
- setPosition(next);
43
- const nextIndex = wrap(next - first, count);
44
- if (nextIndex !== current) onChange?.(createEvent("change", { index: nextIndex }));
43
+ setPosition(nextPosition);
44
+ const nextIndex = wrapIndex(nextPosition - firstPosition, count);
45
+ if (nextIndex !== currentIndex) onChange?.(createEvent("change", { index: nextIndex }));
45
46
  };
46
- const step = (direction) => {
47
- const beyondEnd = current + direction < 0 || current + direction >= count;
48
- moveTo(!looping && beyondEnd ? shown : shown + direction);
47
+ const stepBy = (direction) => {
48
+ const beyondEnd = currentIndex + direction < 0 || currentIndex + direction >= count;
49
+ if (!looping && beyondEnd) moveTo(shownPosition);
50
+ else moveTo(shownPosition + direction);
49
51
  };
50
52
  const advance = useEffectEvent(() => {
51
- if (!looping && current === count - 1) moveTo(first);
52
- else moveTo(shown + 1);
53
+ if (!looping && currentIndex === count - 1) moveTo(firstPosition);
54
+ else moveTo(shownPosition + 1);
53
55
  });
54
56
  useEffect(() => {
55
57
  if (!autoplay || count < 2 || dragging) return;
@@ -60,10 +62,11 @@ const Carousel$1 = ({ autoplay = false, interval = 4e3, loop = false, indicator
60
62
  count,
61
63
  dragging,
62
64
  interval,
63
- current
65
+ currentIndex
64
66
  ]);
65
67
  useEffect(() => {
66
- if (!looping || position !== 0 && position !== count + 1) return;
68
+ const onCopy = position === 0 || position === count + 1;
69
+ if (!looping || !onCopy) return;
67
70
  const timer = setTimeout(() => {
68
71
  setAnimated(false);
69
72
  setPosition(position === 0 ? count : 1);
@@ -75,61 +78,65 @@ const Carousel$1 = ({ autoplay = false, interval = 4e3, loop = false, indicator
75
78
  count
76
79
  ]);
77
80
  const gestureOf = (event) => {
78
- const touch = gesture.current;
79
- return touch?.pointerId === event.pointerId ? touch : null;
81
+ const inProgress = gestureRef.current;
82
+ return inProgress?.pointerId === event.pointerId ? inProgress : null;
80
83
  };
81
84
  const endGesture = () => {
82
- gesture.current = null;
85
+ gestureRef.current = null;
83
86
  setDragging(false);
84
- setDrag(0);
87
+ setDragOffset(0);
85
88
  setAnimated(true);
86
89
  };
87
90
  const onPointerDown = (event) => {
88
91
  if (count < 2 || !event.isPrimary) return;
89
- gesture.current = {
92
+ gestureRef.current = {
90
93
  pointerId: event.pointerId,
91
94
  startX: event.clientX,
92
95
  startY: event.clientY,
93
96
  startTime: event.timeStamp,
94
97
  swiping: false
95
98
  };
96
- swiped.current = false;
99
+ ignoreNextClick.current = false;
97
100
  setDragging(true);
98
- setWidth(viewport.current?.clientWidth ?? 0);
101
+ setViewportWidth(viewportRef.current?.clientWidth ?? 0);
99
102
  };
100
103
  const onPointerMove = (event) => {
101
- const touch = gestureOf(event);
102
- if (!touch) return;
103
- const dx = event.clientX - touch.startX;
104
- const dy = event.clientY - touch.startY;
105
- if (!touch.swiping) {
106
- if (Math.abs(dx) < TAP_SLOP && Math.abs(dy) < TAP_SLOP) return;
107
- if (Math.abs(dy) > Math.abs(dx)) {
104
+ const swipe = gestureOf(event);
105
+ if (!swipe) return;
106
+ const movedX = event.clientX - swipe.startX;
107
+ const movedY = event.clientY - swipe.startY;
108
+ if (!swipe.swiping) {
109
+ if (Math.abs(movedX) < TAP_SLOP && Math.abs(movedY) < TAP_SLOP) return;
110
+ if (Math.abs(movedY) > Math.abs(movedX)) {
108
111
  endGesture();
109
112
  return;
110
113
  }
111
- touch.swiping = true;
112
- swiped.current = true;
114
+ swipe.swiping = true;
115
+ ignoreNextClick.current = true;
113
116
  event.currentTarget.setPointerCapture?.(event.pointerId);
114
117
  setAnimated(false);
115
118
  }
116
- const pastEnd = !looping && (current === 0 && dx > 0 || current === count - 1 && dx < 0);
117
- setDrag(pastEnd ? dx * EDGE_RESISTANCE : dx);
119
+ const beforeFirst = currentIndex === 0 && movedX > 0;
120
+ const afterLast = currentIndex === count - 1 && movedX < 0;
121
+ if (!looping && (beforeFirst || afterLast)) setDragOffset(movedX * EDGE_RESISTANCE);
122
+ else setDragOffset(movedX);
118
123
  };
119
124
  const onPointerUp = (event) => {
120
- const touch = gestureOf(event);
121
- if (!touch) return;
125
+ const swipe = gestureOf(event);
126
+ if (!swipe) return;
122
127
  endGesture();
123
- if (!touch.swiping) return;
124
- const dx = event.clientX - touch.startX;
125
- const speed = Math.abs(dx) / Math.max(1, event.timeStamp - touch.startTime);
126
- if (Math.abs(dx) > width * SWIPE_DISTANCE || speed > SWIPE_SPEED) step(dx < 0 ? 1 : -1);
127
- else moveTo(shown);
128
+ if (!swipe.swiping) return;
129
+ const movedX = event.clientX - swipe.startX;
130
+ const speed = Math.abs(movedX) / Math.max(1, event.timeStamp - swipe.startTime);
131
+ if (Math.abs(movedX) > viewportWidth * SWIPE_DISTANCE || speed > SWIPE_SPEED) stepBy(movedX < 0 ? 1 : -1);
132
+ else moveTo(shownPosition);
128
133
  };
129
134
  const gapWidth = SPACE[gap];
130
135
  const transition = animated ? `transform ${SLIDE_DURATION}ms ${SLIDE_EASING}` : "none";
131
- const swipedSlides = width > 0 ? drag / (width + gapWidth) : 0;
132
- const track = looping ? [
136
+ let draggedSlides = 0;
137
+ if (viewportWidth > 0) draggedSlides = dragOffset / (viewportWidth + gapWidth);
138
+ let track = slides;
139
+ if (looping) track = [
133
140
  {
134
141
  ...slides[count - 1],
135
142
  key: "copy-of-last"
@@ -139,7 +146,7 @@ const Carousel$1 = ({ autoplay = false, interval = 4e3, loop = false, indicator
139
146
  ...slides[0],
140
147
  key: "copy-of-first"
141
148
  }
142
- ] : slides;
149
+ ];
143
150
  return /* @__PURE__ */ jsxs("div", {
144
151
  className: "dot-carousel",
145
152
  role: "region",
@@ -152,15 +159,15 @@ const Carousel$1 = ({ autoplay = false, interval = 4e3, loop = false, indicator
152
159
  ...paddingStyle(padding)
153
160
  },
154
161
  children: [/* @__PURE__ */ jsx("div", {
155
- ref: viewport,
162
+ ref: viewportRef,
156
163
  onPointerDown,
157
164
  onPointerMove,
158
165
  onPointerUp,
159
166
  onPointerCancel: endGesture,
160
167
  onDragStart: (event) => event.preventDefault(),
161
168
  onClickCapture: (event) => {
162
- if (swiped.current) {
163
- swiped.current = false;
169
+ if (ignoreNextClick.current) {
170
+ ignoreNextClick.current = false;
164
171
  event.stopPropagation();
165
172
  }
166
173
  },
@@ -173,17 +180,17 @@ const Carousel$1 = ({ autoplay = false, interval = 4e3, loop = false, indicator
173
180
  style: {
174
181
  display: "flex",
175
182
  gap: gapWidth,
176
- transform: `translateX(calc(${-shown} * (100% + ${gapWidth}px) + ${drag}px))`,
183
+ transform: `translateX(calc(${-shownPosition} * (100% + ${gapWidth}px) + ${dragOffset}px))`,
177
184
  transition
178
185
  },
179
186
  children: track.map((slide, trackPosition) => {
180
- const visible = trackPosition === shown;
181
- const distance = Math.abs(trackPosition - shown + swipedSlides);
187
+ const visible = trackPosition === shownPosition;
188
+ const distance = Math.abs(trackPosition - shownPosition + draggedSlides);
182
189
  const scale = 1 - Math.min(distance, 1) * .15000000000000002;
183
190
  return /* @__PURE__ */ jsx("div", {
184
191
  role: "group",
185
192
  "aria-roledescription": "slide",
186
- "aria-label": `${wrap(trackPosition - first, count) + 1} of ${count}`,
193
+ "aria-label": `${wrapIndex(trackPosition - firstPosition, count) + 1} of ${count}`,
187
194
  "aria-hidden": !visible,
188
195
  inert: !visible,
189
196
  style: {
@@ -202,13 +209,13 @@ const Carousel$1 = ({ autoplay = false, interval = 4e3, loop = false, indicator
202
209
  display: "flex",
203
210
  justifyContent: "center"
204
211
  },
205
- children: slides.map((slide, slideIndex) => {
206
- const active = slideIndex === current;
212
+ children: slides.map((slide, dotIndex) => {
213
+ const active = dotIndex === currentIndex;
207
214
  return /* @__PURE__ */ jsx("button", {
208
215
  type: "button",
209
- "aria-label": `Slide ${slideIndex + 1} of ${count}`,
216
+ "aria-label": `Slide ${dotIndex + 1} of ${count}`,
210
217
  "aria-current": active,
211
- onClick: () => moveTo(first + slideIndex),
218
+ onClick: () => moveTo(firstPosition + dotIndex),
212
219
  style: {
213
220
  padding: "6px 3px",
214
221
  border: "none",
@@ -241,13 +248,10 @@ function slidesOf(children, keyPrefix = "") {
241
248
  }
242
249
  return slides;
243
250
  }
244
- function clamp(value, min, max) {
245
- return Math.max(min, Math.min(value, max));
246
- }
247
251
  function slideIndex(index, count) {
248
252
  return clamp(Math.round(index), 0, count - 1);
249
253
  }
250
- function wrap(value, count) {
254
+ function wrapIndex(value, count) {
251
255
  return (value % count + count) % count;
252
256
  }
253
257
  //#endregion
@@ -1,8 +1,9 @@
1
1
  import { useTheme } from "../theme.js";
2
- import { DISABLED_OPACITY, SPACE, WEIGHTS } from "./tokens.js";
2
+ import { CENTERED, DISABLED_OPACITY, SPACE, WEIGHTS } from "./tokens.js";
3
3
  import { Field } from "./field.js";
4
4
  import { fieldBorderColor, useFieldNote } from "./field-box.js";
5
5
  import { useTypedText } from "./typed-text.js";
6
+ import { wholeCount } from "./numbers.js";
6
7
  import { createEvent } from "@monimesl/dot-core";
7
8
  import { useState } from "react";
8
9
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -12,17 +13,15 @@ const CodeInput$1 = ({ value, length = 6, label, hint, error, secure, disabled,
12
13
  const [text, typeText] = useTypedText(value, onChange !== void 0);
13
14
  const [focused, setFocused] = useState(false);
14
15
  const { noteId, controlAttributes } = useFieldNote(hint, error);
15
- const count = Math.max(1, Math.round(length));
16
+ const count = wholeCount(length);
16
17
  const digits = text.slice(0, count);
17
- const next = Math.min(digits.length, count - 1);
18
+ const nextBox = Math.min(digits.length, count - 1);
18
19
  const boxes = Array.from({ length: count }, (_, index) => {
19
20
  const digit = digits[index];
20
- const borderColor = fieldBorderColor(colors, error, focused && index === next);
21
+ const borderColor = fieldBorderColor(colors, error, focused && index === nextBox);
21
22
  return /* @__PURE__ */ jsx("span", {
22
23
  style: {
23
- display: "flex",
24
- alignItems: "center",
25
- justifyContent: "center",
24
+ ...CENTERED,
26
25
  flex: "1 1 0",
27
26
  maxWidth: 52,
28
27
  height: 52,
@@ -0,0 +1,6 @@
1
+ import { ReactComponent } from "../types.js";
2
+ import { Disclosure } from "@monimesl/dot-core";
3
+ //#region src/components/disclosure.d.ts
4
+ declare const Disclosure$1: ReactComponent<typeof Disclosure>;
5
+ //#endregion
6
+ export { Disclosure$1 as Disclosure };
@@ -0,0 +1,76 @@
1
+ import { useTheme } from "../theme.js";
2
+ import { FONT_SIZE, ICON_SIZE, SPACE, WEIGHTS } from "./tokens.js";
3
+ import { CHEVRON_DOWN_ARTWORK } from "./icon-artwork.js";
4
+ import { IconSvg } from "./icon-svg.js";
5
+ import { createEvent } from "@monimesl/dot-core";
6
+ import { useState } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ //#region src/components/disclosure.tsx
9
+ const Disclosure$1 = ({ title, subtitle, open: startsOpen = false, children, onToggle }) => {
10
+ const { colors } = useTheme();
11
+ const [open, setOpen] = useState(startsOpen);
12
+ return /* @__PURE__ */ jsxs("details", {
13
+ className: "dot-disclosure",
14
+ open,
15
+ onToggle: (event) => {
16
+ const next = event.currentTarget.open;
17
+ if (next !== open) {
18
+ setOpen(next);
19
+ onToggle?.(createEvent("toggle", { open: next }));
20
+ }
21
+ },
22
+ style: { color: colors.text },
23
+ children: [/* @__PURE__ */ jsxs("summary", {
24
+ style: {
25
+ display: "flex",
26
+ alignItems: "center",
27
+ gap: SPACE.medium,
28
+ padding: "12px 0",
29
+ cursor: "pointer"
30
+ },
31
+ children: [/* @__PURE__ */ jsxs("span", {
32
+ style: {
33
+ display: "flex",
34
+ flexDirection: "column",
35
+ gap: 2,
36
+ flex: 1,
37
+ minWidth: 0
38
+ },
39
+ children: [/* @__PURE__ */ jsx("span", {
40
+ style: {
41
+ fontSize: FONT_SIZE.body,
42
+ fontWeight: WEIGHTS.medium
43
+ },
44
+ children: title
45
+ }), subtitle ? /* @__PURE__ */ jsx("span", {
46
+ style: {
47
+ fontSize: FONT_SIZE.caption,
48
+ color: colors.subtle
49
+ },
50
+ children: subtitle
51
+ }) : null]
52
+ }), /* @__PURE__ */ jsx("span", {
53
+ style: {
54
+ display: "flex",
55
+ transform: open ? "rotate(180deg)" : "none",
56
+ transition: "transform 0.2s"
57
+ },
58
+ children: /* @__PURE__ */ jsx(IconSvg, {
59
+ artwork: CHEVRON_DOWN_ARTWORK,
60
+ size: ICON_SIZE.medium,
61
+ color: colors.subtle
62
+ })
63
+ })]
64
+ }), /* @__PURE__ */ jsx("div", {
65
+ style: {
66
+ display: "flex",
67
+ flexDirection: "column",
68
+ gap: SPACE.medium,
69
+ paddingBottom: 12
70
+ },
71
+ children
72
+ })]
73
+ });
74
+ };
75
+ //#endregion
76
+ export { Disclosure$1 as Disclosure };
@@ -1,5 +1,5 @@
1
1
  import { useTheme } from "../theme.js";
2
- import { FONT_SIZE, ICON_SIZE, SPACE, WEIGHTS } from "./tokens.js";
2
+ import { CENTERED, FONT_SIZE, ICON_SIZE, SPACE, WEIGHTS } from "./tokens.js";
3
3
  import { insets, paddingStyle } from "./padding.js";
4
4
  import { IconGraphic } from "./icon.js";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -20,9 +20,7 @@ const EmptyState = ({ icon, title, text, children, ...padding }) => {
20
20
  children: [
21
21
  icon ? /* @__PURE__ */ jsx("span", {
22
22
  style: {
23
- display: "flex",
24
- alignItems: "center",
25
- justifyContent: "center",
23
+ ...CENTERED,
26
24
  width: 64,
27
25
  height: 64,
28
26
  borderRadius: "50%",
@@ -1,13 +1,17 @@
1
1
  import { useTheme } from "../theme.js";
2
2
  import { FONT_SIZE, ICON_SIZE, SPACE, WEIGHTS } from "./tokens.js";
3
+ import { CLOSE_ARTWORK } from "./icon-artwork.js";
4
+ import { IconSvg } from "./icon-svg.js";
5
+ import { clamp } from "./numbers.js";
3
6
  import { insets, paddingStyle } from "./padding.js";
4
- import { STATUS_ICONS, toneBackground, toneColor } from "./tones.js";
5
- import { IconGraphic } from "./icon.js";
7
+ import { STATUS_ARTWORK, toneBackground, toneColor } from "./tones.js";
8
+ import { IconButton } from "./icon-button.js";
9
+ import { createEvent } from "@monimesl/dot-core";
6
10
  import { jsx, jsxs } from "react/jsx-runtime";
7
11
  //#region src/components/feedback.tsx
8
- const ProgressBar = ({ value = 0 }) => {
12
+ const ProgressBar$1 = ({ value = 0 }) => {
9
13
  const { colors } = useTheme();
10
- const percent = Math.min(100, Math.max(0, value));
14
+ const percent = clamp(value, 0, 100);
11
15
  return /* @__PURE__ */ jsx("div", {
12
16
  className: "dot-progress-bar",
13
17
  role: "progressbar",
@@ -29,7 +33,7 @@ const ProgressBar = ({ value = 0 }) => {
29
33
  } })
30
34
  });
31
35
  };
32
- const Notice = ({ tone = "default", title, children, ...padding }) => {
36
+ const Notice$1 = ({ tone = "default", title, children, onClose, ...padding }) => {
33
37
  const { colors, radius } = useTheme();
34
38
  return /* @__PURE__ */ jsxs("div", {
35
39
  className: "dot-notice",
@@ -44,25 +48,37 @@ const Notice = ({ tone = "default", title, children, ...padding }) => {
44
48
  fontSize: FONT_SIZE.body,
45
49
  lineHeight: "normal"
46
50
  },
47
- children: [/* @__PURE__ */ jsx(IconGraphic, {
48
- name: STATUS_ICONS[tone],
49
- size: ICON_SIZE.medium,
50
- color: toneColor(colors, tone)
51
- }), /* @__PURE__ */ jsxs("div", {
52
- style: {
53
- display: "flex",
54
- flexDirection: "column",
55
- gap: 2,
56
- minWidth: 0
57
- },
58
- children: [title ? /* @__PURE__ */ jsx("span", {
59
- style: { fontWeight: WEIGHTS.bold },
60
- children: title
61
- }) : null, children ? /* @__PURE__ */ jsx("span", { children }) : null]
62
- })]
51
+ children: [
52
+ /* @__PURE__ */ jsx(IconSvg, {
53
+ artwork: STATUS_ARTWORK[tone],
54
+ size: ICON_SIZE.medium,
55
+ color: toneColor(colors, tone)
56
+ }),
57
+ /* @__PURE__ */ jsxs("div", {
58
+ style: {
59
+ display: "flex",
60
+ flexDirection: "column",
61
+ gap: 2,
62
+ minWidth: 0
63
+ },
64
+ children: [title ? /* @__PURE__ */ jsx("span", {
65
+ style: { fontWeight: WEIGHTS.bold },
66
+ children: title
67
+ }) : null, children ? /* @__PURE__ */ jsx("span", { children }) : null]
68
+ }),
69
+ onClose ? /* @__PURE__ */ jsx(IconButton, {
70
+ artwork: CLOSE_ARTWORK,
71
+ label: "Close",
72
+ onPress: () => onClose(createEvent("close")),
73
+ style: {
74
+ marginLeft: "auto",
75
+ alignSelf: "flex-start"
76
+ }
77
+ }) : null
78
+ ]
63
79
  });
64
80
  };
65
- const Badge = ({ tone = "default", children }) => {
81
+ const Badge$1 = ({ tone = "default", children }) => {
66
82
  const { colors } = useTheme();
67
83
  return /* @__PURE__ */ jsx("span", {
68
84
  className: "dot-badge",
@@ -82,4 +98,4 @@ const Badge = ({ tone = "default", children }) => {
82
98
  });
83
99
  };
84
100
  //#endregion
85
- export { Badge, Notice, ProgressBar };
101
+ export { Badge$1 as Badge, Notice$1 as Notice, ProgressBar$1 as ProgressBar };
@@ -1,6 +1,6 @@
1
1
  import { useTheme } from "../theme.js";
2
2
  import { SPACE } from "./tokens.js";
3
- import { insets, paddingStyle } from "./padding.js";
3
+ import { HOME_INDICATOR, insets, pinnedPaddingStyle } from "./padding.js";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  //#region src/components/footer.tsx
6
6
  const Footer = ({ children, ...padding }) => {
@@ -14,7 +14,8 @@ const Footer = ({ children, ...padding }) => {
14
14
  display: "flex",
15
15
  flexDirection: "column",
16
16
  gap: SPACE.medium,
17
- ...paddingStyle(padding, insets(SPACE.medium, 0)),
17
+ ...pinnedPaddingStyle(padding, insets(SPACE.medium, 0)),
18
+ marginBottom: `calc(-1 * ${HOME_INDICATOR})`,
18
19
  background: colors.background
19
20
  },
20
21
  children
@@ -1,5 +1,6 @@
1
1
  import { useTheme } from "../theme.js";
2
- import { CARD_RADIUS_SCALE, FONT_SIZE, PILL_STYLE, SPACE } from "./tokens.js";
2
+ import { CARD_RADIUS_SCALE, CENTERED, FONT_SIZE, PILL_STYLE, SPACE } from "./tokens.js";
3
+ import { wholeCount } from "./numbers.js";
3
4
  import { paddingStyle } from "./padding.js";
4
5
  import { IconGraphic } from "./icon.js";
5
6
  import { pressable } from "./pressable.js";
@@ -11,7 +12,7 @@ const Grid$1 = ({ columns = 4, gap = "medium", children, ...padding }) => /* @__
11
12
  className: "dot-grid",
12
13
  style: {
13
14
  display: "grid",
14
- gridTemplateColumns: `repeat(${Math.max(1, Math.round(columns))}, minmax(0, 1fr))`,
15
+ gridTemplateColumns: `repeat(${wholeCount(columns)}, minmax(0, 1fr))`,
15
16
  gap: SPACE[gap],
16
17
  ...paddingStyle(padding)
17
18
  },
@@ -54,9 +55,7 @@ const GridItem$1 = ({ label, icon, image, badge, onTap }) => {
54
55
  children: [/* @__PURE__ */ jsxs("span", {
55
56
  style: {
56
57
  position: "relative",
57
- display: "flex",
58
- alignItems: "center",
59
- justifyContent: "center",
58
+ ...CENTERED,
60
59
  flexShrink: 0,
61
60
  width: TILE_SIZE,
62
61
  height: TILE_SIZE,
@@ -0,0 +1,11 @@
1
+ //#region src/components/https.ts
2
+ function isHttps(href) {
3
+ if (!href) return false;
4
+ try {
5
+ return new URL(href).protocol === "https:";
6
+ } catch {
7
+ return false;
8
+ }
9
+ }
10
+ //#endregion
11
+ export { isHttps };