@monimesl/dot-renderer-react 1.0.0-beta.1 → 1.0.0-beta.11

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 (58) hide show
  1. package/README.md +3 -3
  2. package/dist/components/amount-input.js +1 -1
  3. package/dist/components/avatar.js +1 -1
  4. package/dist/components/box.js +9 -3
  5. package/dist/components/button.js +64 -14
  6. package/dist/components/carousel.js +65 -50
  7. package/dist/components/chip.js +4 -5
  8. package/dist/components/code-input.js +1 -1
  9. package/dist/components/detail-row.js +5 -5
  10. package/dist/components/disclosure.js +11 -6
  11. package/dist/components/empty-state.js +7 -6
  12. package/dist/components/feedback.js +7 -7
  13. package/dist/components/field-box.js +1 -1
  14. package/dist/components/field.js +5 -4
  15. package/dist/components/footer.js +6 -11
  16. package/dist/components/grid.js +25 -15
  17. package/dist/components/hidden-view.js +5 -0
  18. package/dist/components/icon-button.js +1 -1
  19. package/dist/components/icon.js +32 -5
  20. package/dist/components/index.d.ts +2 -1
  21. package/dist/components/index.js +3 -1
  22. package/dist/components/input.js +1 -1
  23. package/dist/components/link.js +11 -3
  24. package/dist/components/list.js +62 -33
  25. package/dist/components/option.js +6 -5
  26. package/dist/components/padding.js +9 -10
  27. package/dist/components/pinned-bar.js +29 -0
  28. package/dist/components/pinned.d.ts +6 -0
  29. package/dist/components/pinned.js +26 -0
  30. package/dist/components/pressable.js +3 -1
  31. package/dist/components/prose.js +23 -20
  32. package/dist/components/rich-text.js +30 -18
  33. package/dist/components/scroll.js +33 -10
  34. package/dist/components/select.js +1 -1
  35. package/dist/components/sheet.js +55 -46
  36. package/dist/components/side-room.js +5 -0
  37. package/dist/components/skeleton.js +1 -1
  38. package/dist/components/slider.js +3 -3
  39. package/dist/components/spinner.js +1 -1
  40. package/dist/components/stack.js +12 -8
  41. package/dist/components/stepper.js +4 -5
  42. package/dist/components/steps.js +17 -14
  43. package/dist/components/tabs.js +74 -27
  44. package/dist/components/text.js +12 -14
  45. package/dist/components/toast.js +3 -3
  46. package/dist/components/toggles.js +14 -11
  47. package/dist/components/tokens.d.ts +16 -0
  48. package/dist/components/tokens.js +26 -1
  49. package/dist/components/view.js +40 -18
  50. package/dist/index.d.ts +2 -1
  51. package/dist/index.js +2 -1
  52. package/dist/page-root.js +9 -3
  53. package/dist/pull-to-refresh.js +1 -1
  54. package/dist/root.js +4 -3
  55. package/dist/styles.css +1 -1
  56. package/dist/theme.d.ts +12 -1
  57. package/dist/theme.js +85 -15
  58. package/package.json +4 -4
@@ -1,9 +1,9 @@
1
+ import { SELECTABLE, WEIGHTS } from "./tokens.js";
1
2
  import { useTheme } from "../theme.js";
2
- import { FONT_SIZE, SELECTABLE, WEIGHTS } from "./tokens.js";
3
3
  import { paddingStyle } from "./padding.js";
4
- import { isTone, toneColor } from "./tones.js";
4
+ import { textToneColor } from "./box.js";
5
5
  import { InsideProse } from "./prose-context.js";
6
- import { customColor } from "./box.js";
6
+ import { DEFAULT_VARIANTS } from "@monimesl/dot-core";
7
7
  import { createContext, useContext } from "react";
8
8
  import { jsx } from "react/jsx-runtime";
9
9
  //#region src/components/text.tsx
@@ -50,10 +50,12 @@ const DECORATION = {
50
50
  underline: "underline",
51
51
  strikethrough: "line-through"
52
52
  };
