@cloudgrid-io/ui 0.33.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +25 -0
  2. package/dist/badge/css-live.d.ts +7 -4
  3. package/dist/badge/css-live.d.ts.map +1 -1
  4. package/dist/badge/css-live.js +9 -6
  5. package/dist/badge/css-live.js.map +1 -1
  6. package/dist/badge/palette.d.ts +1 -1
  7. package/dist/badge/palette.d.ts.map +1 -1
  8. package/dist/badge/palette.js +2 -1
  9. package/dist/badge/palette.js.map +1 -1
  10. package/dist/blocks/bar-chart.d.ts +97 -0
  11. package/dist/blocks/bar-chart.d.ts.map +1 -0
  12. package/dist/blocks/bar-chart.js +25 -0
  13. package/dist/blocks/bar-chart.js.map +1 -0
  14. package/dist/blocks/chart-parts.d.ts +101 -0
  15. package/dist/blocks/chart-parts.d.ts.map +1 -0
  16. package/dist/blocks/chart-parts.js +96 -0
  17. package/dist/blocks/chart-parts.js.map +1 -0
  18. package/dist/blocks/coming-soon-tag.d.ts +34 -0
  19. package/dist/blocks/coming-soon-tag.d.ts.map +1 -0
  20. package/dist/blocks/coming-soon-tag.js +37 -0
  21. package/dist/blocks/coming-soon-tag.js.map +1 -0
  22. package/dist/blocks/disabled-button.d.ts +43 -0
  23. package/dist/blocks/disabled-button.d.ts.map +1 -0
  24. package/dist/blocks/disabled-button.js +50 -0
  25. package/dist/blocks/disabled-button.js.map +1 -0
  26. package/dist/blocks/entity-card.d.ts.map +1 -1
  27. package/dist/blocks/entity-card.js +3 -1
  28. package/dist/blocks/entity-card.js.map +1 -1
  29. package/dist/blocks/full-screen-button.d.ts +9 -0
  30. package/dist/blocks/full-screen-button.d.ts.map +1 -0
  31. package/dist/blocks/full-screen-button.js +51 -0
  32. package/dist/blocks/full-screen-button.js.map +1 -0
  33. package/dist/blocks/icons.d.ts +30 -0
  34. package/dist/blocks/icons.d.ts.map +1 -1
  35. package/dist/blocks/icons.js +30 -0
  36. package/dist/blocks/icons.js.map +1 -1
  37. package/dist/blocks/meter.d.ts +57 -0
  38. package/dist/blocks/meter.d.ts.map +1 -0
  39. package/dist/blocks/meter.js +19 -0
  40. package/dist/blocks/meter.js.map +1 -0
  41. package/dist/blocks/notice.d.ts +87 -0
  42. package/dist/blocks/notice.d.ts.map +1 -0
  43. package/dist/blocks/notice.js +26 -0
  44. package/dist/blocks/notice.js.map +1 -0
  45. package/dist/blocks/pager.d.ts +76 -0
  46. package/dist/blocks/pager.d.ts.map +1 -0
  47. package/dist/blocks/pager.js +86 -0
  48. package/dist/blocks/pager.js.map +1 -0
  49. package/dist/blocks/phosphor-marks.d.ts +20 -0
  50. package/dist/blocks/phosphor-marks.d.ts.map +1 -1
  51. package/dist/blocks/phosphor-marks.js +30 -0
  52. package/dist/blocks/phosphor-marks.js.map +1 -1
  53. package/dist/blocks/ranked-list.d.ts +67 -0
  54. package/dist/blocks/ranked-list.d.ts.map +1 -0
  55. package/dist/blocks/ranked-list.js +32 -0
  56. package/dist/blocks/ranked-list.js.map +1 -0
  57. package/dist/blocks/side-nav.d.ts.map +1 -1
  58. package/dist/blocks/side-nav.js +3 -1
  59. package/dist/blocks/side-nav.js.map +1 -1
  60. package/dist/blocks/sign-in-options.d.ts.map +1 -1
  61. package/dist/blocks/sign-in-options.js +3 -1
  62. package/dist/blocks/sign-in-options.js.map +1 -1
  63. package/dist/blocks/stacked-bar.d.ts +64 -0
  64. package/dist/blocks/stacked-bar.d.ts.map +1 -0
  65. package/dist/blocks/stacked-bar.js +18 -0
  66. package/dist/blocks/stacked-bar.js.map +1 -0
  67. package/dist/blocks/stat-tile.d.ts +42 -1
  68. package/dist/blocks/stat-tile.d.ts.map +1 -1
  69. package/dist/blocks/stat-tile.js +23 -4
  70. package/dist/blocks/stat-tile.js.map +1 -1
  71. package/dist/blocks/tab-bar.d.ts.map +1 -1
  72. package/dist/blocks/tab-bar.js +3 -1
  73. package/dist/blocks/tab-bar.js.map +1 -1
  74. package/dist/blocks/timeline.d.ts +53 -0
  75. package/dist/blocks/timeline.d.ts.map +1 -0
  76. package/dist/blocks/timeline.js +18 -0
  77. package/dist/blocks/timeline.js.map +1 -0
  78. package/dist/blocks/tool-setup.d.ts.map +1 -1
  79. package/dist/blocks/tool-setup.js +3 -1
  80. package/dist/blocks/tool-setup.js.map +1 -1
  81. package/dist/blocks/trend.d.ts +33 -0
  82. package/dist/blocks/trend.d.ts.map +1 -0
  83. package/dist/blocks/trend.js +13 -0
  84. package/dist/blocks/trend.js.map +1 -0
  85. package/dist/claim-banner/css.d.ts +39 -0
  86. package/dist/claim-banner/css.d.ts.map +1 -0
  87. package/dist/claim-banner/css.js +87 -0
  88. package/dist/claim-banner/css.js.map +1 -0
  89. package/dist/claim-banner/index.d.ts +122 -0
  90. package/dist/claim-banner/index.d.ts.map +1 -0
  91. package/dist/claim-banner/index.js +135 -0
  92. package/dist/claim-banner/index.js.map +1 -0
  93. package/dist/docs.css +466 -0
  94. package/dist/expressive-code-palette.d.ts +5 -0
  95. package/dist/expressive-code-palette.d.ts.map +1 -0
  96. package/dist/expressive-code-palette.js +10 -0
  97. package/dist/expressive-code-palette.js.map +1 -0
  98. package/dist/expressive-code.d.ts +57 -0
  99. package/dist/expressive-code.d.ts.map +1 -0
  100. package/dist/expressive-code.js +101 -0
  101. package/dist/expressive-code.js.map +1 -0
  102. package/dist/index.d.ts +12 -0
  103. package/dist/index.d.ts.map +1 -1
  104. package/dist/index.js +12 -0
  105. package/dist/index.js.map +1 -1
  106. package/dist/tokens/base.json +13 -0
  107. package/dist/tokens/surface-ink.json +13 -0
  108. package/dist/tokens/surface-panel.json +13 -0
  109. package/dist/tokens.css +36 -0
  110. package/dist/tokens.json +6 -3
  111. package/dist/ui/card.d.ts +23 -2
  112. package/dist/ui/card.d.ts.map +1 -1
  113. package/dist/ui/card.js +16 -5
  114. package/dist/ui/card.js.map +1 -1
  115. package/dist/ui/drawer.d.ts +37 -6
  116. package/dist/ui/drawer.d.ts.map +1 -1
  117. package/dist/ui/drawer.js +31 -7
  118. package/dist/ui/drawer.js.map +1 -1
  119. package/dist/ui/input-group.d.ts.map +1 -1
  120. package/dist/ui/input-group.js +3 -1
  121. package/dist/ui/input-group.js.map +1 -1
  122. package/package.json +18 -3
