@drawbridge/components 0.1.4 → 0.1.6

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 (37) hide show
  1. package/README.md +6 -2
  2. package/dist/bridge/template-subdomain-gallery.js +3 -1
  3. package/dist/bridge/template-subdomain-gallery.mjs +3 -1
  4. package/dist/bridge/template-subdomain.js +8 -2
  5. package/dist/bridge/template-subdomain.mjs +8 -2
  6. package/dist/dashboard/card-task.js +13 -15
  7. package/dist/dashboard/card-task.mjs +13 -15
  8. package/dist/dashboard/drawer-subscription.js +2 -2
  9. package/dist/dashboard/drawer-subscription.mjs +2 -2
  10. package/dist/dashboard/form-action.js +70 -60
  11. package/dist/dashboard/form-action.mjs +71 -61
  12. package/dist/dashboard/form-organization-campaign-link.js +3 -1
  13. package/dist/dashboard/form-organization-campaign-link.mjs +3 -1
  14. package/dist/dashboard/form-organization-connection.js +2 -1
  15. package/dist/dashboard/form-organization-connection.mjs +2 -1
  16. package/dist/dashboard/lib/connection-controls.js +6 -2
  17. package/dist/dashboard/lib/connection-controls.mjs +4 -1
  18. package/dist/dashboard/recharts.js +5 -1
  19. package/dist/dashboard/recharts.mjs +5 -1
  20. package/dist/dashboard/steps.js +67 -10
  21. package/dist/dashboard/steps.mjs +66 -10
  22. package/dist/dashboard/table.js +10 -0
  23. package/dist/dashboard/table.mjs +10 -1
  24. package/dist/dashboard/template-dashboard.js +79 -55
  25. package/dist/dashboard/template-dashboard.mjs +78 -55
  26. package/dist/dashboard/template-socket.js +18 -5
  27. package/dist/dashboard/template-socket.mjs +18 -5
  28. package/dist/shared/lib/video.js +63 -0
  29. package/dist/shared/lib/video.mjs +37 -0
  30. package/dist/video.js +31 -8
  31. package/dist/video.mjs +31 -8
  32. package/package.json +13 -7
  33. package/types/dashboard/form-action.d.ts +5 -2
  34. package/types/dashboard/lib/connection-controls.d.ts +10 -0
  35. package/types/dashboard/recharts.d.ts +3 -0
  36. package/types/dashboard/table.d.ts +2 -0
  37. package/types/shared/lib/video.d.ts +9 -0
package/README.md CHANGED
@@ -4,12 +4,16 @@ The components for Drawbridge. Three folders: `shared/` renders on both the dash
4
4
 
5
5
  ## Work on a component
6
6
 
7
- npm run storybook # every component, every state, at http://localhost:6006
8
- npm run test-storybook # renders every story in Chromium; fails on an error
7
+ npm run app # every component, every state, at http://localhost:3003 (the family's ports: dashboard 3000, share 3001, website 3002)
8
+ npm run test:app # renders every story in Chromium; fails on an error
9
9
  npm test # exports resolve, every component has a story, unit tests
10
10
 
11
11
  A component without a `.stories.js` beside it fails `npm test`. One story per visible state.
12
12
 
13
+ ## The app on the web
14
+
15
+ `https://components.trydrawbridge.com` is this app, built from `main` on every push (`.do/app.yaml`, `Dockerfile`): `npm run app:build` makes the static site and `server.js` serves it behind the family's staging gate, the `BASIC_AUTH_USERNAME` and `BASIC_AUTH_PASSWORD` pair set on the DigitalOcean app, the same values that open the dev dashboard and website. Unset both and the site is public; `/health` is always open.
16
+
13
17
  ## See it in an app without publishing
14
18
 
15
19
  npm run dev # here: rebuilds dist on change
