@monimesl/dot-renderer-react 1.0.0-beta.8 → 1.0.0-beta.9

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.
@@ -59,4 +59,4 @@ function textToneColor(tone, theme) {
59
59
  return customColor(tone, theme);
60
60
  }
61
61
  //#endregion
62
- export { boxStyle, customColor, textToneColor };
62
+ export { backgroundColor, boxStyle, customColor, textToneColor };
@@ -25,7 +25,7 @@ const SIZES = {
25
25
  const Button$1 = ({ variant = DEFAULT_VARIANTS.Button, tone = "default", size = "medium", disabled, loading, onClick, children }) => {
26
26
  const { colors, radius, fontSize } = useTheme();
27
27
  const look = buttonLook(colors, variant, tone, Boolean(disabled));
28
- const sizing = SIZES[size];
28
+ const sizing = SIZES[size] ?? SIZES.medium;
29
29
  return /* @__PURE__ */ jsxs("button", {
30
30
  type: "button",
31
31
  className: "dot-button",
@@ -57,7 +57,7 @@ const Button$1 = ({ variant = DEFAULT_VARIANTS.Button, tone = "default", size =
57
57
  function buttonLook(colors, variant, tone, disabled) {
58
58
  const toneColor = tone === "default" ? void 0 : colors[tone];
59
59
  if (variant === "outline" || variant === "ghost") {
60
- let color = toneColor ?? colors.primary;
60
+ let color = toneColor ?? colors.primaryText;
61
61
  if (disabled) color = colors.subtle;
62
62
  let border = "none";
63
63
  if (variant === "outline") border = `1px solid ${disabled ? colors.border : color}`;
@@ -1,9 +1,10 @@
1
- import { SPACE } from "./tokens.js";
1
+ import { SHADOW_REACH, SPACE } from "./tokens.js";
2
2
  import { useTheme } from "../theme.js";
3
3
  import { clamp } from "./numbers.js";
4
4
  import { paddingStyle } from "./padding.js";
5
+ import { SideRoomContext } from "./side-room.js";
5
6
  import { createEvent } from "@monimesl/dot-core";
6
- import { Children, Fragment, isValidElement, useEffect, useEffectEvent, useRef, useState } from "react";
7
+ import { Children, Fragment, isValidElement, useContext, useEffect, useEffectEvent, useRef, useState } from "react";
7
8
  import { jsx, jsxs } from "react/jsx-runtime";
8
9
  //#region src/components/carousel.tsx
9
10
  const SLIDE_DURATION = 350;
@@ -13,7 +14,8 @@ const SWIPE_SPEED = .4;
13
14
  const EDGE_RESISTANCE = .3;
14
15
  const TAP_SLOP = 6;
15
16
  const Carousel$1 = ({ autoplay = false, interval = 4e3, loop = false, indicator = "dots", gap = "none", effect = "slide", index, onChange, children, ...padding }) => {
16
- const { colors, space } = useTheme();
17
+ const { colors, space, elevation } = useTheme();
18
+ const mayUseSides = useContext(SideRoomContext);
17
19
  const slides = slidesOf(children);
18
20
  const count = slides.length;
19
21
  const looping = loop && count > 1;
@@ -23,20 +25,20 @@ const Carousel$1 = ({ autoplay = false, interval = 4e3, loop = false, indicator
23
25
  const [animated, setAnimated] = useState(true);
24
26
  const [dragOffset, setDragOffset] = useState(0);
25
27
  const [dragging, setDragging] = useState(false);
26
- const [viewportWidth, setViewportWidth] = useState(0);
28
+ const [slideWidth, setSlideWidth] = useState(0);
27
29
  const [previousIndex, setPreviousIndex] = useState(index);
28
- const viewportRef = useRef(null);
30
+ const swipeAreaRef = useRef(null);
29
31
  const gestureRef = useRef(null);
30
32
  const ignoreNextClick = useRef(false);
33
+ const shownPosition = clamp(position, 0, lastPosition);
34
+ const currentIndex = count === 0 ? 0 : wrapIndex(shownPosition - firstPosition, count);
31
35
  if (index !== previousIndex) {
32
36
  setPreviousIndex(index);
33
- if (index !== void 0) {
37
+ if (index !== void 0 && slideIndex(index, count) !== currentIndex) {
34
38
  setAnimated(true);
35
39
  setPosition(firstPosition + slideIndex(index, count));
36
40
  }
37
41
  }
38
- const shownPosition = clamp(position, 0, lastPosition);
39
- const currentIndex = count === 0 ? 0 : wrapIndex(shownPosition - firstPosition, count);
40
42
  const moveTo = (targetPosition) => {
41
43
  const nextPosition = clamp(targetPosition, 0, lastPosition);
42
44
  setAnimated(true);
@@ -98,7 +100,7 @@ const Carousel$1 = ({ autoplay = false, interval = 4e3, loop = false, indicator
98
100
  };
99
101
  ignoreNextClick.current = false;
100
102
  setDragging(true);
101
- setViewportWidth(viewportRef.current?.clientWidth ?? 0);
103
+ setSlideWidth(swipeAreaRef.current?.clientWidth ?? 0);
102
104
  };
103
105
  const onPointerMove = (event) => {
104
106
  const swipe = gestureOf(event);
@@ -128,13 +130,14 @@ const Carousel$1 = ({ autoplay = false, interval = 4e3, loop = false, indicator
128
130
  if (!swipe.swiping) return;
129
131
  const movedX = event.clientX - swipe.startX;
130
132
  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);
133
+ if (Math.abs(movedX) > slideWidth * SWIPE_DISTANCE || speed > SWIPE_SPEED) stepBy(movedX < 0 ? 1 : -1);
132
134
  else moveTo(shownPosition);
133
135
  };
134
- const gapWidth = space[gap];
136
+ const gapWidth = mayUseSides && elevation === "raised" ? Math.max(space[gap], SHADOW_REACH.sides) : space[gap];
137
+ const sideRoom = mayUseSides ? gapWidth : 0;
135
138
  const transition = animated ? `transform ${SLIDE_DURATION}ms ${SLIDE_EASING}` : "none";
136
139
  let draggedSlides = 0;
137
- if (viewportWidth > 0) draggedSlides = dragOffset / (viewportWidth + gapWidth);
140
+ if (slideWidth > 0) draggedSlides = dragOffset / (slideWidth + gapWidth);
138
141
  let track = slides;
139
142
  if (looping) track = [
140
143
  {
@@ -159,49 +162,61 @@ const Carousel$1 = ({ autoplay = false, interval = 4e3, loop = false, indicator
159
162
  ...paddingStyle(padding, space)
160
163
  },
161
164
  children: [/* @__PURE__ */ jsx("div", {
162
- ref: viewportRef,
163
- onPointerDown,
164
- onPointerMove,
165
- onPointerUp,
166
- onPointerCancel: endGesture,
167
- onDragStart: (event) => event.preventDefault(),
168
- onClickCapture: (event) => {
169
- if (ignoreNextClick.current) {
170
- ignoreNextClick.current = false;
171
- event.stopPropagation();
172
- }
173
- },
174
165
  style: {
175
166
  overflow: "hidden",
176
- touchAction: "pan-y",
177
- userSelect: dragging ? "none" : void 0
167
+ paddingTop: SHADOW_REACH.top,
168
+ paddingBottom: SHADOW_REACH.bottom,
169
+ paddingInline: sideRoom,
170
+ marginTop: -SHADOW_REACH.top,
171
+ marginBottom: -SHADOW_REACH.bottom,
172
+ marginInline: -sideRoom,
173
+ pointerEvents: "none"
178
174
  },
179
175
  children: /* @__PURE__ */ jsx("div", {
176
+ ref: swipeAreaRef,
177
+ onPointerDown,
178
+ onPointerMove,
179
+ onPointerUp,
180
+ onPointerCancel: endGesture,
181
+ onDragStart: (event) => event.preventDefault(),
182
+ onClickCapture: (event) => {
183
+ if (ignoreNextClick.current) {
184
+ ignoreNextClick.current = false;
185
+ event.stopPropagation();
186
+ }
187
+ },
180
188
  style: {
181
- display: "flex",
182
- gap: gapWidth,
183
- transform: `translateX(calc(${-shownPosition} * (100% + ${gapWidth}px) + ${dragOffset}px))`,
184
- transition
189
+ pointerEvents: "auto",
190
+ touchAction: "pan-y",
191
+ userSelect: dragging ? "none" : void 0
185
192
  },
186
- children: track.map((slide, trackPosition) => {
187
- const visible = trackPosition === shownPosition;
188
- const distance = Math.abs(trackPosition - shownPosition + draggedSlides);
189
- const scale = 1 - Math.min(distance, 1) * .15000000000000002;
190
- return /* @__PURE__ */ jsx("div", {
191
- role: "group",
192
- "aria-roledescription": "slide",
193
- "aria-label": `${wrapIndex(trackPosition - firstPosition, count) + 1} of ${count}`,
194
- "aria-hidden": !visible,
195
- inert: !visible,
196
- style: {
197
- display: "grid",
198
- flex: "0 0 100%",
199
- minWidth: 0,
200
- transform: effect === "scale" ? `scale(${scale})` : void 0,
201
- transition: effect === "scale" ? transition : void 0
202
- },
203
- children: slide.node
204
- }, slide.key);
193
+ children: /* @__PURE__ */ jsx("div", {
194
+ style: {
195
+ display: "flex",
196
+ gap: gapWidth,
197
+ transform: `translateX(calc(${-shownPosition} * (100% + ${gapWidth}px) + ${dragOffset}px))`,
198
+ transition
199
+ },
200
+ children: track.map((slide, trackPosition) => {
201
+ const visible = trackPosition === shownPosition;
202
+ const distance = Math.abs(trackPosition - shownPosition + draggedSlides);
203
+ const scale = 1 - Math.min(distance, 1) * .15000000000000002;
204
+ return /* @__PURE__ */ jsx("div", {
205
+ role: "group",
206
+ "aria-roledescription": "slide",
207
+ "aria-label": `${wrapIndex(trackPosition - firstPosition, count) + 1} of ${count}`,
208
+ "aria-hidden": !visible,
209
+ inert: !visible,
210
+ style: {
211
+ display: "grid",
212
+ flex: "0 0 100%",
213
+ minWidth: 0,
214
+ transform: effect === "scale" ? `scale(${scale})` : void 0,
215
+ transition: effect === "scale" ? transition : void 0
216
+ },
217
+ children: slide.node
218
+ }, slide.key);
219
+ })
205
220
  })
206
221
  })
207
222
  }), indicator === "dots" && count > 1 ? /* @__PURE__ */ jsx("div", {
@@ -1,21 +1,16 @@
1
1
  import { SPACE } from "./tokens.js";
2
2
  import { useTheme } from "../theme.js";
3
- import { HOME_INDICATOR, insets, pinnedPaddingStyle } from "./padding.js";
3
+ import { insets } from "./padding.js";
4
+ import { pinnedBarStyle } from "./pinned-bar.js";
4
5
  import { jsx } from "react/jsx-runtime";
5
6
  //#region src/components/footer.tsx
6
7
  const Footer = ({ children, ...padding }) => {
7
8
  const { colors, space } = useTheme();
8
9
  return /* @__PURE__ */ jsx("footer", {
9
- className: "dot-footer",
10
+ className: "dot-footer dot-bar-bottom",
10
11
  style: {
11
- position: "sticky",
12
- bottom: 0,
13
- marginTop: "auto",
14
- display: "flex",
15
- flexDirection: "column",
12
+ ...pinnedBarStyle("bottom", padding, space, insets(SPACE.medium, 0)),
16
13
  gap: SPACE.medium,
17
- ...pinnedPaddingStyle(padding, space, insets(SPACE.medium, 0)),
18
- marginBottom: `calc(-1 * ${HOME_INDICATOR})`,
19
14
  background: colors.background
20
15
  },
21
16
  children
@@ -2,6 +2,7 @@ import { CARD_RADIUS_SCALE, CENTERED, PILL_STYLE } from "./tokens.js";
2
2
  import { useTheme } from "../theme.js";
3
3
  import { wholeCount } from "./numbers.js";
4
4
  import { paddingStyle } from "./padding.js";
5
+ import { SideRoomContext } from "./side-room.js";
5
6
  import { IconGraphic } from "./icon.js";
6
7
  import { pressable } from "./pressable.js";
7
8
  import { createEvent } from "@monimesl/dot-core";
@@ -18,7 +19,10 @@ const Grid$1 = ({ columns = 4, gap = "medium", children, ...padding }) => {
18
19
  gap: space[gap],
19
20
  ...paddingStyle(padding, space)
20
21
  },
21
- children
22
+ children: /* @__PURE__ */ jsx(SideRoomContext.Provider, {
23
+ value: false,
24
+ children
25
+ })
22
26
  });
23
27
  };
24
28
  const GridItem$1 = ({ label, icon, image, badge, onTap }) => {
@@ -5,7 +5,7 @@ import { IconSvg } from "./icon-svg.js";
5
5
  import { textToneColor } from "./box.js";
6
6
  import { jsx } from "react/jsx-runtime";
7
7
  //#region src/components/icon.tsx
8
- const ICON_PATH = /^statics\/(?!.*\.\.)[\w./-]+\.svg$/;
8
+ const ICON_PATH = /^statics\/(?!.*\.\.)[\w./-]+\.svg$/i;
9
9
  const unknownIcons = /* @__PURE__ */ new Set();
10
10
  function IconGraphic({ name, size, color }) {
11
11
  const { icons } = useTheme();
@@ -17,6 +17,7 @@ import { Input } from "./input.js";
17
17
  import { Link } from "./link.js";
18
18
  import { List, ListItem } from "./list.js";
19
19
  import { Option } from "./option.js";
20
+ import { Pinned } from "./pinned.js";
20
21
  import { Bullet, BulletList, Prose, Quote } from "./prose.js";
21
22
  import { QRCode } from "./qr-code.js";
22
23
  import { RichText } from "./rich-text.js";
@@ -35,4 +36,4 @@ import { TextArea } from "./text-area.js";
35
36
  import { Toast } from "./toast.js";
36
37
  import { Checkbox, Radio, RadioGroup, Switch } from "./toggles.js";
37
38
  import { View } from "./view.js";
38
- export { AmountInput, Avatar, Badge, Bullet, BulletList, Button, Carousel, Checkbox, Chip, CodeInput, DetailRow, Disclosure, Divider, EmptyState, Footer, Grid, GridItem, Icon, Image, Input, Link, List, ListItem, Notice, Option, ProgressBar, Prose, QRCode, Quote, Radio, RadioGroup, RichText, Scroll, Select, Sheet, Skeleton, Slider, Spinner, Stack, Step, Stepper, Steps, Switch, Tab, Tabs, Text, TextArea, Toast, View };
39
+ export { AmountInput, Avatar, Badge, Bullet, BulletList, Button, Carousel, Checkbox, Chip, CodeInput, DetailRow, Disclosure, Divider, EmptyState, Footer, Grid, GridItem, Icon, Image, Input, Link, List, ListItem, Notice, Option, Pinned, ProgressBar, Prose, QRCode, Quote, Radio, RadioGroup, RichText, Scroll, Select, Sheet, Skeleton, Slider, Spinner, Stack, Step, Stepper, Steps, Switch, Tab, Tabs, Text, TextArea, Toast, View };
@@ -19,6 +19,7 @@ import { Input } from "./input.js";
19
19
  import { Link } from "./link.js";
20
20
  import { List, ListItem } from "./list.js";
21
21
  import { Option } from "./option.js";
22
+ import { Pinned } from "./pinned.js";
22
23
  import { Bullet, BulletList, Prose, Quote } from "./prose.js";
23
24
  import { QRCode } from "./qr-code.js";
24
25
  import { Text } from "./text.js";
@@ -63,6 +64,7 @@ var components_exports = /* @__PURE__ */ __exportAll({
63
64
  ListItem: () => ListItem,
64
65
  Notice: () => Notice,
65
66
  Option: () => Option,
67
+ Pinned: () => Pinned,
66
68
  ProgressBar: () => ProgressBar,
67
69
  Prose: () => Prose,
68
70
  QRCode: () => QRCode,
@@ -89,4 +91,4 @@ var components_exports = /* @__PURE__ */ __exportAll({
89
91
  View: () => View
90
92
  });
91
93
  //#endregion
92
- export { AmountInput, Avatar, Badge, Bullet, BulletList, Button, Carousel, Checkbox, Chip, CodeInput, DetailRow, Disclosure, Divider, EmptyState, Footer, Grid, GridItem, Icon, Image, Input, Link, List, ListItem, Notice, Option, ProgressBar, Prose, QRCode, Quote, Radio, RadioGroup, RichText, Scroll, Select, Sheet, Skeleton, Slider, Spinner, Stack, Step, Stepper, Steps, Switch, Tab, Tabs, Text, TextArea, Toast, View, components_exports };
94
+ export { AmountInput, Avatar, Badge, Bullet, BulletList, Button, Carousel, Checkbox, Chip, CodeInput, DetailRow, Disclosure, Divider, EmptyState, Footer, Grid, GridItem, Icon, Image, Input, Link, List, ListItem, Notice, Option, Pinned, ProgressBar, Prose, QRCode, Quote, Radio, RadioGroup, RichText, Scroll, Select, Sheet, Skeleton, Slider, Spinner, Stack, Step, Stepper, Steps, Switch, Tab, Tabs, Text, TextArea, Toast, View, components_exports };
@@ -8,7 +8,7 @@ import { jsx } from "react/jsx-runtime";
8
8
  const Link$1 = ({ href, tone, onTap, children }) => {
9
9
  const { colors } = useTheme();
10
10
  const style = {
11
- color: tone ? toneColor(colors, tone) : colors.primary,
11
+ color: tone ? toneColor(colors, tone) : colors.primaryText,
12
12
  textDecorationLine: "underline",
13
13
  textUnderlineOffset: "2px",
14
14
  cursor: "pointer"
@@ -44,7 +44,7 @@ const List$1 = ({ size = "sm", onEndReached, children, ...padding }) => {
44
44
  return () => observer.disconnect();
45
45
  });
46
46
  return /* @__PURE__ */ jsx(ListSizeContext.Provider, {
47
- value: size,
47
+ value: Object.hasOwn(ROW_SIZES, size) ? size : "sm",
48
48
  children: /* @__PURE__ */ jsx("ul", {
49
49
  ref: listRef,
50
50
  className: "dot-list",
@@ -0,0 +1,29 @@
1
+ import { insets, paddingStyle, pinnedPaddingStyle } from "./padding.js";
2
+ //#region src/components/pinned-bar.ts
3
+ const STATUS_BAR = "env(safe-area-inset-top, 0px)";
4
+ function pinnedBarStyle(edge, padding, space, ownInsets = insets(0)) {
5
+ let edgeStyle;
6
+ if (edge === "top") edgeStyle = {
7
+ ...paddingStyle(padding, space, ownInsets),
8
+ top: STATUS_BAR
9
+ };
10
+ else edgeStyle = {
11
+ ...pinnedPaddingStyle(padding, space, ownInsets),
12
+ bottom: 0,
13
+ marginTop: "auto"
14
+ };
15
+ return {
16
+ ...edgeStyle,
17
+ position: "sticky",
18
+ zIndex: 1,
19
+ display: "flex",
20
+ flexDirection: "column",
21
+ alignSelf: "stretch",
22
+ marginInlineStart: "calc(-1 * var(--dot-view-start, 0px))",
23
+ marginInlineEnd: "calc(-1 * var(--dot-view-end, 0px))",
24
+ paddingInlineStart: `calc(var(--dot-view-start, 0px) + ${edgeStyle.paddingInlineStart}px)`,
25
+ paddingInlineEnd: `calc(var(--dot-view-end, 0px) + ${edgeStyle.paddingInlineEnd}px)`
26
+ };
27
+ }
28
+ //#endregion
29
+ export { pinnedBarStyle };
@@ -0,0 +1,6 @@
1
+ import { ReactComponent } from "../types.js";
2
+ import { Pinned } from "@monimesl/dot-core";
3
+ //#region src/components/pinned.d.ts
4
+ declare const Pinned$1: ReactComponent<typeof Pinned>;
5
+ //#endregion
6
+ export { Pinned$1 as Pinned };
@@ -0,0 +1,26 @@
1
+ import { useTheme } from "../theme.js";
2
+ import { backgroundColor } from "./box.js";
3
+ import { pinnedBarStyle } from "./pinned-bar.js";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/components/pinned.tsx
6
+ const Pinned = ({ edge = "top", gap = "medium", background = "background", border = "none", children, ...padding }) => {
7
+ const theme = useTheme();
8
+ const { colors, space } = theme;
9
+ const style = {
10
+ ...pinnedBarStyle(edge, padding, space),
11
+ gap: space[gap],
12
+ background: backgroundColor(background, theme)
13
+ };
14
+ if (border !== "none") {
15
+ const line = `1px solid ${border === "strong" ? colors.subtle : colors.border}`;
16
+ if (edge === "top") style.borderBottom = line;
17
+ else style.borderTop = line;
18
+ }
19
+ return /* @__PURE__ */ jsx("div", {
20
+ className: `dot-pinned dot-bar-${edge}`,
21
+ style,
22
+ children
23
+ });
24
+ };
25
+ //#endregion
26
+ export { Pinned };
@@ -1,55 +1,67 @@
1
1
  import { useTheme } from "../theme.js";
2
- import { Divider } from "./divider.js";
3
- import { Image } from "./image.js";
2
+ import { Divider as Divider$1 } from "./divider.js";
3
+ import { Image as Image$2 } from "./image.js";
4
4
  import { isHttps } from "./https.js";
5
- import { Link } from "./link.js";
6
- import { Bullet, BulletList, Prose, Quote } from "./prose.js";
5
+ import { Link as Link$2 } from "./link.js";
6
+ import { Bullet as Bullet$1, BulletList as BulletList$1, Prose as Prose$2, Quote as Quote$1 } from "./prose.js";
7
7
  import { parseMarkdown } from "../markdown.js";
8
- import { Text } from "./text.js";
8
+ import { Text as Text$2 } from "./text.js";
9
+ import { withComponentDefaults } from "@monimesl/dot-core";
9
10
  import { useMemo } from "react";
10
11
  import { jsx } from "react/jsx-runtime";
11
12
  //#region src/components/rich-text.tsx
13
+ function themed(name, Component) {
14
+ return function Themed(props) {
15
+ const { components } = useTheme();
16
+ const merged = withComponentDefaults(name, props, components);
17
+ return /* @__PURE__ */ jsx(Component, { ...merged });
18
+ };
19
+ }
20
+ const Image$1 = themed("Image", Image$2);
21
+ const Link$1 = themed("Link", Link$2);
22
+ const Prose$1 = themed("Prose", Prose$2);
23
+ const Text$1 = themed("Text", Text$2);
12
24
  const HEADING_TEXT = {
13
25
  1: { variant: "title" },
14
26
  2: { variant: "subtitle" },
15
27
  3: { weight: "bold" }
16
28
  };
17
- const RichText = ({ markdown }) => {
29
+ const RichText$1 = ({ markdown }) => {
18
30
  const blocks = useMemo(() => parseMarkdown(markdown ?? ""), [markdown]);
19
- return /* @__PURE__ */ jsx(Prose, { children: blocks.map(renderBlock) });
31
+ return /* @__PURE__ */ jsx(Prose$1, { children: blocks.map(renderBlock) });
20
32
  };
21
33
  function renderBlock(block, key) {
22
34
  switch (block.kind) {
23
- case "heading": return /* @__PURE__ */ jsx(Text, {
35
+ case "heading": return /* @__PURE__ */ jsx(Text$1, {
24
36
  ...HEADING_TEXT[block.level],
25
37
  children: block.content.map(renderInline)
26
38
  }, key);
27
- case "paragraph": return /* @__PURE__ */ jsx(Text, { children: block.content.map(renderInline) }, key);
28
- case "list": return /* @__PURE__ */ jsx(BulletList, {
39
+ case "paragraph": return /* @__PURE__ */ jsx(Text$1, { children: block.content.map(renderInline) }, key);
40
+ case "list": return /* @__PURE__ */ jsx(BulletList$1, {
29
41
  ordered: block.ordered,
30
- children: block.items.map((item, itemKey) => /* @__PURE__ */ jsx(Bullet, { children: item.map(renderBlock) }, itemKey))
42
+ children: block.items.map((item, itemKey) => /* @__PURE__ */ jsx(Bullet$1, { children: item.map(renderBlock) }, itemKey))
31
43
  }, key);
32
- case "quote": return /* @__PURE__ */ jsx(Quote, { children: block.blocks.map(renderBlock) }, key);
33
- case "image": return /* @__PURE__ */ jsx(Image, {
44
+ case "quote": return /* @__PURE__ */ jsx(Quote$1, { children: block.blocks.map(renderBlock) }, key);
45
+ case "image": return /* @__PURE__ */ jsx(Image$1, {
34
46
  src: block.src,
35
47
  alt: block.alt
36
48
  }, key);
37
- case "rule": return /* @__PURE__ */ jsx(Divider, {}, key);
49
+ case "rule": return /* @__PURE__ */ jsx(Divider$1, {}, key);
38
50
  }
39
51
  }
40
52
  function renderInline(inline, key) {
41
53
  switch (inline.kind) {
42
54
  case "text": return inline.text;
43
- case "strong": return /* @__PURE__ */ jsx(Text, {
55
+ case "strong": return /* @__PURE__ */ jsx(Text$1, {
44
56
  weight: "bold",
45
57
  children: inline.children.map(renderInline)
46
58
  }, key);
47
- case "emphasis": return /* @__PURE__ */ jsx(Text, {
59
+ case "emphasis": return /* @__PURE__ */ jsx(Text$1, {
48
60
  italic: true,
49
61
  children: inline.children.map(renderInline)
50
62
  }, key);
51
63
  case "code": return /* @__PURE__ */ jsx(Code, { text: inline.text }, key);
52
- case "link": return isHttps(inline.href) ? /* @__PURE__ */ jsx(Link, {
64
+ case "link": return isHttps(inline.href) ? /* @__PURE__ */ jsx(Link$1, {
53
65
  href: inline.href,
54
66
  children: inline.children.map(renderInline)
55
67
  }, key) : inline.children.map(renderInline);
@@ -70,4 +82,4 @@ function Code({ text }) {
70
82
  });
71
83
  }
72
84
  //#endregion
73
- export { RichText };
85
+ export { RichText$1 as RichText };
@@ -1,7 +1,9 @@
1
+ import { SCROLL_SHADOW_ROOM, SHADOW_REACH } from "./tokens.js";
1
2
  import { useTheme } from "../theme.js";
2
3
  import { paddingStyle } from "./padding.js";
4
+ import { SideRoomContext } from "./side-room.js";
3
5
  import { createEvent } from "@monimesl/dot-core";
4
- import { useEffect, useRef } from "react";
6
+ import { useContext, useEffect, useRef } from "react";
5
7
  import { jsx } from "react/jsx-runtime";
6
8
  //#region src/components/scroll.tsx
7
9
  const REST_DELAY_MS = 120;
@@ -9,9 +11,11 @@ const Scroll$1 = ({ gap = "medium", snap = "none", index, onChange, children, ..
9
11
  const rowRef = useRef(null);
10
12
  const reportedIndex = useRef(void 0);
11
13
  const restTimer = useRef(void 0);
12
- const { space } = useTheme();
14
+ const { space, elevation } = useTheme();
13
15
  const paddingCss = paddingStyle(padding, space);
14
- const startPadding = Number(paddingCss.paddingInlineStart);
16
+ const sideRoom = useContext(SideRoomContext) && elevation === "raised" ? SHADOW_REACH.sides : 0;
17
+ const startPadding = Number(paddingCss.paddingInlineStart) + sideRoom;
18
+ const endPadding = Number(paddingCss.paddingInlineEnd) + sideRoom;
15
19
  useEffect(() => {
16
20
  const element = rowRef.current;
17
21
  if (index === void 0 || index === reportedIndex.current || !element) return;
@@ -44,10 +48,22 @@ const Scroll$1 = ({ gap = "medium", snap = "none", index, onChange, children, ..
44
48
  }, REST_DELAY_MS);
45
49
  } : void 0,
46
50
  style: {
51
+ position: "relative",
52
+ display: "flex",
53
+ overflowX: "auto",
54
+ overflowY: "hidden",
55
+ overscrollBehaviorX: "contain",
56
+ scrollbarWidth: "none",
47
57
  gap: space[gap],
48
- ...paddingCss,
49
- scrollPaddingInlineStart: paddingCss.paddingInlineStart,
50
- scrollPaddingInlineEnd: paddingCss.paddingInlineEnd,
58
+ paddingTop: Number(paddingCss.paddingTop) + SCROLL_SHADOW_ROOM.top,
59
+ paddingBottom: Number(paddingCss.paddingBottom) + SCROLL_SHADOW_ROOM.bottom,
60
+ paddingInlineStart: startPadding,
61
+ paddingInlineEnd: endPadding,
62
+ marginTop: -SCROLL_SHADOW_ROOM.top,
63
+ marginBottom: -SCROLL_SHADOW_ROOM.bottom,
64
+ marginInline: -sideRoom,
65
+ scrollPaddingInlineStart: startPadding,
66
+ scrollPaddingInlineEnd: endPadding,
51
67
  scrollSnapType: snap === "none" ? void 0 : "x mandatory",
52
68
  "--dot-scroll-snap": snap === "none" ? "none" : snap
53
69
  },
@@ -32,7 +32,7 @@ const SheetPanel = ({ open, title, showCloseIcon = false, onClose, children, ...
32
32
  zIndex: 100,
33
33
  background: "rgba(0, 0, 0, 0.4)"
34
34
  }
35
- }), /* @__PURE__ */ jsxs(DialogPopup, {
35
+ }), /* @__PURE__ */ jsx(DialogPopup, {
36
36
  className: "dot-sheet",
37
37
  "aria-label": title,
38
38
  style: {
@@ -40,17 +40,8 @@ const SheetPanel = ({ open, title, showCloseIcon = false, onClose, children, ...
40
40
  insetInline: 0,
41
41
  bottom: 0,
42
42
  zIndex: 101,
43
- display: "flex",
44
- flexDirection: "column",
45
- gap: SPACE.medium,
46
43
  maxHeight: "85vh",
47
44
  overflowY: "auto",
48
- ...pinnedPaddingStyle(padding, space, {
49
- top: 16,
50
- start: 16,
51
- end: 16,
52
- bottom: 24
53
- }),
54
45
  borderTopLeftRadius: cornerRadius,
55
46
  borderTopRightRadius: cornerRadius,
56
47
  background: colors.surface,
@@ -58,39 +49,53 @@ const SheetPanel = ({ open, title, showCloseIcon = false, onClose, children, ...
58
49
  fontFamily,
59
50
  outline: "none"
60
51
  },
61
- children: [
62
- title ? /* @__PURE__ */ jsx(DialogTitle, {
63
- style: {
64
- margin: 0,
65
- fontSize: fontSize.body,
66
- fontFamily: headingFontFamily,
67
- fontWeight: WEIGHTS[headingWeight ?? "bold"],
68
- lineHeight: "normal",
69
- textAlign: "center"
70
- },
71
- children: title
72
- }) : null,
73
- showCloseIcon ? /* @__PURE__ */ jsx(DialogClose, {
74
- "aria-label": "Close",
75
- style: {
76
- position: "absolute",
77
- top: 10,
78
- insetInlineEnd: 10,
79
- display: "flex",
80
- padding: 4,
81
- border: "none",
82
- borderRadius: "50%",
83
- background: "transparent",
84
- cursor: "pointer"
85
- },
86
- children: /* @__PURE__ */ jsx(IconSvg, {
87
- artwork: CLOSE_ARTWORK,
88
- size: ICON_SIZE.medium,
89
- color: colors.subtle
52
+ children: /* @__PURE__ */ jsxs("div", {
53
+ style: {
54
+ display: "flex",
55
+ flexDirection: "column",
56
+ gap: SPACE.medium,
57
+ overflow: "clip",
58
+ ...pinnedPaddingStyle(padding, space, {
59
+ top: 16,
60
+ start: 16,
61
+ end: 16,
62
+ bottom: 24
90
63
  })
91
- }) : null,
92
- children
93
- ]
64
+ },
65
+ children: [
66
+ title ? /* @__PURE__ */ jsx(DialogTitle, {
67
+ style: {
68
+ margin: 0,
69
+ fontSize: fontSize.body,
70
+ fontFamily: headingFontFamily,
71
+ fontWeight: WEIGHTS[headingWeight ?? "bold"],
72
+ lineHeight: "normal",
73
+ textAlign: "center"
74
+ },
75
+ children: title
76
+ }) : null,
77
+ showCloseIcon ? /* @__PURE__ */ jsx(DialogClose, {
78
+ "aria-label": "Close",
79
+ style: {
80
+ position: "absolute",
81
+ top: 10,
82
+ insetInlineEnd: 10,
83
+ display: "flex",
84
+ padding: 4,
85
+ border: "none",
86
+ borderRadius: "50%",
87
+ background: "transparent",
88
+ cursor: "pointer"
89
+ },
90
+ children: /* @__PURE__ */ jsx(IconSvg, {
91
+ artwork: CLOSE_ARTWORK,
92
+ size: ICON_SIZE.medium,
93
+ color: colors.subtle
94
+ })
95
+ }) : null,
96
+ children
97
+ ]
98
+ })
94
99
  })]
95
100
  })
96
101
  });
@@ -0,0 +1,5 @@
1
+ import { createContext } from "react";
2
+ //#region src/components/side-room.ts
3
+ const SideRoomContext = createContext(true);
4
+ //#endregion
5
+ export { SideRoomContext };
@@ -1,4 +1,5 @@
1
1
  import { useTheme } from "../theme.js";
2
+ import { SideRoomContext } from "./side-room.js";
2
3
  import { boxStyle } from "./box.js";
3
4
  import { Children } from "react";
4
5
  import { jsx } from "react/jsx-runtime";
@@ -11,14 +12,17 @@ const Stack = (props) => {
11
12
  display: "grid",
12
13
  ...boxStyle(props, theme)
13
14
  },
14
- children: Children.map(props.children, (child) => /* @__PURE__ */ jsx("div", {
15
- style: {
16
- gridArea: "1 / 1",
17
- display: "grid",
18
- minWidth: 0
19
- },
20
- children: child
21
- }))
15
+ children: /* @__PURE__ */ jsx(SideRoomContext.Provider, {
16
+ value: false,
17
+ children: Children.map(props.children, (child) => /* @__PURE__ */ jsx("div", {
18
+ style: {
19
+ gridArea: "1 / 1",
20
+ display: "grid",
21
+ minWidth: 0
22
+ },
23
+ children: child
24
+ }))
25
+ })
22
26
  });
23
27
  };
24
28
  //#endregion
@@ -1,6 +1,7 @@
1
- import { CENTERED, PILL_STYLE, WEIGHTS } from "./tokens.js";
1
+ import { CENTERED, ICON_SIZE, PILL_STYLE, WEIGHTS } from "./tokens.js";
2
2
  import { useTheme } from "../theme.js";
3
3
  import { paddingStyle } from "./padding.js";
4
+ import { IconGraphic } from "./icon.js";
4
5
  import { TabsRoot } from "../vendor/@base-ui/react/tabs/root/TabsRoot.js";
5
6
  import { TabsTab } from "../vendor/@base-ui/react/tabs/tab/TabsTab.js";
6
7
  import { TabsList } from "../vendor/@base-ui/react/tabs/list/TabsList.js";
@@ -10,19 +11,19 @@ import { jsx, jsxs } from "react/jsx-runtime";
10
11
  //#region src/components/tabs.tsx
11
12
  const TabsContext = createContext({
12
13
  value: void 0,
13
- segmented: false
14
+ variant: DEFAULT_VARIANTS.Tabs
14
15
  });
16
+ const BAR_ICON_SIZE = 24;
15
17
  const Tabs$1 = ({ value, variant = DEFAULT_VARIANTS.Tabs, onChange, children, ...padding }) => {
16
18
  const { colors, radius, space } = useTheme();
17
- const segmented = variant === "segmented";
18
- let variantStyle;
19
- if (segmented) variantStyle = {
19
+ let variantStyle = {};
20
+ if (variant === "segmented") variantStyle = {
20
21
  gap: 2,
21
22
  padding: 3,
22
23
  borderRadius: radius,
23
24
  background: colors.muted
24
25
  };
25
- else variantStyle = { borderBottom: `1px solid ${colors.border}` };
26
+ else if (variant === "underline") variantStyle = { boxShadow: `inset 0 -1px 0 ${colors.border}` };
26
27
  return /* @__PURE__ */ jsx(TabsRoot, {
27
28
  value: value ?? null,
28
29
  onValueChange: (next) => {
@@ -34,36 +35,59 @@ const Tabs$1 = ({ value, variant = DEFAULT_VARIANTS.Tabs, onChange, children, ..
34
35
  style: {
35
36
  display: "flex",
36
37
  overflowX: "auto",
38
+ overflowY: "hidden",
37
39
  ...variantStyle
38
40
  },
39
41
  children: /* @__PURE__ */ jsx(TabsContext.Provider, {
40
42
  value: {
41
43
  value,
42
- segmented
44
+ variant
43
45
  },
44
46
  children
45
47
  })
46
48
  })
47
49
  });
48
50
  };
49
- const Tab$1 = ({ value, label, badge }) => {
51
+ const Tab$1 = ({ value, label, icon, badge }) => {
50
52
  const { colors, radius, fontSize } = useTheme();
51
- const { value: chosen, segmented } = useContext(TabsContext);
53
+ const { value: chosen, variant } = useContext(TabsContext);
52
54
  if (value === void 0) return null;
53
55
  const selected = value === chosen;
56
+ const bar = variant === "bar";
54
57
  let color = colors.subtle;
55
- if (selected) color = segmented ? colors.text : colors.primary;
58
+ if (selected) color = variant === "segmented" ? colors.text : colors.primaryText;
56
59
  let variantStyle;
57
- if (segmented) variantStyle = {
60
+ if (variant === "segmented") variantStyle = {
61
+ minHeight: 36,
58
62
  borderRadius: Math.max(radius - 2, 0),
59
63
  background: selected ? colors.surface : "transparent",
60
64
  boxShadow: selected ? "0 1px 3px rgba(0, 0, 0, 0.15)" : "none"
61
65
  };
66
+ else if (bar) variantStyle = {
67
+ flexDirection: "column",
68
+ gap: 2,
69
+ flex: "1 1 0",
70
+ minWidth: 0,
71
+ minHeight: 50,
72
+ padding: "6px 4px",
73
+ fontSize: fontSize.caption,
74
+ background: "transparent"
75
+ };
62
76
  else variantStyle = {
63
- marginBottom: -1,
77
+ minHeight: 44,
64
78
  background: "transparent",
65
- borderBottom: `2px solid ${selected ? colors.primary : "transparent"}`
79
+ boxShadow: selected ? `inset 0 -2px 0 ${colors.primary}` : "none"
66
80
  };
81
+ const count = badge ? /* @__PURE__ */ jsx("span", {
82
+ style: {
83
+ ...PILL_STYLE,
84
+ display: "block",
85
+ background: colors.primary,
86
+ color: colors.onPrimary
87
+ },
88
+ children: badge
89
+ }) : null;
90
+ const countOnIcon = bar && Boolean(icon);
67
91
  return /* @__PURE__ */ jsxs(TabsTab, {
68
92
  className: "dot-tab",
69
93
  value,
@@ -71,7 +95,6 @@ const Tab$1 = ({ value, label, badge }) => {
71
95
  ...CENTERED,
72
96
  gap: 6,
73
97
  flex: "1 0 auto",
74
- minHeight: segmented ? 36 : 44,
75
98
  padding: "0 12px",
76
99
  border: "none",
77
100
  fontSize: fontSize.body,
@@ -81,14 +104,37 @@ const Tab$1 = ({ value, label, badge }) => {
81
104
  cursor: "pointer",
82
105
  ...variantStyle
83
106
  },
84
- children: [label, badge ? /* @__PURE__ */ jsx("span", {
107
+ children: [icon ? /* @__PURE__ */ jsxs("span", {
85
108
  style: {
86
- ...PILL_STYLE,
87
- background: colors.primary,
88
- color: colors.onPrimary
109
+ position: "relative",
110
+ display: "inline-flex"
89
111
  },
90
- children: badge
91
- }) : null]
112
+ children: [/* @__PURE__ */ jsx(IconGraphic, {
113
+ name: icon,
114
+ size: bar ? BAR_ICON_SIZE : ICON_SIZE.medium,
115
+ color: "currentColor"
116
+ }), countOnIcon && count ? /* @__PURE__ */ jsx("span", {
117
+ style: {
118
+ position: "absolute",
119
+ top: -6,
120
+ insetInlineStart: 16
121
+ },
122
+ children: count
123
+ }) : null]
124
+ }) : null, /* @__PURE__ */ jsxs("span", {
125
+ style: {
126
+ ...CENTERED,
127
+ gap: 6,
128
+ maxWidth: "100%"
129
+ },
130
+ children: [/* @__PURE__ */ jsx("span", {
131
+ style: {
132
+ overflow: "hidden",
133
+ textOverflow: "ellipsis"
134
+ },
135
+ children: label
136
+ }), countOnIcon ? null : count]
137
+ })]
92
138
  });
93
139
  };
94
140
  //#endregion
@@ -50,10 +50,12 @@ const DECORATION = {
50
50
  underline: "underline",
51
51
  strikethrough: "line-through"
52
52
  };
53
- const Text$1 = ({ variant = DEFAULT_VARIANTS.Text, tone, weight, align, maxLines, decoration, numeric, leading, italic, selectable, children, ...padding }) => {
53
+ const Text$1 = ({ variant: namedVariant, tone, weight, align, maxLines, decoration, numeric, leading, italic, selectable, children, ...padding }) => {
54
54
  const theme = useTheme();
55
55
  const insideText = useContext(InsideText);
56
56
  const inProse = useContext(InsideProse);
57
+ let variant = namedVariant ?? DEFAULT_VARIANTS.Text;
58
+ if (!Object.hasOwn(VARIANTS, variant)) variant = DEFAULT_VARIANTS.Text;
57
59
  const { heading, lineHeight, weight: defaultWeight } = VARIANTS[variant];
58
60
  const defaultLeading = inProse && !heading ? "relaxed" : "normal";
59
61
  const style = {
@@ -30,6 +30,15 @@ const SHADOWS = {
30
30
  high: "0 4px 12px rgba(0, 0, 0, 0.5), 0 12px 32px rgba(0, 0, 0, 0.5)"
31
31
  }
32
32
  };
33
+ const SHADOW_REACH = {
34
+ top: 20,
35
+ sides: 32,
36
+ bottom: 44
37
+ };
38
+ const SCROLL_SHADOW_ROOM = {
39
+ top: 6,
40
+ bottom: 10
41
+ };
33
42
  const CENTERED = {
34
43
  display: "flex",
35
44
  alignItems: "center",
@@ -56,4 +65,4 @@ const SELECTABLE = {
56
65
  WebkitUserSelect: "text"
57
66
  };
58
67
  //#endregion
59
- export { CARD_RADIUS_SCALE, CENTERED, DISABLED_OPACITY, FONT_SIZE, ICON_SIZE, PILL_STYLE, SELECTABLE, SHADOWS, SPACE, WEIGHTS };
68
+ export { CARD_RADIUS_SCALE, CENTERED, DISABLED_OPACITY, FONT_SIZE, ICON_SIZE, PILL_STYLE, SCROLL_SHADOW_ROOM, SELECTABLE, SHADOWS, SHADOW_REACH, SPACE, WEIGHTS };
@@ -1,11 +1,11 @@
1
1
  import { SHADOWS } from "./tokens.js";
2
2
  import { useTheme } from "../theme.js";
3
3
  import { insets, paddingStyle } from "./padding.js";
4
+ import { SideRoomContext } from "./side-room.js";
4
5
  import { boxStyle } from "./box.js";
5
- import { Footer as Footer$1 } from "./footer.js";
6
6
  import { pressable } from "./pressable.js";
7
7
  import { DEFAULT_VARIANTS, createEvent } from "@monimesl/dot-core";
8
- import { Children, isValidElement } from "react";
8
+ import { useContext } from "react";
9
9
  import { jsx } from "react/jsx-runtime";
10
10
  //#region src/components/view.tsx
11
11
  const ALIGN = {
@@ -30,13 +30,15 @@ const RAISED_CARD_LOOK = {
30
30
  border: "none"
31
31
  };
32
32
  const View$1 = (props) => {
33
- const { direction = "column", gap = "medium", align = "stretch", justify = "start", wrap = false, grow = false, variant = DEFAULT_VARIANTS.View, onTap, children } = props;
33
+ const { id, direction = "column", gap = "medium", align = "stretch", justify = "start", wrap = false, grow = false, variant = DEFAULT_VARIANTS.View, onTap, children } = props;
34
34
  const theme = useTheme();
35
+ const sideRoomAround = useContext(SideRoomContext);
35
36
  const card = variant === "card";
36
- const raisedCard = card && theme.elevation === "raised";
37
+ const raisedCard = card && theme.elevation === "raised" && props.elevation !== "none";
37
38
  let cardLook;
38
39
  if (card) cardLook = raisedCard ? RAISED_CARD_LOOK : CARD_LOOK;
39
40
  const elevation = props.elevation ?? (raisedCard ? "low" : "none");
41
+ const padding = paddingStyle(props, theme.space, card ? insets(theme.space.large) : void 0);
40
42
  const style = {
41
43
  display: "flex",
42
44
  flexDirection: direction,
@@ -44,12 +46,15 @@ const View$1 = (props) => {
44
46
  justifyContent: JUSTIFY[justify],
45
47
  flexWrap: wrap ? "wrap" : "nowrap",
46
48
  gap: theme.space[gap],
47
- ...paddingStyle(props, theme.space, card ? insets(theme.space.large) : void 0),
49
+ ...padding,
50
+ "--dot-view-top": `${padding.paddingTop}px`,
51
+ "--dot-view-bottom": `${padding.paddingBottom}px`,
52
+ "--dot-view-start": `${padding.paddingInlineStart}px`,
53
+ "--dot-view-end": `${padding.paddingInlineEnd}px`,
48
54
  ...boxStyle(props, theme, cardLook),
49
55
  cursor: onTap ? "pointer" : void 0
50
56
  };
51
57
  if (elevation !== "none") style.boxShadow = SHADOWS[theme.scheme][elevation];
52
- if (endsWithFooter(children)) style.paddingBottom = 0;
53
58
  if (grow) {
54
59
  style.flexGrow = 1;
55
60
  style.minWidth = 0;
@@ -57,14 +62,14 @@ const View$1 = (props) => {
57
62
  const press = onTap ? pressable(() => onTap(createEvent("tap"))) : void 0;
58
63
  return /* @__PURE__ */ jsx("div", {
59
64
  className: "dot-view",
65
+ "data-dot-view": id,
60
66
  style,
61
67
  ...press,
62
- children
68
+ children: /* @__PURE__ */ jsx(SideRoomContext.Provider, {
69
+ value: sideRoomAround && direction !== "row",
70
+ children
71
+ })
63
72
  });
64
73
  };
65
- function endsWithFooter(children) {
66
- const last = Children.toArray(children).at(-1);
67
- return isValidElement(last) && last.type === Footer$1;
68
- }
69
74
  //#endregion
70
75
  export { View$1 as View };
package/dist/index.d.ts CHANGED
@@ -20,6 +20,7 @@ import { Input } from "./components/input.js";
20
20
  import { Link } from "./components/link.js";
21
21
  import { List, ListItem } from "./components/list.js";
22
22
  import { Option } from "./components/option.js";
23
+ import { Pinned } from "./components/pinned.js";
23
24
  import { Bullet, BulletList, Prose, Quote } from "./components/prose.js";
24
25
  import { QRCode } from "./components/qr-code.js";
25
26
  import { RichText } from "./components/rich-text.js";
@@ -40,4 +41,4 @@ import { Checkbox, Radio, RadioGroup, Switch } from "./components/toggles.js";
40
41
  import { View } from "./components/view.js";
41
42
  import "./components/index.js";
42
43
  import { ResolvedColors, ResolvedTheme, resolveTheme, useTheme } from "./theme.js";
43
- export { AmountInput, Avatar, Badge, type BuiltinReactComponents, Bullet, BulletList, Button, Carousel, Checkbox, Chip, CodeInput, type CreateReactRendererOptions, DetailRow, Disclosure, Divider, EmptyState, Footer, Grid, GridItem, Icon, Image, Input, Link, List, ListItem, Notice, Option, ProgressBar, Prose, QRCode, Quote, Radio, RadioGroup, type ReactComponent, type ReactComponents, type ReactRendererOptions, type ResolvedColors, type ResolvedTheme, RichText, Scroll, Select, Sheet, Skeleton, Slider, Spinner, Stack, Step, Stepper, Steps, Switch, Tab, Tabs, Text, TextArea, Toast, View, type Wrapper, createReactRenderer, reactRenderer as default, resolveTheme, useTheme };
44
+ export { AmountInput, Avatar, Badge, type BuiltinReactComponents, Bullet, BulletList, Button, Carousel, Checkbox, Chip, CodeInput, type CreateReactRendererOptions, DetailRow, Disclosure, Divider, EmptyState, Footer, Grid, GridItem, Icon, Image, Input, Link, List, ListItem, Notice, Option, Pinned, ProgressBar, Prose, QRCode, Quote, Radio, RadioGroup, type ReactComponent, type ReactComponents, type ReactRendererOptions, type ResolvedColors, type ResolvedTheme, RichText, Scroll, Select, Sheet, Skeleton, Slider, Spinner, Stack, Step, Stepper, Steps, Switch, Tab, Tabs, Text, TextArea, Toast, View, type Wrapper, createReactRenderer, reactRenderer as default, resolveTheme, useTheme };
package/dist/index.js CHANGED
@@ -19,6 +19,7 @@ import { Input } from "./components/input.js";
19
19
  import { Link } from "./components/link.js";
20
20
  import { List, ListItem } from "./components/list.js";
21
21
  import { Option } from "./components/option.js";
22
+ import { Pinned } from "./components/pinned.js";
22
23
  import { Bullet, BulletList, Prose, Quote } from "./components/prose.js";
23
24
  import { QRCode } from "./components/qr-code.js";
24
25
  import { Text } from "./components/text.js";
@@ -40,4 +41,4 @@ import { createReactRenderer, reactRenderer } from "./renderer.js";
40
41
  //#region src/index.ts
41
42
  var src_default = reactRenderer;
42
43
  //#endregion
43
- export { AmountInput, Avatar, Badge, Bullet, BulletList, Button, Carousel, Checkbox, Chip, CodeInput, DetailRow, Disclosure, Divider, EmptyState, Footer, Grid, GridItem, Icon, Image, Input, Link, List, ListItem, Notice, Option, ProgressBar, Prose, QRCode, Quote, Radio, RadioGroup, RichText, Scroll, Select, Sheet, Skeleton, Slider, Spinner, Stack, Step, Stepper, Steps, Switch, Tab, Tabs, Text, TextArea, Toast, View, createReactRenderer, src_default as default, resolveTheme, useTheme };
44
+ export { AmountInput, Avatar, Badge, Bullet, BulletList, Button, Carousel, Checkbox, Chip, CodeInput, DetailRow, Disclosure, Divider, EmptyState, Footer, Grid, GridItem, Icon, Image, Input, Link, List, ListItem, Notice, Option, Pinned, ProgressBar, Prose, QRCode, Quote, Radio, RadioGroup, RichText, Scroll, Select, Sheet, Skeleton, Slider, Spinner, Stack, Step, Stepper, Steps, Switch, Tab, Tabs, Text, TextArea, Toast, View, createReactRenderer, src_default as default, resolveTheme, useTheme };
package/dist/root.js CHANGED
@@ -27,6 +27,7 @@ function Root({ page, dotContext, components, wrapper: Wrapper }) {
27
27
  ref: setRootElement,
28
28
  className: "dot-theme-root",
29
29
  "data-dot-scheme": resolvedTheme.scheme,
30
+ style: { overflow: "clip" },
30
31
  children: /* @__PURE__ */ jsx(PortalContainerContext.Provider, {
31
32
  value: rootElement,
32
33
  children: /* @__PURE__ */ jsx(PullToRefresh, {
package/dist/styles.css CHANGED
@@ -1 +1 @@
1
- @layer base{.dot-theme-root,.dot-theme-root *,.dot-theme-root :before,.dot-theme-root :after{box-sizing:border-box}.dot-theme-root :is(button,input,select,textarea){font:inherit}[data-dot-scheme=light]{color-scheme:light}[data-dot-scheme=dark]{color-scheme:dark}.dot-theme-root{-webkit-user-select:none;user-select:none}.dot-theme-root :is(input,textarea){-webkit-user-select:text;user-select:text}}@keyframes dot-skeleton-pulse{0%,to{opacity:1}50%{opacity:.4}}.dot-image{max-width:100%;display:block}.dot-list>.dot-list-item:not(:last-child){border-bottom:1px solid var(--dot-list-divider)}.dot-scroll{overscroll-behavior-x:contain;scrollbar-width:none;flex-direction:row;display:flex;position:relative;overflow:auto hidden}.dot-scroll::-webkit-scrollbar{display:none}.dot-scroll>*{scroll-snap-align:var(--dot-scroll-snap);flex:none;width:min-content}.dot-scroll .dot-image{max-width:none}.dot-disclosure>summary{list-style:none}.dot-disclosure>summary::-webkit-details-marker{display:none}.dot-steps>.dot-step:last-child .dot-step-line{display:none}.dot-sheet-backdrop{transition:opacity .2s}.dot-sheet{transition:transform .2s ease-out}.dot-sheet-backdrop:is([data-starting-style],[data-ending-style]){opacity:0}.dot-sheet:is([data-starting-style],[data-ending-style]){transform:translateY(100%)}.dot-prose>.dot-heading{margin-block-start:.5em}.dot-prose>.dot-heading:first-child{margin-block-start:0}
1
+ @layer base{.dot-theme-root,.dot-theme-root *,.dot-theme-root :before,.dot-theme-root :after{box-sizing:border-box}.dot-theme-root :is(button,input,select,textarea){font:inherit}[data-dot-scheme=light]{color-scheme:light}[data-dot-scheme=dark]{color-scheme:dark}.dot-theme-root{-webkit-user-select:none;user-select:none}.dot-theme-root :is(input,textarea){-webkit-user-select:text;user-select:text}}@keyframes dot-skeleton-pulse{0%,to{opacity:1}50%{opacity:.4}}.dot-image{max-width:100%;display:block}.dot-list>.dot-list-item:not(:last-child){border-bottom:1px solid var(--dot-list-divider)}.dot-scroll::-webkit-scrollbar{display:none}.dot-scroll>*{scroll-snap-align:var(--dot-scroll-snap);flex:none;width:min-content}.dot-scroll .dot-image{max-width:none}.dot-bar-top:not(:not(.dot-toast)~*){margin-top:calc(-1 * var(--dot-view-top,0px))}.dot-bar-bottom:not(:has(~:not(.dot-toast))){margin-bottom:calc(-1 * var(--dot-view-bottom,0px) - env(safe-area-inset-bottom,0px))}.dot-disclosure>summary{list-style:none}.dot-disclosure>summary::-webkit-details-marker{display:none}.dot-steps>.dot-step:last-child .dot-step-line{display:none}.dot-sheet-backdrop{transition:opacity .2s}.dot-sheet{transition:transform .2s ease-out}.dot-sheet-backdrop:is([data-starting-style],[data-ending-style]){opacity:0}.dot-sheet:is([data-starting-style],[data-ending-style]){transform:translateY(100%)}.dot-prose>.dot-heading{margin-block-start:.5em}.dot-prose>.dot-heading:first-child{margin-block-start:0}
package/dist/theme.d.ts CHANGED
@@ -4,6 +4,7 @@ import { ColorScheme, PageColors, Theme, ThemeComponents, ThemeElevation, ThemeS
4
4
  export interface ResolvedColors {
5
5
  primary: string;
6
6
  onPrimary: string;
7
+ primaryText: string;
7
8
  background: string;
8
9
  surface: string;
9
10
  text: string;
package/dist/theme.js CHANGED
@@ -72,10 +72,13 @@ function resolveTheme(theme, systemScheme, pageColors = {}) {
72
72
  const text = ownColor("text") ?? readableText(defaults.text, background);
73
73
  let subtle = defaults.subtle;
74
74
  if (!onDashBackground) subtle = quieterText(text, background, surface);
75
- const primary = ownColor("primary") ?? defaults.primary;
75
+ const { primary, onPrimary } = primaryAndOnPrimary(ownColor("primary") ?? defaults.primary, surface, text, scheme);
76
+ const primaryReads = contrast(primary, background) >= READABLE_CONTRAST && contrast(primary, surface) >= READABLE_CONTRAST;
76
77
  const colors = {
77
78
  ...defaults,
78
- ...primaryAndOnPrimary(primary, surface, text, scheme),
79
+ primary,
80
+ onPrimary,
81
+ primaryText: primaryReads ? primary : text,
79
82
  background,
80
83
  surface,
81
84
  text,
@@ -116,6 +119,7 @@ function scaledFontSize(scale) {
116
119
  function resolveGradients(gradients, colors, customColors) {
117
120
  const resolved = {};
118
121
  for (const [name, gradient] of Object.entries(gradients)) {
122
+ if (!gradient) continue;
119
123
  const from = themeColor(gradient.from, colors, customColors);
120
124
  const to = themeColor(gradient.to, colors, customColors);
121
125
  const direction = GRADIENT_DIRECTIONS[gradient.direction ?? "down"] ?? GRADIENT_DIRECTIONS.down;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monimesl/dot-renderer-react",
3
- "version": "1.0.0-beta.8",
3
+ "version": "1.0.0-beta.9",
4
4
  "description": "React renderer for Dot webviews",
5
5
  "type": "module",
6
6
  "repository": {
@@ -29,8 +29,8 @@
29
29
  "react": "19.3.0",
30
30
  "react-dom": "19.3.0",
31
31
  "uqr": "^0.1.3",
32
- "@monimesl/dot-core": "1.0.0-beta.8",
33
- "@monimesl/dot-webview": "1.0.0-beta.8"
32
+ "@monimesl/dot-core": "1.0.0-beta.9",
33
+ "@monimesl/dot-webview": "1.0.0-beta.9"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@base-ui/react": "1.8.0",
@@ -46,7 +46,7 @@
46
46
  "tsdown": "^0.23.0",
47
47
  "typescript": "^5.9.3",
48
48
  "vitest": "^5.0.1",
49
- "@monimesl/dot-service": "1.0.0-beta.8"
49
+ "@monimesl/dot-service": "1.0.0-beta.9"
50
50
  },
51
51
  "publishConfig": {
52
52
  "access": "public"