53
- const Text = ({ variant = "body", 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 = {
@@ -61,16 +63,17 @@ const Text = ({ variant = "body", tone, weight, align, maxLines, decoration, num
61
63
  marginInline: 0,
62
64
  marginBlockEnd: 0,
63
65
  marginBlockStart: inProse && heading ? void 0 : 0,
64
- fontSize: FONT_SIZE[variant],
65
- fontWeight: WEIGHTS[weight ?? defaultWeight],
66
+ fontSize: theme.fontSize[variant],
67
+ fontFamily: heading ? theme.headingFontFamily : void 0,
68
+ fontWeight: WEIGHTS[weight ?? (heading ? theme.headingWeight : void 0) ?? defaultWeight],
66
69
  fontStyle: italic ? "italic" : void 0,
67
70
  letterSpacing: variant === "display" ? "-0.02em" : "normal",
68
- color: tone ? textColor(tone, theme) : "inherit",
71
+ color: tone ? textToneColor(tone, theme) : "inherit",
69
72
  textAlign: align,
70
73
  textDecorationLine: decoration ? DECORATION[decoration] : void 0,
71
74
  fontVariantNumeric: numeric ? "tabular-nums" : void 0,
72
75
  ...selectable ? SELECTABLE : void 0,
73
- ...paddingStyle(padding)
76
+ ...paddingStyle(padding, theme.space)
74
77
  };
75
78
  if (!insideText) {
76
79
  style.lineHeight = lineHeight + LEADING_CHANGE[leading ?? defaultLeading];
@@ -98,10 +101,5 @@ const Text = ({ variant = "body", tone, weight, align, maxLines, decoration, num
98
101
  children: content
99
102
  });
100
103
  };
101
- function textColor(tone, theme) {
102
- if (isTone(tone)) return toneColor(theme.colors, tone);
103
- if (tone === "onPrimary") return theme.colors.onPrimary;
104
- return customColor(tone, theme);
105
- }
106
104
  //#endregion
107
- export { Text };
105
+ export { Text$1 as Text };
@@ -1,5 +1,5 @@
1
+ import { ICON_SIZE, SPACE } from "./tokens.js";
1
2
  import { useTheme } from "../theme.js";
2
- import { FONT_SIZE, ICON_SIZE, SPACE } from "./tokens.js";
3
3
  import { IconSvg } from "./icon-svg.js";
4
4
  import { STATUS_ARTWORK } from "./tones.js";
5
5
  import { createEvent } from "@monimesl/dot-core";
@@ -7,7 +7,7 @@ import { useEffect, useEffectEvent, useState } from "react";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  //#region src/components/toast.tsx
9
9
  const Toast$1 = ({ message, tone = "default", duration = 2500, onHide }) => {
10
- const { colors, radius } = useTheme();
10
+ const { colors, radius, fontSize } = useTheme();
11
11
  const [hidden, setHidden] = useState(false);
12
12
  const [previousMessage, setPreviousMessage] = useState(message);
13
13
  if (message !== previousMessage) {
@@ -42,7 +42,7 @@ const Toast$1 = ({ message, tone = "default", duration = 2500, onHide }) => {
42
42
  borderRadius: radius,
43
43
  background: colors.primary,
44
44
  color: colors.onPrimary,
45
- fontSize: FONT_SIZE.body,
45
+ fontSize: fontSize.body,
46
46
  boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2)"
47
47
  },
48
48
  children: [tone === "default" ? null : /* @__PURE__ */ jsx(IconSvg, {
@@ -1,5 +1,5 @@
1
+ import { CENTERED, ICON_SIZE, SPACE, pressLook } from "./tokens.js";
1
2
  import { useTheme } from "../theme.js";
2
- import { CENTERED, DISABLED_OPACITY, FONT_SIZE, ICON_SIZE, SPACE } from "./tokens.js";
3
3
  import { FieldLabel } from "./field.js";
4
4
  import { CHECK_ARTWORK } from "./icon-artwork.js";
5
5
  import { IconSvg } from "./icon-svg.js";
@@ -14,7 +14,8 @@ import { createEvent } from "@monimesl/dot-core";
14
14
  import { useId } from "react";
15
15
  import { jsx, jsxs } from "react/jsx-runtime";
16
16
  //#region src/components/toggles.tsx
17
- function rowStyle(colors, disabled) {
17
+ function rowStyle(theme, disabled) {
18
+ const { colors, fontSize } = theme;
18
19
  return {
19
20
  display: "flex",
20
21
  alignItems: "center",
@@ -24,10 +25,9 @@ function rowStyle(colors, disabled) {
24
25
  border: "none",
25
26
  background: "transparent",
26
27
  color: colors.text,
27
- fontSize: FONT_SIZE.body,
28
+ fontSize: fontSize.body,
28
29
  textAlign: "start",
29
- opacity: disabled ? DISABLED_OPACITY : 1,
30
- cursor: disabled ? "default" : "pointer"
30
+ ...pressLook(disabled)
31
31
  };
32
32
  }
33
33
  function ChoiceMark({ round, checked, children }) {
@@ -47,14 +47,15 @@ function ChoiceMark({ round, checked, children }) {
47
47
  });
48
48
  }
49
49
  const Switch$1 = ({ checked = false, label, disabled, onChange }) => {
50
- const { colors } = useTheme();
50
+ const theme = useTheme();
51
+ const { colors } = theme;
51
52
  return /* @__PURE__ */ jsxs(SwitchRoot, {
52
53
  className: "dot-switch",
53
54
  checked,
54
55
  disabled,
55
56
  onCheckedChange: (next) => onChange?.(createEvent("change", { checked: next })),
56
57
  style: {
57
- ...rowStyle(colors, disabled),
58
+ ...rowStyle(theme, disabled),
58
59
  justifyContent: "space-between"
59
60
  },
60
61
  children: [label ? /* @__PURE__ */ jsx("span", { children: label }) : null, /* @__PURE__ */ jsx("span", {
@@ -81,13 +82,14 @@ const Switch$1 = ({ checked = false, label, disabled, onChange }) => {
81
82
  });
82
83
  };
83
84
  const Checkbox$1 = ({ checked = false, label, disabled, onChange }) => {
84
- const { colors } = useTheme();
85
+ const theme = useTheme();
86
+ const { colors } = theme;
85
87
  return /* @__PURE__ */ jsxs(CheckboxRoot, {
86
88
  className: "dot-checkbox",
87
89
  checked,
88
90
  disabled,
89
91
  onCheckedChange: (next) => onChange?.(createEvent("change", { checked: next })),
90
- style: rowStyle(colors, disabled),
92
+ style: rowStyle(theme, disabled),
91
93
  children: [/* @__PURE__ */ jsx(ChoiceMark, {
92
94
  round: false,
93
95
  checked,
@@ -122,13 +124,14 @@ const RadioGroup$1 = ({ value, label, disabled = false, onChange, children }) =>
122
124
  });
123
125
  };
124
126
  const Radio$1 = ({ value, label, disabled }) => {
125
- const { colors } = useTheme();
127
+ const theme = useTheme();
128
+ const { colors } = theme;
126
129
  if (value === void 0) return null;
127
130
  return /* @__PURE__ */ jsx(RadioRoot, {
128
131
  className: "dot-radio",
129
132
  value,
130
133
  disabled,
131
- style: (state) => rowStyle(colors, state.disabled),
134
+ style: (state) => rowStyle(theme, state.disabled),
132
135
  render: (props, state) => /* @__PURE__ */ jsxs("span", {
133
136
  ...props,
134
137
  children: [/* @__PURE__ */ jsx(ChoiceMark, {
@@ -0,0 +1,16 @@
1
+ //#region src/components/tokens.d.ts
2
+ export declare const SPACE: {
3
+ readonly none: 0;
4
+ readonly small: 4;
5
+ readonly medium: 8;
6
+ readonly large: 16;
7
+ };
8
+ export declare const FONT_SIZE: {
9
+ readonly caption: 13;
10
+ readonly body: 15;
11
+ readonly lead: 17;
12
+ readonly subtitle: 17;
13
+ readonly title: 20;
14
+ readonly display: 32;
15
+ };
16
+ //#endregion
@@ -20,6 +20,31 @@ const WEIGHTS = {
20
20
  };
21
21
  const CARD_RADIUS_SCALE = 1.5;
22
22
  const DISABLED_OPACITY = .6;
23
+ function pressLook(disabled) {
24
+ return {
25
+ opacity: disabled ? DISABLED_OPACITY : 1,
26
+ cursor: disabled ? "default" : "pointer"
27
+ };
28
+ }
29
+ const SHADOWS = {
30
+ light: {
31
+ low: "0 1px 2px rgba(0, 0, 0, 0.06), 0 2px 8px rgba(0, 0, 0, 0.06)",
32
+ high: "0 4px 12px rgba(0, 0, 0, 0.08), 0 12px 32px rgba(0, 0, 0, 0.14)"
33
+ },
34
+ dark: {
35
+ low: "0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3)",
36
+ high: "0 4px 12px rgba(0, 0, 0, 0.5), 0 12px 32px rgba(0, 0, 0, 0.5)"
37
+ }
38
+ };
39
+ const SHADOW_REACH = {
40
+ top: 20,
41
+ sides: 32,
42
+ bottom: 44
43
+ };
44
+ const SCROLL_SHADOW_ROOM = {
45
+ top: 6,
46
+ bottom: 10
47
+ };
23
48
  const CENTERED = {
24
49
  display: "flex",
25
50
  alignItems: "center",
@@ -46,4 +71,4 @@ const SELECTABLE = {
46
71
  WebkitUserSelect: "text"
47
72
  };
48
73
  //#endregion
49
- export { CARD_RADIUS_SCALE, CENTERED, DISABLED_OPACITY, FONT_SIZE, ICON_SIZE, PILL_STYLE, SELECTABLE, SPACE, WEIGHTS };
74
+ export { CARD_RADIUS_SCALE, CENTERED, DISABLED_OPACITY, FONT_SIZE, ICON_SIZE, PILL_STYLE, SCROLL_SHADOW_ROOM, SELECTABLE, SHADOWS, SHADOW_REACH, SPACE, WEIGHTS, pressLook };
@@ -1,11 +1,12 @@
1
+ import { DISABLED_OPACITY, SHADOWS } from "./tokens.js";
1
2
  import { useTheme } from "../theme.js";
2
- import { SPACE } from "./tokens.js";
3
3
  import { insets, paddingStyle } from "./padding.js";
4
- import { Footer as Footer$1 } from "./footer.js";
5
- import { pressable } from "./pressable.js";
4
+ import { SideRoomContext } from "./side-room.js";
6
5
  import { boxStyle } from "./box.js";
7
- import { createEvent } from "@monimesl/dot-core";
8
- import { Children, isValidElement } from "react";
6
+ import { pressable } from "./pressable.js";
7
+ import { HiddenViewContext } from "./hidden-view.js";
8
+ import { DEFAULT_VARIANTS, createEvent } from "@monimesl/dot-core";
9
+ import { useContext } from "react";
9
10
  import { jsx } from "react/jsx-runtime";
10
11
  //#region src/components/view.tsx
11
12
  const ALIGN = {
@@ -25,37 +26,58 @@ const CARD_LOOK = {
25
26
  border: "subtle",
26
27
  radius: "large"
27
28
  };
29
+ const RAISED_CARD_LOOK = {
30
+ ...CARD_LOOK,
31
+ border: "none"
32
+ };
28
33
  const View$1 = (props) => {
29
- const { direction = "column", gap = "medium", align = "stretch", justify = "start", wrap = false, grow = false, variant = "plain", onTap, children } = props;
34
+ const { id, hidden = false, disabled = false, direction = "column", gap = "medium", align = "stretch", justify = "start", wrap = false, grow = false, variant = DEFAULT_VARIANTS.View, onTap, children } = props;
30
35
  const theme = useTheme();
36
+ const sideRoomAround = useContext(SideRoomContext);
37
+ const hiddenAround = useContext(HiddenViewContext);
31
38
  const card = variant === "card";
39
+ const raisedCard = card && theme.elevation === "raised" && props.elevation !== "none";
40
+ let cardLook;
41
+ if (card) cardLook = raisedCard ? RAISED_CARD_LOOK : CARD_LOOK;
42
+ const elevation = props.elevation ?? (raisedCard ? "low" : "none");
43
+ const padding = paddingStyle(props, theme.space, card ? insets(theme.space.large) : void 0);
32
44
  const style = {
33
- display: "flex",
45
+ display: hidden ? "none" : "flex",
34
46
  flexDirection: direction,
35
47
  alignItems: ALIGN[align],
36
48
  justifyContent: JUSTIFY[justify],
37
49
  flexWrap: wrap ? "wrap" : "nowrap",
38
- gap: SPACE[gap],
39
- ...paddingStyle(props, card ? insets(SPACE.large) : void 0),
40
- ...boxStyle(props, theme, card ? CARD_LOOK : void 0),
41
- cursor: onTap ? "pointer" : void 0
50
+ gap: theme.space[gap],
51
+ ...padding,
52
+ "--dot-view-top": `${padding.paddingTop}px`,
53
+ "--dot-view-bottom": `${padding.paddingBottom}px`,
54
+ "--dot-view-start": `${padding.paddingInlineStart}px`,
55
+ "--dot-view-end": `${padding.paddingInlineEnd}px`,
56
+ ...boxStyle(props, theme, cardLook),
57
+ opacity: disabled ? DISABLED_OPACITY : void 0,
58
+ cursor: onTap && !disabled ? "pointer" : void 0
42
59
  };
43
- if (endsWithFooter(children)) style.paddingBottom = 0;
60
+ if (elevation !== "none") style.boxShadow = SHADOWS[theme.scheme][elevation];
44
61
  if (grow) {
45
62
  style.flexGrow = 1;
46
63
  style.minWidth = 0;
47
64
  }
48
- const press = onTap ? pressable(() => onTap(createEvent("tap"))) : void 0;
65
+ const press = onTap && !disabled ? pressable(() => onTap(createEvent("tap"))) : void 0;
49
66
  return /* @__PURE__ */ jsx("div", {
50
67
  className: "dot-view",
68
+ "data-dot-view": id,
69
+ hidden,
70
+ inert: disabled,
51
71
  style,
52
72
  ...press,
53
- children
73
+ children: /* @__PURE__ */ jsx(HiddenViewContext.Provider, {
74
+ value: hiddenAround || hidden,
75
+ children: /* @__PURE__ */ jsx(SideRoomContext.Provider, {
76
+ value: sideRoomAround && direction !== "row",
77
+ children
78
+ })
79
+ })
54
80
  });
55
81
  };
56
- function endsWithFooter(children) {
57
- const last = Children.toArray(children).at(-1);
58
- return isValidElement(last) && last.type === Footer$1;
59
- }
60
82
  //#endregion
61
83
  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/page-root.js CHANGED
@@ -1,18 +1,24 @@
1
- import { toList, toText } from "@monimesl/dot-core";
1
+ import { useTheme } from "./theme.js";
2
+ import { toList, toText, withComponentDefaults } from "@monimesl/dot-core";
2
3
  import { Fragment, createElement, useMemo, useSyncExternalStore } from "react";
3
4
  //#region src/page-root.tsx
4
5
  function PageRoot({ page, components }) {
5
6
  const state = useSyncExternalStore(page.subscribe, page.getState);
7
+ const themeComponents = useTheme().components;
6
8
  const context = useMemo(() => ({
7
9
  h(type, props, ...children) {
8
10
  const component = components[type];
9
11
  if (!component) throw new Error(`The renderer has no component for <${type}>. Check the tag's name, or pass a component for it to the renderer.`);
10
- return createElement(component, props, ...children);
12
+ return createElement(component, withComponentDefaults(type, props, themeComponents), ...children);
11
13
  },
12
14
  text: toText,
13
15
  on: (handler, data) => page.on(handler, data),
14
16
  each: (items, render, key) => createElement(Fragment, null, toList(items).map((item, index) => createElement(Fragment, { key: key ? String(key(item, index)) : index }, ...render(item, index))))
15
- }), [page, components]);
17
+ }), [
18
+ page,
19
+ components,
20
+ themeComponents
21
+ ]);
16
22
  if (!state) return null;
17
23
  return page.viewRenderer(state, context);
18
24
  }
@@ -1,5 +1,5 @@
1
- import { useTheme } from "./theme.js";
2
1
  import { CENTERED } from "./components/tokens.js";
2
+ import { useTheme } from "./theme.js";
3
3
  import { LoadingIcon } from "./components/spinner.js";
4
4
  import { useEffect, useRef, useState, useSyncExternalStore } from "react";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
package/dist/root.js CHANGED
@@ -1,4 +1,4 @@
1
- import { FONT_FAMILY, ThemeContext, resolveTheme, useTheme } from "./theme.js";
1
+ import { ThemeContext, resolveTheme, useTheme } from "./theme.js";
2
2
  import { HOME_INDICATOR } from "./components/padding.js";
3
3
  import { PortalContainerContext } from "./portal.js";
4
4
  import { PageRoot } from "./page-root.js";
@@ -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, {
@@ -51,7 +52,7 @@ function withPageColors(theme, scheme, pageColors) {
51
52
  };
52
53
  }
53
54
  function DefaultSurface({ children }) {
54
- const { colors } = useTheme();
55
+ const { colors, fontFamily } = useTheme();
55
56
  return /* @__PURE__ */ jsx("div", {
56
57
  className: "dot-surface",
57
58
  style: {
@@ -62,7 +63,7 @@ function DefaultSurface({ children }) {
62
63
  flexDirection: "column",
63
64
  background: colors.background,
64
65
  color: colors.text,
65
- fontFamily: FONT_FAMILY
66
+ fontFamily
66
67
  },
67
68
  children
68
69
  });
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,[hidden])~*){margin-top:calc(-1 * var(--dot-view-top,0px))}.dot-bar-bottom:not(:has(~:not(.dot-toast,[hidden]))){margin-bottom:calc(-1 * var(--dot-view-bottom,0px) - env(safe-area-inset-bottom,0px))}html{overscroll-behavior-y: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}
package/dist/theme.d.ts CHANGED
@@ -1,8 +1,10 @@
1
- import { ColorScheme, PageColors, Theme, ThemeShape } from "@monimesl/dot-core";
1
+ import { FONT_SIZE, SPACE } from "./components/tokens.js";
2
+ import { ColorScheme, PageColors, Theme, ThemeComponents, ThemeElevation, ThemeShape, ThemeWeight } from "@monimesl/dot-core";
2
3
  //#region src/theme.d.ts
3
4
  export interface ResolvedColors {
4
5
  primary: string;
5
6
  onPrimary: string;
7
+ primaryText: string;
6
8
  background: string;
7
9
  surface: string;
8
10
  text: string;
@@ -20,6 +22,15 @@ export interface ResolvedTheme {
20
22
  customColors: Readonly<Record<string, string>>;
21
23
  shape: ThemeShape;
22
24
  radius: number;
25
+ fontFamily: string;
26
+ headingFontFamily: string;
27
+ headingWeight: ThemeWeight | undefined;
28
+ fontSize: Readonly<Record<keyof typeof FONT_SIZE, number>>;
29
+ components: ThemeComponents;
30
+ icons: Readonly<Record<string, string>>;
31
+ gradients: Readonly<Record<string, string>>;
32
+ space: Readonly<Record<keyof typeof SPACE, number>>;
33
+ elevation: ThemeElevation;
23
34
  }
24
35
  export declare function resolveTheme(theme: Theme, systemScheme: ColorScheme, pageColors?: PageColors): ResolvedTheme;
25
36
  export declare function useTheme(): ResolvedTheme;
package/dist/theme.js CHANGED
@@ -1,8 +1,29 @@
1
+ import { FONT_SIZE, SPACE } from "./components/tokens.js";
1
2
  import { darken, mix } from "./color.js";
2
3
  import { DEFAULT_BACKGROUND, READABLE_CONTRAST, contrast, isDark, pageBackground, textOn, themeScheme } from "@monimesl/dot-core";
3
4
  import { createContext, useContext } from "react";
4
5
  //#region src/theme.ts
5
- const FONT_FAMILY = "system-ui, sans-serif";
6
+ const DENSITY_SCALE = {
7
+ compact: .75,
8
+ regular: 1,
9
+ comfortable: 1.25
10
+ };
11
+ const TEXT_SCALE = {
12
+ small: .9,
13
+ regular: 1,
14
+ large: 1.1
15
+ };
16
+ const GRADIENT_DIRECTIONS = {
17
+ down: "to bottom",
18
+ right: "to right",
19
+ diagonal: "to bottom right"
20
+ };
21
+ const FONT_FAMILIES = {
22
+ system: "system-ui, sans-serif",
23
+ rounded: "ui-rounded, \"SF Pro Rounded\", system-ui, sans-serif",
24
+ serif: "ui-serif, \"New York\", Georgia, serif",
25
+ mono: "ui-monospace, \"SF Mono\", Menlo, monospace"
26
+ };
6
27
  const SHAPE_RADIUS = {
7
28
  square: 0,
8
29
  subtle: 4,
@@ -51,22 +72,71 @@ function resolveTheme(theme, systemScheme, pageColors = {}) {
51
72
  const text = ownColor("text") ?? readableText(defaults.text, background);
52
73
  let subtle = defaults.subtle;
53
74
  if (!onDashBackground) subtle = quieterText(text, background, surface);
54
- 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;
77
+ const colors = {
78
+ ...defaults,
79
+ primary,
80
+ onPrimary,
81
+ primaryText: primaryReads ? primary : text,
82
+ background,
83
+ surface,
84
+ text,
85
+ muted: ownColor("muted") ?? surfaces.muted,
86
+ border: ownColor("border") ?? surfaces.border,
87
+ subtle: ownColor("subtle") ?? subtle,
88
+ info: ownColor("info") ?? defaults.info,
89
+ success: ownColor("success") ?? defaults.success,
90
+ warning: ownColor("warning") ?? defaults.warning,
91
+ danger: ownColor("danger") ?? defaults.danger
92
+ };
93
+ const customColors = resolveCustomColors(themeColors.custom ?? {}, scheme);
94
+ const fontFamily = FONT_FAMILIES[theme.font ?? "system"] ?? FONT_FAMILIES.system;
95
+ let headingFontFamily = fontFamily;
96
+ if (theme.headingFont) headingFontFamily = FONT_FAMILIES[theme.headingFont] ?? fontFamily;
55
97
  return {
56
98
  scheme,
57
- colors: {
58
- ...defaults,
59
- ...primaryAndOnPrimary(primary, surface, text, scheme),
60
- background,
61
- surface,
62
- text,
63
- muted: ownColor("muted") ?? surfaces.muted,
64
- border: ownColor("border") ?? surfaces.border,
65
- subtle: ownColor("subtle") ?? subtle
66
- },
67
- customColors: resolveCustomColors(themeColors.custom ?? {}, scheme),
99
+ colors,
100
+ customColors,
68
101
  shape,
69
- radius: SHAPE_RADIUS[shape]
102
+ radius: SHAPE_RADIUS[shape],
103
+ fontFamily,
104
+ headingFontFamily,
105
+ headingWeight: theme.headingWeight,
106
+ fontSize: scaledFontSize(TEXT_SCALE[theme.textSize ?? "regular"] ?? 1),
107
+ components: theme.components ?? {},
108
+ icons: theme.icons ?? {},
109
+ gradients: resolveGradients(theme.gradients ?? {}, colors, customColors),
110
+ space: scaledSpace(DENSITY_SCALE[theme.density ?? "regular"] ?? 1),
111
+ elevation: theme.elevation === "raised" ? "raised" : "flat"
112
+ };
113
+ }
114
+ function scaledFontSize(scale) {
115
+ const sizes = { ...FONT_SIZE };
116
+ for (const variant of Object.keys(sizes)) sizes[variant] = Math.round(FONT_SIZE[variant] * scale);
117
+ return sizes;
118
+ }
119
+ function resolveGradients(gradients, colors, customColors) {
120
+ const resolved = {};
121
+ for (const [name, gradient] of Object.entries(gradients)) {
122
+ if (!gradient) continue;
123
+ const from = themeColor(gradient.from, colors, customColors);
124
+ const to = themeColor(gradient.to, colors, customColors);
125
+ const direction = GRADIENT_DIRECTIONS[gradient.direction ?? "down"] ?? GRADIENT_DIRECTIONS.down;
126
+ if (from && to) resolved[name] = `linear-gradient(${direction}, ${from}, ${to})`;
127
+ }
128
+ return resolved;
129
+ }
130
+ function themeColor(name, colors, customColors) {
131
+ if (Object.hasOwn(colors, name)) return colors[name];
132
+ if (Object.hasOwn(customColors, name)) return customColors[name];
133
+ }
134
+ function scaledSpace(scale) {
135
+ return {
136
+ none: SPACE.none,
137
+ small: Math.round(SPACE.small * scale),
138
+ medium: Math.round(SPACE.medium * scale),
139
+ large: Math.round(SPACE.large * scale)
70
140
  };
71
141
  }
72
142
  function surfacesOn(background) {
@@ -126,4 +196,4 @@ function useTheme() {
126
196
  return useContext(ThemeContext);
127
197
  }
128
198
  //#endregion
129
- export { FONT_FAMILY, ThemeContext, resolveTheme, useTheme };
199
+ export { ThemeContext, resolveTheme, useTheme };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monimesl/dot-renderer-react",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.11",
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-webview": "1.0.0-beta.1",
33
- "@monimesl/dot-core": "1.0.0-beta.1"
32
+ "@monimesl/dot-core": "1.0.0-beta.11",
33
+ "@monimesl/dot-webview": "1.0.0-beta.11"
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.1"
49
+ "@monimesl/dot-service": "1.0.0-beta.11"
50
50
  },
51
51
  "publishConfig": {
52
52
  "access": "public"