@@ -0,0 +1,87 @@
1
+ /**
2
+ * The guest claim banner's stylesheet (ui issue 342), for a `<style>` beside it.
3
+ *
4
+ * Every rule reads a token by name. The banner sits in a page the library does
5
+ * not style (the inspiration-server's wrapper, or a dropped site's own HTML on
6
+ * the legacy inline path), so the root declares the values of the tokens it
7
+ * reads, taken from the badge's generated palette, which `build-badge.mjs`
8
+ * resolves from tokens.css. Nothing here is a literal colour.
9
+ *
10
+ * The look is the founder's mock (cloudgrid issue 4872), spread out on his
11
+ * review of the gallery (2026-10-19): the grid purple bar and white text in
12
+ * three parts, the white wordmark at the left edge, the sentence centred on the
13
+ * bar, and the lime pill with ink text at the right just before the close x.
14
+ * The sides are equal columns, so the sentence sits on the bar's centre whatever
15
+ * the wordmark and the pill measure; the padding is the same on both sides and
16
+ * the pill keeps clear of the x with its own margin. No sign tile. The sentence is regular with
17
+ * its first half bold, the library's 400 and 600: the mock draws 500 and 700,
18
+ * the library has no 700, and 500 against 600 does not read as bold.
19
+ *
20
+ * At 640px and under: the wordmark hides, the type steps down to the 13px
21
+ * caption, the sentence takes the first line and the button wraps under it.
22
+ *
23
+ * The root is `order:-1` and does not shrink, so in a flex-column body (the
24
+ * wrapper's, after cloudgrid issue 4872) it leads the page even when it is appended
25
+ * after the iframe, and pushes the site down instead of covering it.
26
+ *
27
+ * `data-placement="fixed"` (the legacy inline path) fixes it to the top of the
28
+ * viewport instead; the inline script pads the body by its height.
29
+ *
30
+ * It loads no font. The family is the library's UI stack (Manrope, then the
31
+ * system faces); the badge registers Manrope on the wrapper's document, and
32
+ * `CLAIM_BANNER_FONT_FACES` names the file for a page without the badge.
33
+ */
34
+ import { BADGE_TOKENS } from "../badge/palette.js";
35
+ /** Every token the stylesheet reads. Tests hold the stylesheet to this list. */
36
+ export const CLAIM_BANNER_TOKEN_NAMES = [
37
+ "--cg-primary",
38
+ "--cg-text-on-primary",
39
+ "--cg-secondary",
40
+ "--cg-secondary-press",
41
+ "--cg-text-on-accent",
42
+ "--cg-font-ui",
43
+ "--cg-weight-regular",
44
+ "--cg-weight-bold",
45
+ "--cg-body-14",
46
+ "--cg-caption",
47
+ "--cg-radius-pill",
48
+ "--cg-duration",
49
+ "--cg-ease-out",
50
+ ];
51
+ /** The token values as one declaration list, for the banner root. */
52
+ const DECLARATIONS = CLAIM_BANNER_TOKEN_NAMES.map((name) => `${name}:${BADGE_TOKENS[name]};`).join("");
53
+ /** The banner's width breakpoint: at and under it, the phone layout. */
54
+ export const CLAIM_BANNER_PHONE_MAX = 640;
55
+ export const CLAIM_BANNER_CSS = /* @__PURE__ */ (() => `.cgcb{all:initial;${DECLARATIONS}box-sizing:border-box;position:relative;order:-1;flex:none;display:grid;` +
56
+ `grid-template-columns:minmax(max-content,1fr) minmax(0,auto) minmax(max-content,1fr);align-items:center;column-gap:32px;` +
57
+ `width:100%;margin:0;padding:10px 24px;background:var(--cg-primary);color:var(--cg-text-on-primary);` +
58
+ `font-family:var(--cg-font-ui);font-size:var(--cg-body-14);font-weight:var(--cg-weight-regular);line-height:1.4;text-align:center;` +
59
+ `-webkit-font-smoothing:antialiased;-webkit-tap-highlight-color:transparent}
60
+ .cgcb[hidden]{display:none!important}
61
+ .cgcb[data-placement=fixed]{position:fixed;top:0;left:0;right:0;z-index:2147483646}
62
+ .cgcb *,.cgcb *::before,.cgcb *::after{box-sizing:border-box}
63
+ .cgcb svg{display:block;flex:none}
64
+ .cgcb svg path{fill:currentColor}
65
+ .cgcb-mark{display:flex;align-items:center;justify-self:start}
66
+ .cgcb-wordmark{width:auto;height:16px;color:var(--cg-text-on-primary)}
67
+ .cgcb-msg{min-width:0}
68
+ .cgcb-msg b{font-weight:var(--cg-weight-bold)}
69
+ .cgcb-btn{display:inline-flex;align-items:center;gap:6px;flex:none;padding:7px 14px;border-radius:var(--cg-radius-pill);` +
70
+ `background:var(--cg-secondary);color:var(--cg-text-on-accent);font-family:var(--cg-font-ui);font-size:var(--cg-caption);font-weight:var(--cg-weight-bold);` +
71
+ `justify-self:end;margin-right:28px;line-height:1;text-decoration:none;white-space:nowrap;cursor:pointer;transition:background-color var(--cg-duration) var(--cg-ease-out)}
72
+ .cgcb-btn:hover{background:var(--cg-secondary-press)}
73
+ .cgcb-x{position:absolute;top:50%;right:12px;display:grid;place-items:center;width:32px;height:32px;margin:0;padding:0;border:0;` +
74
+ `border-radius:var(--cg-radius-pill);background:transparent;color:color-mix(in srgb,var(--cg-text-on-primary) 80%,transparent);cursor:pointer;` +
75
+ `transform:translateY(-50%);transition:background-color var(--cg-duration) var(--cg-ease-out),color var(--cg-duration) var(--cg-ease-out)}
76
+ .cgcb-x svg{width:16px;height:16px}
77
+ .cgcb-x:hover{color:var(--cg-text-on-primary);background:color-mix(in srgb,var(--cg-text-on-primary) 14%,transparent)}
78
+ .cgcb-btn:focus-visible,.cgcb-x:focus-visible{outline:2px solid var(--cg-text-on-primary);outline-offset:2px}
79
+ .cgcb :focus:not(:focus-visible){outline:none}
80
+ @media (max-width:${CLAIM_BANNER_PHONE_MAX}px){` +
81
+ `.cgcb{display:flex;flex-wrap:wrap;justify-content:center;gap:10px;padding:10px 44px 10px 14px;font-size:var(--cg-caption)}` +
82
+ `.cgcb .cgcb-mark{display:none}` +
83
+ `.cgcb-msg{flex:1 1 100%}` +
84
+ `.cgcb-btn{margin-right:0}.cgcb-x{right:8px}}
85
+ @media (prefers-reduced-motion:reduce){.cgcb *{transition:none!important}}
86
+ `)();
87
+ //# sourceMappingURL=css.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"css.js","sourceRoot":"","sources":["../../src/claim-banner/css.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAElD,gFAAgF;AAChF,MAAM,CAAC,MAAM,wBAAwB,GAAsB;IACzD,cAAc;IACd,sBAAsB;IACtB,gBAAgB;IAChB,sBAAsB;IACtB,qBAAqB;IACrB,cAAc;IACd,qBAAqB;IACrB,kBAAkB;IAClB,cAAc;IACd,cAAc;IACd,kBAAkB;IAClB,eAAe;IACf,eAAe;CAChB,CAAA;AAED,qEAAqE;AACrE,MAAM,YAAY,GAAG,wBAAwB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AAEtG,wEAAwE;AACxE,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAA;AAEzC,MAAM,CAAC,MAAM,gBAAgB,GAAW,eAAe,CAAC,CAAC,GAAG,EAAE,CAC5D,qBAAqB,YAAY,0EAA0E;IAC3G,0HAA0H;IAC1H,qGAAqG;IACrG,mIAAmI;IACnI;;;;;;;;;;yHAUuH;IACvH,4JAA4J;IAC5J;;iIAE+H;IAC/H,+IAA+I;IAC/I;;;;;oBAKkB,sBAAsB,MAAM;IAC9C,4HAA4H;IAC5H,gCAAgC;IAChC,0BAA0B;IAC1B;;CAED,CAAC,EAAE,CAAA"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * THE GUEST CLAIM BANNER, FRAMEWORK-FREE (ui#342, for cloudgrid#4872).
3
+ *
4
+ * The top bar on every unclaimed guest drop: "This site disappears in N days
5
+ * unless it's claimed." and a lime "Claim it" pill, with a close x that hides
6
+ * it for the visit. The platform's inspiration-server builds HTML strings, so
7
+ * this is the badge's shape: a builder that returns a string, and the
8
+ * stylesheet and the inline script as strings beside it. No React, no DOM.
9
+ *
10
+ * ```ts
11
+ * import { claimBanner } from "@cloudgrid-io/ui/claim-banner"
12
+ * const ctaMarkup = claimBanner({ claimUrl, expiresAt: entity.inspiration_block?.expires_at, nonce })
13
+ * ```
14
+ *
15
+ * What the consumer's constraints settle (cloudgrid#4872):
16
+ *
17
+ * byte-identical nothing here depends on the viewer or the clock. The day
18
+ * count is computed by the inline script, in the browser,
19
+ * from `data-expires-at`, so a cached render is right for
20
+ * every viewer on every day
21
+ * no flash the banner is `hidden` in the bytes; the script reveals it
22
+ * unless this visit dismissed it (`sessionStorage`)
23
+ * nonce with `nonce`, the `<style>` and `<script>` carry it, for
24
+ * the wrapper's `script-src 'nonce-...'`; without it they
25
+ * carry nothing, for the legacy inline path
26
+ * escaping every caller value is text; the URL is attribute-escaped
27
+ * and refused (`#`) unless it is http(s)
28
+ * sentinel nothing here says `data-cloudgrid-badge`, so the badge
29
+ * injector's idempotency check never trips on it
30
+ * fonts no `@font-face` and no `@import`; the UI stack falls back
31
+ * to the system faces under `font-src 'self' api.cloudgrid.io`
32
+ */
33
+ export { CLAIM_BANNER_CSS, CLAIM_BANNER_PHONE_MAX, CLAIM_BANNER_TOKEN_NAMES } from "./css.js";
34
+ /** The banner element's id. The script finds the banner by it. */
35
+ export declare const CLAIM_BANNER_ID = "cg-claim-banner";
36
+ /**
37
+ * The `sessionStorage` key the close x sets to "1": hidden for this visit, back
38
+ * in a new tab. The platform's own banner's key (cloudgrid#4978), so a viewer
39
+ * who dismissed that one is not shown this one again in the same visit.
40
+ */
41
+ export declare const CLAIM_BANNER_DISMISS_KEY = "cg_anon_cta_dismissed";
42
+ /** The days shown when the drop carries no expiry, or one the browser cannot read. */
43
+ export declare const CLAIM_BANNER_FALLBACK_DAYS = 7;
44
+ /** The button's label. The arrow after it is decorative. */
45
+ export declare const CLAIM_BANNER_LABEL = "Claim it";
46
+ /** The close x's accessible name. */
47
+ export declare const CLAIM_BANNER_CLOSE_LABEL = "Hide for this visit";
48
+ /**
49
+ * The one face the banner sets type in: Manrope, the badge's file. The banner
50
+ * loads nothing itself. On the wrapper the badge already registers it; a page
51
+ * without the badge hosts this file (the package carries it at
52
+ * `@cloudgrid-io/ui/brand/badge/<file>`) and declares it. Without it the system
53
+ * stack renders.
54
+ */
55
+ export declare const CLAIM_BANNER_FONT_FACES: {
56
+ family: string;
57
+ weight: string;
58
+ style: string;
59
+ file: string;
60
+ unicodeRange: string;
61
+ }[];
62
+ /**
63
+ * The inline script. ES5, no globals left behind. It finds the banner, leaves
64
+ * it hidden if this visit dismissed it, writes the day count, reveals it, and
65
+ * wires the close x. Storage that throws (a sandbox, a privacy mode) counts as
66
+ * "not dismissed", and the x still hides the banner for the page.
67
+ *
68
+ * The count is in whole calendar days in the viewer's time zone, from today to
69
+ * the expiry's date: "in N days", "in 1 day", "today" on the last day. A past
70
+ * expiry reads "today", never a negative number (the platform's expired page
71
+ * normally answers first). No expiry, or one that does not parse, reads
72
+ * "in 7 days".
73
+ *
74
+ * With `placement: "fixed"` (`data-placement="fixed"` on the banner) the bar is
75
+ * fixed to the top of the viewport, and the script pads the body by the bar's
76
+ * height (rounded up, so a fractional bar never overlaps), on top of the body's
77
+ * own top padding, so it covers nothing. It keeps
78
+ * the padding in step on resize (the bar wraps on a narrow screen) and puts the
79
+ * body's own padding back on the x.
80
+ */
81
+ export declare const CLAIM_BANNER_SCRIPT: string;
82
+ export interface ClaimBannerOptions {
83
+ /**
84
+ * Where "Claim it" goes, in the same tab. The caller builds it with its
85
+ * query (`door`, `drop`, `entity`, `utm_source`); it is attribute-escaped here,
86
+ * and anything but http(s) becomes `#`.
87
+ */
88
+ claimUrl: string;
89
+ /**
90
+ * The drop's expiry, as the entity carries it (ISO 8601). Emitted as
91
+ * `data-expires-at`, escaped, and read by the inline script. Missing, empty or
92
+ * unreadable reads "in 7 days".
93
+ */
94
+ expiresAt?: string | Date | null;
95
+ /**
96
+ * The page's CSP nonce, for the wrapper page. Placed on the `<style>` and the
97
+ * `<script>`. Omit it on a page without a nonce-based CSP.
98
+ */
99
+ nonce?: string | null;
100
+ /**
101
+ * `flow` (the default): a block in the page's flow, for the wrapper page,
102
+ * which emits it before the iframe in a flex-column body; it pushes the site
103
+ * down. `fixed`: a bar fixed to the top of the viewport, for a page the caller
104
+ * cannot reorder (the legacy inline path appends it at the end of the
105
+ * dropper's own HTML); the script pads the body by its height, so it still
106
+ * pushes the content down instead of covering it.
107
+ */
108
+ placement?: ClaimBannerPlacement;
109
+ }
110
+ export type ClaimBannerPlacement = "flow" | "fixed";
111
+ /**
112
+ * The banner's markup alone: the bar, hidden, with the fallback count in its
113
+ * bytes. Needs `CLAIM_BANNER_CSS` on the page and `CLAIM_BANNER_SCRIPT` after it.
114
+ */
115
+ export declare function claimBannerMarkup({ claimUrl, expiresAt, placement }: Omit<ClaimBannerOptions, "nonce">): string;
116
+ /**
117
+ * The whole drop-in: the stylesheet, the banner, and the script that reveals
118
+ * it, in that order. One string, the same for every viewer of a drop; with a
119
+ * `nonce`, the same for every viewer of one response.
120
+ */
121
+ export declare function claimBanner({ claimUrl, expiresAt, nonce, placement }: ClaimBannerOptions): string;
122
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/claim-banner/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAOH,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAA;AAE7F,kEAAkE;AAClE,eAAO,MAAM,eAAe,oBAAoB,CAAA;AAEhD;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,0BAA0B,CAAA;AAE/D,sFAAsF;AACtF,eAAO,MAAM,0BAA0B,IAAI,CAAA;AAE3C,4DAA4D;AAC5D,eAAO,MAAM,kBAAkB,aAAa,CAAA;AAE5C,qCAAqC;AACrC,eAAO,MAAM,wBAAwB,wBAAwB,CAAA;AAE7D;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB;;;;;;GAAkE,CAAA;AAEtG;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAiBzB,CAAA;AAET,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;IAChC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,oBAAoB,CAAA;CACjC;AAED,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,OAAO,CAAA;AAoBnD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAkB,EAAE,EAAE,IAAI,CAAC,kBAAkB,EAAE,OAAO,CAAC,GAAG,MAAM,CAUxH;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,kBAAkB,GAAG,MAAM,CAGjG"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * THE GUEST CLAIM BANNER, FRAMEWORK-FREE (ui#342, for cloudgrid#4872).
3
+ *
4
+ * The top bar on every unclaimed guest drop: "This site disappears in N days
5
+ * unless it's claimed." and a lime "Claim it" pill, with a close x that hides
6
+ * it for the visit. The platform's inspiration-server builds HTML strings, so
7
+ * this is the badge's shape: a builder that returns a string, and the
8
+ * stylesheet and the inline script as strings beside it. No React, no DOM.
9
+ *
10
+ * ```ts
11
+ * import { claimBanner } from "@cloudgrid-io/ui/claim-banner"
12
+ * const ctaMarkup = claimBanner({ claimUrl, expiresAt: entity.inspiration_block?.expires_at, nonce })
13
+ * ```
14
+ *
15
+ * What the consumer's constraints settle (cloudgrid#4872):
16
+ *
17
+ * byte-identical nothing here depends on the viewer or the clock. The day
18
+ * count is computed by the inline script, in the browser,
19
+ * from `data-expires-at`, so a cached render is right for
20
+ * every viewer on every day
21
+ * no flash the banner is `hidden` in the bytes; the script reveals it
22
+ * unless this visit dismissed it (`sessionStorage`)
23
+ * nonce with `nonce`, the `<style>` and `<script>` carry it, for
24
+ * the wrapper's `script-src 'nonce-...'`; without it they
25
+ * carry nothing, for the legacy inline path
26
+ * escaping every caller value is text; the URL is attribute-escaped
27
+ * and refused (`#`) unless it is http(s)
28
+ * sentinel nothing here says `data-cloudgrid-badge`, so the badge
29
+ * injector's idempotency check never trips on it
30
+ * fonts no `@font-face` and no `@import`; the UI stack falls back
31
+ * to the system faces under `font-src 'self' api.cloudgrid.io`
32
+ */
33
+ import { esc, glyph, href, wordmark } from "../badge/html.js";
34
+ import { X } from "../badge/marks.js";
35
+ import { BADGE_FONT_FACES } from "../badge/palette.js";
36
+ import { CLAIM_BANNER_CSS } from "./css.js";
37
+ export { CLAIM_BANNER_CSS, CLAIM_BANNER_PHONE_MAX, CLAIM_BANNER_TOKEN_NAMES } from "./css.js";
38
+ /** The banner element's id. The script finds the banner by it. */
39
+ export const CLAIM_BANNER_ID = "cg-claim-banner";
40
+ /**
41
+ * The `sessionStorage` key the close x sets to "1": hidden for this visit, back
42
+ * in a new tab. The platform's own banner's key (cloudgrid#4978), so a viewer
43
+ * who dismissed that one is not shown this one again in the same visit.
44
+ */
45
+ export const CLAIM_BANNER_DISMISS_KEY = "cg_anon_cta_dismissed";
46
+ /** The days shown when the drop carries no expiry, or one the browser cannot read. */
47
+ export const CLAIM_BANNER_FALLBACK_DAYS = 7;
48
+ /** The button's label. The arrow after it is decorative. */
49
+ export const CLAIM_BANNER_LABEL = "Claim it";
50
+ /** The close x's accessible name. */
51
+ export const CLAIM_BANNER_CLOSE_LABEL = "Hide for this visit";
52
+ /**
53
+ * The one face the banner sets type in: Manrope, the badge's file. The banner
54
+ * loads nothing itself. On the wrapper the badge already registers it; a page
55
+ * without the badge hosts this file (the package carries it at
56
+ * `@cloudgrid-io/ui/brand/badge/<file>`) and declares it. Without it the system
57
+ * stack renders.
58
+ */
59
+ export const CLAIM_BANNER_FONT_FACES = BADGE_FONT_FACES.filter((f) => f.family === "Manrope Variable");
60
+ /**
61
+ * The inline script. ES5, no globals left behind. It finds the banner, leaves
62
+ * it hidden if this visit dismissed it, writes the day count, reveals it, and
63
+ * wires the close x. Storage that throws (a sandbox, a privacy mode) counts as
64
+ * "not dismissed", and the x still hides the banner for the page.
65
+ *
66
+ * The count is in whole calendar days in the viewer's time zone, from today to
67
+ * the expiry's date: "in N days", "in 1 day", "today" on the last day. A past
68
+ * expiry reads "today", never a negative number (the platform's expired page
69
+ * normally answers first). No expiry, or one that does not parse, reads
70
+ * "in 7 days".
71
+ *
72
+ * With `placement: "fixed"` (`data-placement="fixed"` on the banner) the bar is
73
+ * fixed to the top of the viewport, and the script pads the body by the bar's
74
+ * height (rounded up, so a fractional bar never overlaps), on top of the body's
75
+ * own top padding, so it covers nothing. It keeps
76
+ * the padding in step on resize (the bar wraps on a narrow screen) and puts the
77
+ * body's own padding back on the x.
78
+ */
79
+ export const CLAIM_BANNER_SCRIPT = "(function(){" +
80
+ `var b=document.getElementById(${JSON.stringify(CLAIM_BANNER_ID)});if(!b)return;` +
81
+ `var k=${JSON.stringify(CLAIM_BANNER_DISMISS_KEY)};` +
82
+ 'try{if(window.sessionStorage.getItem(k)==="1")return}catch(e){}' +
83
+ `var d=${CLAIM_BANNER_FALLBACK_DAYS},a=b.getAttribute("data-expires-at");` +
84
+ "if(a){var t=new Date(a);if(!isNaN(t.getTime())){var n=new Date();" +
85
+ "d=Math.round((new Date(t.getFullYear(),t.getMonth(),t.getDate())-new Date(n.getFullYear(),n.getMonth(),n.getDate()))/864e5);" +
86
+ "if(!(d>0))d=0}}" +
87
+ 'var w=b.querySelector("[data-claim-when]");' +
88
+ 'if(w)w.textContent=d===0?"today":"in "+d+(d===1?" day":" days");' +
89
+ "b.hidden=false;" +
90
+ 'var f=b.getAttribute("data-placement")==="fixed",y=document.body,s0="",p0=0;' +
91
+ 'function pad(){if(!b.hidden)y.style.paddingTop=(p0+Math.ceil(b.getBoundingClientRect().height))+"px"}' +
92
+ 'if(f&&y){s0=y.style.paddingTop;p0=parseFloat(window.getComputedStyle(y).paddingTop)||0;pad();window.addEventListener("resize",pad)}' +
93
+ 'var x=b.querySelector("[data-claim-dismiss]");' +
94
+ 'if(x)x.addEventListener("click",function(){b.hidden=true;if(f&&y)y.style.paddingTop=s0;try{window.sessionStorage.setItem(k,"1")}catch(e){}})' +
95
+ "})();";
96
+ /**
97
+ * A caller value for an attribute: escaped, and the badge injector's sentinel
98
+ * broken with a character reference, so even a slug that spells it out cannot
99
+ * put `data-cloudgrid-badge` in the bytes. The browser reads the value unchanged.
100
+ */
101
+ function safe(value) {
102
+ return value.replace(/data-cloudgrid-badge/gi, (m) => m.replace("-", "&#45;"));
103
+ }
104
+ function expiryAttr(expiresAt) {
105
+ if (expiresAt == null)
106
+ return "";
107
+ if (expiresAt instanceof Date) {
108
+ return Number.isNaN(expiresAt.getTime()) ? "" : ` data-expires-at="${safe(esc(expiresAt.toISOString()))}"`;
109
+ }
110
+ const value = String(expiresAt).trim();
111
+ return value ? ` data-expires-at="${safe(esc(value))}"` : "";
112
+ }
113
+ /**
114
+ * The banner's markup alone: the bar, hidden, with the fallback count in its
115
+ * bytes. Needs `CLAIM_BANNER_CSS` on the page and `CLAIM_BANNER_SCRIPT` after it.
116
+ */
117
+ export function claimBannerMarkup({ claimUrl, expiresAt, placement = "flow" }) {
118
+ const fixed = placement === "fixed" ? ' data-placement="fixed"' : "";
119
+ return (`<div id="${CLAIM_BANNER_ID}" class="cgcb" role="note"${fixed}${expiryAttr(expiresAt)} hidden>` +
120
+ `<span class="cgcb-mark">${wordmark("cgcb-wordmark")}</span>` +
121
+ `<span class="cgcb-msg"><b>This site disappears <span data-claim-when>in ${CLAIM_BANNER_FALLBACK_DAYS} days</span></b> unless it&#39;s claimed.</span>` +
122
+ `<a class="cgcb-btn" href="${safe(href(claimUrl))}">${CLAIM_BANNER_LABEL} <span aria-hidden="true">&rarr;</span></a>` +
123
+ `<button class="cgcb-x" type="button" data-claim-dismiss aria-label="${CLAIM_BANNER_CLOSE_LABEL}">${glyph(X)}</button>` +
124
+ `</div>`);
125
+ }
126
+ /**
127
+ * The whole drop-in: the stylesheet, the banner, and the script that reveals
128
+ * it, in that order. One string, the same for every viewer of a drop; with a
129
+ * `nonce`, the same for every viewer of one response.
130
+ */
131
+ export function claimBanner({ claimUrl, expiresAt, nonce, placement }) {
132
+ const n = nonce ? ` nonce="${safe(esc(nonce))}"` : "";
133
+ return `<style${n}>${CLAIM_BANNER_CSS}</style>` + claimBannerMarkup({ claimUrl, expiresAt, placement }) + `<script${n}>${CLAIM_BANNER_SCRIPT}</script>`;
134
+ }
135
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/claim-banner/index.ts"],"names":[],"mappings":"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"}