@@ -460,7 +460,9 @@ const TemplateSubdomainGallery = ({
460
460
  "div",
461
461
  {
462
462
  ...{
463
- className: "items-center absolute top-0 left-0 z-50 flex w-full px-3 py-5 lg:p-5 " + (length > 1 ? "flex" : "hidden")
463
+ // @story public-entry/view-page
464
+ // Above the skip buttons and holds no control: taps pass through to them.
465
+ className: "items-center absolute top-0 left-0 z-50 flex w-full px-3 py-5 lg:p-5 pointer-events-none " + (length > 1 ? "flex" : "hidden")
464
466
  },
465
467
  children: gallery.map(({ id }, key) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
466
468
  "div",
@@ -428,7 +428,9 @@ const TemplateSubdomainGallery = ({
428
428
  "div",
429
429
  {
430
430
  ...{
431
- className: "items-center absolute top-0 left-0 z-50 flex w-full px-3 py-5 lg:p-5 " + (length > 1 ? "flex" : "hidden")
431
+ // @story public-entry/view-page
432
+ // Above the skip buttons and holds no control: taps pass through to them.
433
+ className: "items-center absolute top-0 left-0 z-50 flex w-full px-3 py-5 lg:p-5 pointer-events-none " + (length > 1 ? "flex" : "hidden")
432
434
  },
433
435
  children: gallery.map(({ id }, key) => /* @__PURE__ */ jsx(
434
436
  "div",
@@ -122,7 +122,10 @@ const TemplateSubdomain = ({
122
122
  "div",
123
123
  {
124
124
  ...{
125
- className: "z-30 fixed top-0 left-0 right-1 lg:left-auto lg:right-1.5 lg:w-auto",
125
+ // @story public-entry/view-page
126
+ // Fixed over the top of the gallery: taps on its empty space (and the
127
+ // logo, a picture) pass through to the gallery; its buttons keep theirs.
128
+ className: "z-30 fixed top-0 left-0 right-1 lg:left-auto lg:right-1.5 lg:w-auto pointer-events-none [&_a]:pointer-events-auto [&_button]:pointer-events-auto",
126
129
  style: {
127
130
  ...gallery.length > 1 && {
128
131
  "--controls-padding-top": controlPaddingTop + "px"
@@ -216,7 +219,10 @@ const TemplateSubdomain = ({
216
219
  "div",
217
220
  {
218
221
  ...{
219
- className: "lg:hidden absolute bottom-2 left-0 w-full px-6 pb-4 pt-12 lg:pt-20 text-center z-20 space-y-4",
222
+ // @story public-entry/view-page
223
+ // Sits over the lower gallery, above its skip buttons: taps pass through
224
+ // to the gallery except on this block's own buttons and links.
225
+ className: "lg:hidden absolute bottom-2 left-0 w-full px-6 pb-4 pt-12 lg:pt-20 text-center z-20 space-y-4 pointer-events-none [&_a]:pointer-events-auto [&_button]:pointer-events-auto",
220
226
  style: {
221
227
  ...(style == null ? void 0 : style.text) && {
222
228
  color: (_a2 = style.text.color) == null ? void 0 : _a2.rgb
@@ -90,7 +90,10 @@ const TemplateSubdomain = ({
90
90
  "div",
91
91
  {
92
92
  ...{
93
- className: "z-30 fixed top-0 left-0 right-1 lg:left-auto lg:right-1.5 lg:w-auto",
93
+ // @story public-entry/view-page
94
+ // Fixed over the top of the gallery: taps on its empty space (and the
95
+ // logo, a picture) pass through to the gallery; its buttons keep theirs.
96
+ className: "z-30 fixed top-0 left-0 right-1 lg:left-auto lg:right-1.5 lg:w-auto pointer-events-none [&_a]:pointer-events-auto [&_button]:pointer-events-auto",
94
97
  style: {
95
98
  ...gallery.length > 1 && {
96
99
  "--controls-padding-top": controlPaddingTop + "px"
@@ -184,7 +187,10 @@ const TemplateSubdomain = ({
184
187
  "div",
185
188
  {
186
189
  ...{
187
- className: "lg:hidden absolute bottom-2 left-0 w-full px-6 pb-4 pt-12 lg:pt-20 text-center z-20 space-y-4",
190
+ // @story public-entry/view-page
191
+ // Sits over the lower gallery, above its skip buttons: taps pass through
192
+ // to the gallery except on this block's own buttons and links.
193
+ className: "lg:hidden absolute bottom-2 left-0 w-full px-6 pb-4 pt-12 lg:pt-20 text-center z-20 space-y-4 pointer-events-none [&_a]:pointer-events-auto [&_button]:pointer-events-auto",
188
194
  style: {
189
195
  ...(style == null ? void 0 : style.text) && {
190
196
  color: (_a2 = style.text.color) == null ? void 0 : _a2.rgb
@@ -61,27 +61,25 @@ const CardTask = ({
61
61
  classes.push(className);
62
62
  }
63
63
  const button = () => {
64
- const { element = import_link.default, text, ...props } = rest == null ? void 0 : rest.button;
64
+ const { element = import_link.default, onClick, text, ...props } = rest == null ? void 0 : rest.button;
65
+ const close = () => {
66
+ modal.close();
67
+ queryClient.setQueryData(
68
+ ["drawer"],
69
+ (data) => ({
70
+ ...data,
71
+ active: false
72
+ })
73
+ );
74
+ };
65
75
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
66
76
  import_button.default,
67
77
  {
68
78
  ...{
69
79
  element,
70
- onClick: () => {
71
- modal.close();
72
- queryClient.setQueryData(
73
- ["drawer"],
74
- (data) => ({
75
- ...data,
76
- active: false
77
- })
78
- );
79
- if (props.onClick) {
80
- props.onClick();
81
- }
82
- },
83
80
  size: "small",
84
- ...props
81
+ ...props,
82
+ onClick: onClick || close
85
83
  },
86
84
  children: text
87
85
  }
@@ -29,27 +29,25 @@ const CardTask = ({
29
29
  classes.push(className);
30
30
  }
31
31
  const button = () => {
32
- const { element = Link, text, ...props } = rest == null ? void 0 : rest.button;
32
+ const { element = Link, onClick, text, ...props } = rest == null ? void 0 : rest.button;
33
+ const close = () => {
34
+ modal.close();
35
+ queryClient.setQueryData(
36
+ ["drawer"],
37
+ (data) => ({
38
+ ...data,
39
+ active: false
40
+ })
41
+ );
42
+ };
33
43
  return /* @__PURE__ */ jsx(
34
44
  Button,
35
45
  {
36
46
  ...{
37
47
  element,
38
- onClick: () => {
39
- modal.close();
40
- queryClient.setQueryData(
41
- ["drawer"],
42
- (data) => ({
43
- ...data,
44
- active: false
45
- })
46
- );
47
- if (props.onClick) {
48
- props.onClick();
49
- }
50
- },
51
48
  size: "small",
52
- ...props
49
+ ...props,
50
+ onClick: onClick || close
53
51
  },
54
52
  children: text
55
53
  }
@@ -294,9 +294,9 @@ const DrawerSubscriptionSummary = ({
294
294
  "Subtotal",
295
295
  "$" + (0, import_drawbridge_utils.formatNumber)((first.subtotal || 0) / 100, 2)
296
296
  ),
297
- renderLine(
297
+ first.discount > 0 && renderLine(
298
298
  "Discount",
299
- "-$" + (0, import_drawbridge_utils.formatNumber)((first.discount || 0) / 100, 2)
299
+ "-$" + (0, import_drawbridge_utils.formatNumber)(first.discount / 100, 2)
300
300
  ),
301
301
  renderLine(
302
302
  "Tax",
@@ -262,9 +262,9 @@ const DrawerSubscriptionSummary = ({
262
262
  "Subtotal",
263
263
  "$" + formatNumber((first.subtotal || 0) / 100, 2)
264
264
  ),
265
- renderLine(
265
+ first.discount > 0 && renderLine(
266
266
  "Discount",
267
- "-$" + formatNumber((first.discount || 0) / 100, 2)
267
+ "-$" + formatNumber(first.discount / 100, 2)
268
268
  ),
269
269
  renderLine(
270
270
  "Tax",
@@ -42,78 +42,88 @@ const FormAction = ({
42
42
  children,
43
43
  callback,
44
44
  className = "space-y-4",
45
+ defaultValues,
45
46
  disabled,
46
47
  fineprint,
47
48
  onError,
48
49
  onSuccess
49
50
  }) => {
50
- const { handleSubmit } = (0, import_form.useForm)();
51
+ const methods = (0, import_form.useForm)({
52
+ defaultValues,
53
+ guard: false
54
+ });
51
55
  const mutation = (0, import_react_query.useMutation)({
52
56
  mutationFn: callback,
53
57
  onSuccess,
54
58
  onError,
55
59
  retry: false
56
60
  });
57
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
58
- import_form2.default,
61
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
62
+ import_form.FormProvider,
59
63
  {
60
- ...{
61
- className: "w-full space-y-6",
62
- onSubmit: handleSubmit(() => mutation.mutate())
63
- },
64
- children: [
65
- children && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
66
- "div",
67
- {
68
- ...{
69
- className
70
- },
71
- children
72
- }
73
- ),
74
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
75
- import_fieldset.default,
76
- {
77
- ...{
78
- // Side by side once there is something to cancel, so the pair reads as a
79
- // prompt; equal grid columns, as a Button never shrinks in a flex row.
80
- className: cancel ? "grid grid-cols-2 gap-4" : "space-y-6",
81
- disabled: disabled || mutation.isPending
82
- },
83
- children: [
84
- cancel && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
85
- import_button.default,
86
- {
87
- ...{
88
- onClick: cancel.onClick,
89
- variant: "secondary"
90
- },
91
- children: cancel.action || "Cancel"
92
- }
93
- ),
94
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
95
- import_button.default,
96
- {
97
- ...{
98
- loading: mutation == null ? void 0 : mutation.isPending,
99
- type: "submit"
100
- },
101
- children: action
102
- }
103
- )
104
- ]
105
- }
106
- ),
107
- fineprint && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
108
- "div",
109
- {
110
- ...{
111
- className: "text-sm text-center"
112
- },
113
- children: fineprint
114
- }
115
- )
116
- ]
64
+ ...methods,
65
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
66
+ import_form2.default,
67
+ {
68
+ ...{
69
+ className: "w-full space-y-6",
70
+ onSubmit: methods.handleSubmit((values) => mutation.mutate(values))
71
+ },
72
+ children: [
73
+ children && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
74
+ "div",
75
+ {
76
+ ...{
77
+ className
78
+ },
79
+ children
80
+ }
81
+ ),
82
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
83
+ import_fieldset.default,
84
+ {
85
+ ...{
86
+ // Side by side once there is something to cancel, so the pair reads as a
87
+ // prompt; equal grid columns, as a Button never shrinks in a flex row.
88
+ className: cancel ? "grid grid-cols-2 gap-4" : "space-y-6",
89
+ disabled: disabled || mutation.isPending
90
+ },
91
+ children: [
92
+ cancel && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
93
+ import_button.default,
94
+ {
95
+ ...{
96
+ onClick: cancel.onClick,
97
+ variant: "secondary"
98
+ },
99
+ children: cancel.action || "Cancel"
100
+ }
101
+ ),
102
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
103
+ import_button.default,
104
+ {
105
+ ...{
106
+ loading: mutation == null ? void 0 : mutation.isPending,
107
+ type: "submit"
108
+ },
109
+ children: action
110
+ }
111
+ )
112
+ ]
113
+ }
114
+ ),
115
+ fineprint && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
116
+ "div",
117
+ {
118
+ ...{
119
+ className: "text-sm text-center"
120
+ },
121
+ children: fineprint
122
+ }
123
+ )
124
+ ]
125
+ }
126
+ )
117
127
  }
118
128
  );
119
129
  };
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useForm } from "../shared/lib/form.mjs";
2
+ import { FormProvider, useForm } from "../shared/lib/form.mjs";
3
3
  import { useMutation } from "@tanstack/react-query";
4
4
  import Form from "../shared/form.mjs";
5
5
  import Fieldset from "../shared/fieldset.mjs";
@@ -10,78 +10,88 @@ const FormAction = ({
10
10
  children,
11
11
  callback,
12
12
  className = "space-y-4",
13
+ defaultValues,
13
14
  disabled,
14
15
  fineprint,
15
16
  onError,
16
17
  onSuccess
17
18
  }) => {
18
- const { handleSubmit } = useForm();
19
+ const methods = useForm({
20
+ defaultValues,
21
+ guard: false
22
+ });
19
23
  const mutation = useMutation({
20
24
  mutationFn: callback,
21
25
  onSuccess,
22
26
  onError,
23
27
  retry: false
24
28
  });
25
- return /* @__PURE__ */ jsxs(
26
- Form,
29
+ return /* @__PURE__ */ jsx(
30
+ FormProvider,
27
31
  {
28
- ...{
29
- className: "w-full space-y-6",
30
- onSubmit: handleSubmit(() => mutation.mutate())
31
- },
32
- children: [
33
- children && /* @__PURE__ */ jsx(
34
- "div",
35
- {
36
- ...{
37
- className
38
- },
39
- children
40
- }
41
- ),
42
- /* @__PURE__ */ jsxs(
43
- Fieldset,
44
- {
45
- ...{
46
- // Side by side once there is something to cancel, so the pair reads as a
47
- // prompt; equal grid columns, as a Button never shrinks in a flex row.
48
- className: cancel ? "grid grid-cols-2 gap-4" : "space-y-6",
49
- disabled: disabled || mutation.isPending
50
- },
51
- children: [
52
- cancel && /* @__PURE__ */ jsx(
53
- Button,
54
- {
55
- ...{
56
- onClick: cancel.onClick,
57
- variant: "secondary"
58
- },
59
- children: cancel.action || "Cancel"
60
- }
61
- ),
62
- /* @__PURE__ */ jsx(
63
- Button,
64
- {
65
- ...{
66
- loading: mutation == null ? void 0 : mutation.isPending,
67
- type: "submit"
68
- },
69
- children: action
70
- }
71
- )
72
- ]
73
- }
74
- ),
75
- fineprint && /* @__PURE__ */ jsx(
76
- "div",
77
- {
78
- ...{
79
- className: "text-sm text-center"
80
- },
81
- children: fineprint
82
- }
83
- )
84
- ]
32
+ ...methods,
33
+ children: /* @__PURE__ */ jsxs(
34
+ Form,
35
+ {
36
+ ...{
37
+ className: "w-full space-y-6",
38
+ onSubmit: methods.handleSubmit((values) => mutation.mutate(values))
39
+ },
40
+ children: [
41
+ children && /* @__PURE__ */ jsx(
42
+ "div",
43
+ {
44
+ ...{
45
+ className
46
+ },
47
+ children
48
+ }
49
+ ),
50
+ /* @__PURE__ */ jsxs(
51
+ Fieldset,
52
+ {
53
+ ...{
54
+ // Side by side once there is something to cancel, so the pair reads as a
55
+ // prompt; equal grid columns, as a Button never shrinks in a flex row.
56
+ className: cancel ? "grid grid-cols-2 gap-4" : "space-y-6",
57
+ disabled: disabled || mutation.isPending
58
+ },
59
+ children: [
60
+ cancel && /* @__PURE__ */ jsx(
61
+ Button,
62
+ {
63
+ ...{
64
+ onClick: cancel.onClick,
65
+ variant: "secondary"
66
+ },
67
+ children: cancel.action || "Cancel"
68
+ }
69
+ ),
70
+ /* @__PURE__ */ jsx(
71
+ Button,
72
+ {
73
+ ...{
74
+ loading: mutation == null ? void 0 : mutation.isPending,
75
+ type: "submit"
76
+ },
77
+ children: action
78
+ }
79
+ )
80
+ ]
81
+ }
82
+ ),
83
+ fineprint && /* @__PURE__ */ jsx(
84
+ "div",
85
+ {
86
+ ...{
87
+ className: "text-sm text-center"
88
+ },
89
+ children: fineprint
90
+ }
91
+ )
92
+ ]
93
+ }
94
+ )
85
95
  }
86
96
  );
87
97
  };
@@ -78,7 +78,9 @@ const FormOrganizationCampaignLink = ({
78
78
  import_yup.default.object({
79
79
  asset: import_yup.default.object().label("Asset").nullable(),
80
80
  title: import_yup.default.string().label("Title").required().max(150),
81
- url: import_yup.default.string().label("Url").max(150).matches(import_drawbridge_utils.regex.url, "Url must start with https").required().url()
81
+ // @story links/create
82
+ // The server stores any length; a UTM-tagged URL passes 150 easily.
83
+ url: import_yup.default.string().label("Url").max(2048).matches(import_drawbridge_utils.regex.url, "Url must start with https").required().url()
82
84
  })
83
85
  ),
84
86
  values
@@ -46,7 +46,9 @@ const FormOrganizationCampaignLink = ({
46
46
  yup.object({
47
47
  asset: yup.object().label("Asset").nullable(),
48
48
  title: yup.string().label("Title").required().max(150),
49
- url: yup.string().label("Url").max(150).matches(regex.url, "Url must start with https").required().url()
49
+ // @story links/create
50
+ // The server stores any length; a UTM-tagged URL passes 150 easily.
51
+ url: yup.string().label("Url").max(2048).matches(regex.url, "Url must start with https").required().url()
50
52
  })
51
53
  ),
52
54
  values
@@ -49,6 +49,7 @@ var import_input_checkbox = __toESM(require("../input-checkbox"));
49
49
  var import_input_switch = __toESM(require("../shared/input-switch"));
50
50
  var import_button = __toESM(require("../shared/button"));
51
51
  var import_copy_to_clipboard = __toESM(require("../shared/copy-to-clipboard"));
52
+ var import_connection_controls = require("./lib/connection-controls");
52
53
  const Submit = ({
53
54
  disabled,
54
55
  isSubmitting,
@@ -252,7 +253,7 @@ const FormOrganizationConnection = ({
252
253
  onError(error);
253
254
  }
254
255
  };
255
- const connected = Boolean(values == null ? void 0 : values.id) && (values == null ? void 0 : values.status) !== "error";
256
+ const connected = (0, import_connection_controls.isConnected)(values);
256
257
  const inputs = (0, import_react.useMemo)(
257
258
  () => fields.filter((field) => field.input && !(connected && field.secret)),
258
259
  [connected, fields]
@@ -17,6 +17,7 @@ import InputCheckbox from "../input-checkbox.mjs";
17
17
  import InputSwitch from "../shared/input-switch.mjs";
18
18
  import Button from "../shared/button.mjs";
19
19
  import CopyToClipboard from "../shared/copy-to-clipboard.mjs";
20
+ import { isConnected } from "./lib/connection-controls.mjs";
20
21
  const Submit = ({
21
22
  disabled,
22
23
  isSubmitting,
@@ -220,7 +221,7 @@ const FormOrganizationConnection = ({
220
221
  onError(error);
221
222
  }
222
223
  };
223
- const connected = Boolean(values == null ? void 0 : values.id) && (values == null ? void 0 : values.status) !== "error";
224
+ const connected = isConnected(values);
224
225
  const inputs = useMemo(
225
226
  () => fields.filter((field) => field.input && !(connected && field.secret)),
226
227
  [connected, fields]
@@ -17,9 +17,12 @@ var __copyProps = (to, from, except, desc) => {
17
17
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
18
  var connection_controls_exports = {};
19
19
  __export(connection_controls_exports, {
20
- connectionControls: () => connectionControls
20
+ connectionControls: () => connectionControls,
21
+ isConnected: () => isConnected
21
22
  });
22
23
  module.exports = __toCommonJS(connection_controls_exports);
24
+ var import_reconnect = require("@drawbridge/drawbridge-utils/connections/reconnect");
25
+ const isConnected = (connection) => Boolean(connection == null ? void 0 : connection.id) && !(0, import_reconnect.requiresReconnect)(connection);
23
26
  const connectionControls = (connection) => {
24
27
  var _a;
25
28
  const paused = Boolean(connection == null ? void 0 : connection.paused);
@@ -30,5 +33,6 @@ const connectionControls = (connection) => {
30
33
  };
31
34
  // Annotate the CommonJS export names for ESM import in node:
32
35
  0 && (module.exports = {
33
- connectionControls
36
+ connectionControls,
37
+ isConnected
34
38
  });
@@ -1,3 +1,5 @@
1
+ import { requiresReconnect } from "@drawbridge/drawbridge-utils/connections/reconnect";
2
+ const isConnected = (connection) => Boolean(connection == null ? void 0 : connection.id) && !requiresReconnect(connection);
1
3
  const connectionControls = (connection) => {
2
4
  var _a;
3
5
  const paused = Boolean(connection == null ? void 0 : connection.paused);
@@ -7,5 +9,6 @@ const connectionControls = (connection) => {
7
9
  };
8
10
  };
9
11
  export {
10
- connectionControls
12
+ connectionControls,
13
+ isConnected
11
14
  };
@@ -256,6 +256,7 @@ const ChartLine = ({
256
256
  "shortcodes"
257
257
  ],
258
258
  empty,
259
+ format = (value) => value,
259
260
  legend = true
260
261
  }) => {
261
262
  const [active, setActive] = (0, import_react.useState)(datasets);
@@ -432,8 +433,11 @@ const ChartLine = ({
432
433
  import_recharts.Tooltip,
433
434
  {
434
435
  ...{
436
+ // @story redemptions/overview
437
+ // A money chart passes `format` so a day's summed gross reads
438
+ // "$12.50", not 12.500000000000002; counts pass nothing.
435
439
  formatter: (value, name) => [
436
- value,
440
+ format(value),
437
441
  label(name)
438
442
  ]
439
443
  }
@@ -223,6 +223,7 @@ const ChartLine = ({
223
223
  "shortcodes"
224
224
  ],
225
225
  empty,
226
+ format = (value) => value,
226
227
  legend = true
227
228
  }) => {
228
229
  const [active, setActive] = useState(datasets);
@@ -399,8 +400,11 @@ const ChartLine = ({
399
400
  Tooltip,
400
401
  {
401
402
  ...{
403
+ // @story redemptions/overview
404
+ // A money chart passes `format` so a day's summed gross reads
405
+ // "$12.50", not 12.500000000000002; counts pass nothing.
402
406
  formatter: (value, name) => [
403
- value,
407
+ format(value),
404
408
  label(name)
405
409
  ]
406
410
  }