@drawbridge/components 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -45,6 +45,7 @@ const TableCellTitle = (props) => {
45
45
  asset,
46
46
  avatar,
47
47
  error,
48
+ icon,
48
49
  indicator,
49
50
  status,
50
51
  subtitle,
@@ -88,6 +89,8 @@ const TableCellTitle = (props) => {
88
89
  asset,
89
90
  // As tall as the avatar, so two lines of text sit beside it.
90
91
  className: "w-10 h-10",
92
+ // What the row is when it has no image ( a link's social glyph ); the image wins.
93
+ icon,
91
94
  sizes: "40px",
92
95
  title
93
96
  }
@@ -12,6 +12,7 @@ const TableCellTitle = (props) => {
12
12
  asset,
13
13
  avatar,
14
14
  error,
15
+ icon,
15
16
  indicator,
16
17
  status,
17
18
  subtitle,
@@ -55,6 +56,8 @@ const TableCellTitle = (props) => {
55
56
  asset,
56
57
  // As tall as the avatar, so two lines of text sit beside it.
57
58
  className: "w-10 h-10",
59
+ // What the row is when it has no image ( a link's social glyph ); the image wins.
60
+ icon,
58
61
  sizes: "40px",
59
62
  title
60
63
  }
@@ -52,6 +52,7 @@ var import_link_back = __toESM(require("./link-back"));
52
52
  var import_wrapper_gradient = __toESM(require("../wrapper-gradient"));
53
53
  var import_image = __toESM(require("next/image"));
54
54
  var import_link = __toESM(require("./link"));
55
+ var import_loader2 = __toESM(require("../shared/loader"));
55
56
  const Refetch = ({
56
57
  invalidate = []
57
58
  }) => {
@@ -494,15 +495,41 @@ const TemplateDashboardTasks = ({
494
495
  }
495
496
  return null;
496
497
  };
498
+ const SETTLE_QUIET = 50;
499
+ const SETTLE_CAP = 3e3;
500
+ const useSettled = (enabled) => {
501
+ const queryClient = (0, import_react_query.useQueryClient)();
502
+ const fetching = (0, import_react_query.useIsFetching)();
503
+ const [settled, setSettled] = (0, import_react.useState)(!enabled);
504
+ (0, import_react.useEffect)(
505
+ () => {
506
+ if (settled) return void 0;
507
+ const cap = setTimeout(() => setSettled(true), SETTLE_CAP);
508
+ return () => clearTimeout(cap);
509
+ },
510
+ [settled]
511
+ );
512
+ (0, import_react.useEffect)(
513
+ () => {
514
+ if (settled || fetching) return void 0;
515
+ const quiet = setTimeout(() => queryClient.isFetching() || setSettled(true), SETTLE_QUIET);
516
+ return () => clearTimeout(quiet);
517
+ },
518
+ [fetching, queryClient, settled]
519
+ );
520
+ return settled;
521
+ };
497
522
  const TemplateDashboard = ({
498
523
  back,
499
524
  badges = [],
500
525
  children,
501
526
  className = "px-6 pb-6 md:px-12 md:pb-12",
527
+ settle = false,
502
528
  tasks,
503
529
  title,
504
530
  ...rest
505
531
  }) => {
532
+ const settled = useSettled(settle);
506
533
  const classes = {
507
534
  container: [
508
535
  "flex flex-col w-full justify-between flex-1 min-h-0"
@@ -512,7 +539,9 @@ const TemplateDashboard = ({
512
539
  className
513
540
  ],
514
541
  wrapper: [
515
- "relative flex flex-col justify-between h-full w-full space-y-6 pt-6 md:pt-12"
542
+ "relative flex flex-col justify-between h-full w-full space-y-6 pt-6 md:pt-12",
543
+ // Hidden, not unmounted: the sections stay laid out and their requests run.
544
+ ...settled ? [] : ["invisible"]
516
545
  ]
517
546
  };
518
547
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -562,6 +591,14 @@ const TemplateDashboard = ({
562
591
  }
563
592
  )
564
593
  }
594
+ ),
595
+ !settled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
596
+ import_loader2.default,
597
+ {
598
+ ...{
599
+ className: "visible"
600
+ }
601
+ }
565
602
  )
566
603
  ]
567
604
  }
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { isValidElement, useState } from "react";
3
- import { keepPreviousData, useQueryClient } from "@tanstack/react-query";
2
+ import { isValidElement, useEffect, useState } from "react";
3
+ import { keepPreviousData, useIsFetching, useQueryClient } from "@tanstack/react-query";
4
4
  import { notFound } from "next/navigation";
5
5
  import { isTransientError } from "@drawbridge/drawbridge-utils/fetch";
6
6
  import { useDrawer } from "./lib/hooks.mjs";
@@ -17,6 +17,7 @@ import LinkBack from "./link-back.mjs";
17
17
  import WrapperGradient from "../wrapper-gradient.mjs";
18
18
  import Image from "next/image";
19
19
  import Link from "./link.mjs";
20
+ import Loader from "../shared/loader.mjs";
20
21
  const Refetch = ({
21
22
  invalidate = []
22
23
  }) => {
@@ -459,15 +460,41 @@ const TemplateDashboardTasks = ({
459
460
  }
460
461
  return null;
461
462
  };
463
+ const SETTLE_QUIET = 50;
464
+ const SETTLE_CAP = 3e3;
465
+ const useSettled = (enabled) => {
466
+ const queryClient = useQueryClient();
467
+ const fetching = useIsFetching();
468
+ const [settled, setSettled] = useState(!enabled);
469
+ useEffect(
470
+ () => {
471
+ if (settled) return void 0;
472
+ const cap = setTimeout(() => setSettled(true), SETTLE_CAP);
473
+ return () => clearTimeout(cap);
474
+ },
475
+ [settled]
476
+ );
477
+ useEffect(
478
+ () => {
479
+ if (settled || fetching) return void 0;
480
+ const quiet = setTimeout(() => queryClient.isFetching() || setSettled(true), SETTLE_QUIET);
481
+ return () => clearTimeout(quiet);
482
+ },
483
+ [fetching, queryClient, settled]
484
+ );
485
+ return settled;
486
+ };
462
487
  const TemplateDashboard = ({
463
488
  back,
464
489
  badges = [],
465
490
  children,
466
491
  className = "px-6 pb-6 md:px-12 md:pb-12",
492
+ settle = false,
467
493
  tasks,
468
494
  title,
469
495
  ...rest
470
496
  }) => {
497
+ const settled = useSettled(settle);
471
498
  const classes = {
472
499
  container: [
473
500
  "flex flex-col w-full justify-between flex-1 min-h-0"
@@ -477,7 +504,9 @@ const TemplateDashboard = ({
477
504
  className
478
505
  ],
479
506
  wrapper: [
480
- "relative flex flex-col justify-between h-full w-full space-y-6 pt-6 md:pt-12"
507
+ "relative flex flex-col justify-between h-full w-full space-y-6 pt-6 md:pt-12",
508
+ // Hidden, not unmounted: the sections stay laid out and their requests run.
509
+ ...settled ? [] : ["invisible"]
481
510
  ]
482
511
  };
483
512
  return /* @__PURE__ */ jsxs(
@@ -527,6 +556,14 @@ const TemplateDashboard = ({
527
556
  }
528
557
  )
529
558
  }
559
+ ),
560
+ !settled && /* @__PURE__ */ jsx(
561
+ Loader,
562
+ {
563
+ ...{
564
+ className: "visible"
565
+ }
566
+ }
530
567
  )
531
568
  ]
532
569
  }
@@ -0,0 +1,46 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var filter_small_exports = {};
19
+ __export(filter_small_exports, {
20
+ default: () => filter_small_default
21
+ });
22
+ module.exports = __toCommonJS(filter_small_exports);
23
+ var import_jsx_runtime = require("react/jsx-runtime");
24
+ const FilterSmall = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
25
+ "svg",
26
+ {
27
+ ...{
28
+ fill: "none",
29
+ stroke: "currentColor",
30
+ strokeWidth: 1.5,
31
+ viewBox: "-3 -3 30 30",
32
+ ...props
33
+ },
34
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
35
+ "path",
36
+ {
37
+ ...{
38
+ d: "M10.5 6h9.75M10.5 6a1.5 1.5 0 1 1-3 0m3 0a1.5 1.5 0 1 0-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m-9.75 0h9.75",
39
+ strokeLinecap: "round",
40
+ strokeLinejoin: "round"
41
+ }
42
+ }
43
+ )
44
+ }
45
+ );
46
+ var filter_small_default = FilterSmall;
@@ -0,0 +1,27 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ const FilterSmall = (props) => /* @__PURE__ */ jsx(
3
+ "svg",
4
+ {
5
+ ...{
6
+ fill: "none",
7
+ stroke: "currentColor",
8
+ strokeWidth: 1.5,
9
+ viewBox: "-3 -3 30 30",
10
+ ...props
11
+ },
12
+ children: /* @__PURE__ */ jsx(
13
+ "path",
14
+ {
15
+ ...{
16
+ d: "M10.5 6h9.75M10.5 6a1.5 1.5 0 1 1-3 0m3 0a1.5 1.5 0 1 0-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m-9.75 0h9.75",
17
+ strokeLinecap: "round",
18
+ strokeLinejoin: "round"
19
+ }
20
+ }
21
+ )
22
+ }
23
+ );
24
+ var filter_small_default = FilterSmall;
25
+ export {
26
+ filter_small_default as default
27
+ };
@@ -0,0 +1,46 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var plus_small_exports = {};
19
+ __export(plus_small_exports, {
20
+ default: () => plus_small_default
21
+ });
22
+ module.exports = __toCommonJS(plus_small_exports);
23
+ var import_jsx_runtime = require("react/jsx-runtime");
24
+ const PlusSmall = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
25
+ "svg",
26
+ {
27
+ ...{
28
+ fill: "none",
29
+ stroke: "currentColor",
30
+ strokeWidth: 1.5,
31
+ viewBox: "-3 -3 30 30",
32
+ ...props
33
+ },
34
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
35
+ "path",
36
+ {
37
+ ...{
38
+ d: "M12 4.5v15m7.5-7.5h-15",
39
+ strokeLinecap: "round",
40
+ strokeLinejoin: "round"
41
+ }
42
+ }
43
+ )
44
+ }
45
+ );
46
+ var plus_small_default = PlusSmall;
@@ -0,0 +1,27 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ const PlusSmall = (props) => /* @__PURE__ */ jsx(
3
+ "svg",
4
+ {
5
+ ...{
6
+ fill: "none",
7
+ stroke: "currentColor",
8
+ strokeWidth: 1.5,
9
+ viewBox: "-3 -3 30 30",
10
+ ...props
11
+ },
12
+ children: /* @__PURE__ */ jsx(
13
+ "path",
14
+ {
15
+ ...{
16
+ d: "M12 4.5v15m7.5-7.5h-15",
17
+ strokeLinecap: "round",
18
+ strokeLinejoin: "round"
19
+ }
20
+ }
21
+ )
22
+ }
23
+ );
24
+ var plus_small_default = PlusSmall;
25
+ export {
26
+ plus_small_default as default
27
+ };
@@ -288,7 +288,9 @@ const published = {
288
288
  };
289
289
  const running = {
290
290
  active: { text: "Active", type: "success" },
291
- error: { text: "Error", type: "error" }
291
+ error: { text: "Error", type: "error" },
292
+ paused: { text: "Paused", type: "warning" },
293
+ pending: { text: "Pending", type: "warning" }
292
294
  };
293
295
  const organizationStatuses = ["active", "canceled", "incomplete", "incomplete_expired", "locked", "past_due", "paused", "suspended", "trialing", "unpaid", "unsubscribed"];
294
296
  const statuses = {
@@ -299,9 +301,14 @@ const statuses = {
299
301
  disqualified: { text: "Disqualified", type: "error" },
300
302
  verified: { text: "Verified", type: "success" }
301
303
  },
304
+ // Every Stripe invoice status, in one set of colours across the dashboard: money
305
+ // owed is red, paid green, a draft or a voided invoice gray.
302
306
  invoice: {
307
+ draft: { text: "Draft", type: "secondary" },
308
+ open: { text: "Open", type: "error" },
303
309
  paid: { text: "Paid", type: "success" },
304
- void: { text: "Void", type: "error" }
310
+ uncollectible: { text: "Uncollectible", type: "error" },
311
+ void: { text: "Void", type: "secondary" }
305
312
  },
306
313
  // A lead's status is derived per campaign from the denial records: approved or
307
314
  // denied. Approved is every lead's normal state, so only Denied shows, as on
@@ -309,11 +316,11 @@ const statuses = {
309
316
  lead: {
310
317
  denied: { text: "Denied", type: "error" }
311
318
  },
312
- // pending is a member who has been invited and not yet accepted.
319
+ // pending is a member who has been invited and not yet accepted: yellow, like every pending.
313
320
  member: {
314
- pending: { text: "Invited", type: "primary" }
321
+ pending: { text: "Invited", type: "warning" }
315
322
  },
316
- organization: Object.fromEntries(organizationStatuses.map((status) => [status, { text: (0, import_drawbridge_utils.capitalize)(status.replace("_", " ")), type: ["active", "unsubscribed"].includes(status) ? "success" : "error" }])),
323
+ organization: Object.fromEntries(organizationStatuses.map((status) => [status, { text: (0, import_drawbridge_utils.capitalize)(status.replace("_", " ")), type: ["active", "unsubscribed"].includes(status) ? "success" : status === "paused" ? "warning" : "error" }])),
317
324
  page: published,
318
325
  prize: published,
319
326
  range: {
@@ -229,7 +229,9 @@ const published = {
229
229
  };
230
230
  const running = {
231
231
  active: { text: "Active", type: "success" },
232
- error: { text: "Error", type: "error" }
232
+ error: { text: "Error", type: "error" },
233
+ paused: { text: "Paused", type: "warning" },
234
+ pending: { text: "Pending", type: "warning" }
233
235
  };
234
236
  const organizationStatuses = ["active", "canceled", "incomplete", "incomplete_expired", "locked", "past_due", "paused", "suspended", "trialing", "unpaid", "unsubscribed"];
235
237
  const statuses = {
@@ -240,9 +242,14 @@ const statuses = {
240
242
  disqualified: { text: "Disqualified", type: "error" },
241
243
  verified: { text: "Verified", type: "success" }
242
244
  },
245
+ // Every Stripe invoice status, in one set of colours across the dashboard: money
246
+ // owed is red, paid green, a draft or a voided invoice gray.
243
247
  invoice: {
248
+ draft: { text: "Draft", type: "secondary" },
249
+ open: { text: "Open", type: "error" },
244
250
  paid: { text: "Paid", type: "success" },
245
- void: { text: "Void", type: "error" }
251
+ uncollectible: { text: "Uncollectible", type: "error" },
252
+ void: { text: "Void", type: "secondary" }
246
253
  },
247
254
  // A lead's status is derived per campaign from the denial records: approved or
248
255
  // denied. Approved is every lead's normal state, so only Denied shows, as on
@@ -250,11 +257,11 @@ const statuses = {
250
257
  lead: {
251
258
  denied: { text: "Denied", type: "error" }
252
259
  },
253
- // pending is a member who has been invited and not yet accepted.
260
+ // pending is a member who has been invited and not yet accepted: yellow, like every pending.
254
261
  member: {
255
- pending: { text: "Invited", type: "primary" }
262
+ pending: { text: "Invited", type: "warning" }
256
263
  },
257
- organization: Object.fromEntries(organizationStatuses.map((status) => [status, { text: capitalize(status.replace("_", " ")), type: ["active", "unsubscribed"].includes(status) ? "success" : "error" }])),
264
+ organization: Object.fromEntries(organizationStatuses.map((status) => [status, { text: capitalize(status.replace("_", " ")), type: ["active", "unsubscribed"].includes(status) ? "success" : status === "paused" ? "warning" : "error" }])),
258
265
  page: published,
259
266
  prize: published,
260
267
  range: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawbridge/components",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "exports": {
5
5
  "./advertisement": {
6
6
  "types": "./types/advertisement.d.ts",
@@ -1027,6 +1027,11 @@
1027
1027
  "import": "./dist/icons/facebook.mjs",
1028
1028
  "default": "./dist/icons/facebook.js"
1029
1029
  },
1030
+ "./icons/filter-small": {
1031
+ "types": "./types/icons/filter-small.d.ts",
1032
+ "import": "./dist/icons/filter-small.mjs",
1033
+ "default": "./dist/icons/filter-small.js"
1034
+ },
1030
1035
  "./icons/filter": {
1031
1036
  "types": "./types/icons/filter.d.ts",
1032
1037
  "import": "./dist/icons/filter.mjs",
@@ -1137,6 +1142,11 @@
1137
1142
  "import": "./dist/icons/play.mjs",
1138
1143
  "default": "./dist/icons/play.js"
1139
1144
  },
1145
+ "./icons/plus-small": {
1146
+ "types": "./types/icons/plus-small.d.ts",
1147
+ "import": "./dist/icons/plus-small.mjs",
1148
+ "default": "./dist/icons/plus-small.js"
1149
+ },
1140
1150
  "./icons/plus": {
1141
1151
  "types": "./types/icons/plus.d.ts",
1142
1152
  "import": "./dist/icons/plus.mjs",
@@ -10,6 +10,8 @@ export interface TableCellTitleProps {
10
10
  // Why the record needs attention, as alertFor returns it, or just the words.
11
11
  // Shown with the small Error: the warning, or the lock a locked record passes.
12
12
  error?: { icon?: ComponentType<SVGProps<SVGSVGElement>>; message?: string | null } | string | null;
13
+ // Drawn in the thumbnail's place when asset has no image ( a link's social glyph ); an image wins.
14
+ icon?: ComponentType<SVGProps<SVGSVGElement>> | null;
13
15
  // A campaign's dot when there is no picture, with the words it shows on hover: active
14
16
  // ('Accepting submissions'), error (published, 'Not accepting submissions'), inactive (draft).
15
17
  indicator?: { label: string; type: 'active' | 'error' | 'inactive' | 'warning' } | null;
@@ -44,6 +44,9 @@ export interface TemplateDashboardProps {
44
44
  controls?: TemplateDashboardButton[];
45
45
  // A <Refetch /> beside the title.
46
46
  refetch?: ReactNode;
47
+ // The page appears once: hidden behind the loader until no request is in flight
48
+ // (at most 3 s), so sections don't land one by one. The first load only.
49
+ settle?: boolean;
47
50
  // Next steps: the first item in a bar, the rest in the drawer.
48
51
  tasks?: { items?: TemplateDashboardTask[] };
49
52
  // No header without a title.
@@ -0,0 +1,5 @@
1
+ import { ComponentType, SVGProps } from 'react';
2
+
3
+ declare const Icon: ComponentType<SVGProps<SVGSVGElement>>;
4
+
5
+ export default Icon;
@@ -0,0 +1,5 @@
1
+ import { ComponentType, SVGProps } from 'react';
2
+
3
+ declare const Icon: ComponentType<SVGProps<SVGSVGElement>>;
4
+
5
+ export default Icon;