@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.
- package/README.md +25 -0
- package/dist/badge/css-live.d.ts +7 -4
- package/dist/badge/css-live.d.ts.map +1 -1
- package/dist/badge/css-live.js +9 -6
- package/dist/badge/css-live.js.map +1 -1
- package/dist/badge/palette.d.ts +1 -1
- package/dist/badge/palette.d.ts.map +1 -1
- package/dist/badge/palette.js +2 -1
- package/dist/badge/palette.js.map +1 -1
- package/dist/blocks/bar-chart.d.ts +97 -0
- package/dist/blocks/bar-chart.d.ts.map +1 -0
- package/dist/blocks/bar-chart.js +25 -0
- package/dist/blocks/bar-chart.js.map +1 -0
- package/dist/blocks/chart-parts.d.ts +101 -0
- package/dist/blocks/chart-parts.d.ts.map +1 -0
- package/dist/blocks/chart-parts.js +96 -0
- package/dist/blocks/chart-parts.js.map +1 -0
- package/dist/blocks/coming-soon-tag.d.ts +34 -0
- package/dist/blocks/coming-soon-tag.d.ts.map +1 -0
- package/dist/blocks/coming-soon-tag.js +37 -0
- package/dist/blocks/coming-soon-tag.js.map +1 -0
- package/dist/blocks/disabled-button.d.ts +43 -0
- package/dist/blocks/disabled-button.d.ts.map +1 -0
- package/dist/blocks/disabled-button.js +50 -0
- package/dist/blocks/disabled-button.js.map +1 -0
- package/dist/blocks/entity-card.d.ts.map +1 -1
- package/dist/blocks/entity-card.js +3 -1
- package/dist/blocks/entity-card.js.map +1 -1
- package/dist/blocks/full-screen-button.d.ts +9 -0
- package/dist/blocks/full-screen-button.d.ts.map +1 -0
- package/dist/blocks/full-screen-button.js +51 -0
- package/dist/blocks/full-screen-button.js.map +1 -0
- package/dist/blocks/icons.d.ts +30 -0
- package/dist/blocks/icons.d.ts.map +1 -1
- package/dist/blocks/icons.js +30 -0
- package/dist/blocks/icons.js.map +1 -1
- package/dist/blocks/meter.d.ts +57 -0
- package/dist/blocks/meter.d.ts.map +1 -0
- package/dist/blocks/meter.js +19 -0
- package/dist/blocks/meter.js.map +1 -0
- package/dist/blocks/notice.d.ts +87 -0
- package/dist/blocks/notice.d.ts.map +1 -0
- package/dist/blocks/notice.js +26 -0
- package/dist/blocks/notice.js.map +1 -0
- package/dist/blocks/pager.d.ts +76 -0
- package/dist/blocks/pager.d.ts.map +1 -0
- package/dist/blocks/pager.js +86 -0
- package/dist/blocks/pager.js.map +1 -0
- package/dist/blocks/phosphor-marks.d.ts +20 -0
- package/dist/blocks/phosphor-marks.d.ts.map +1 -1
- package/dist/blocks/phosphor-marks.js +30 -0
- package/dist/blocks/phosphor-marks.js.map +1 -1
- package/dist/blocks/ranked-list.d.ts +67 -0
- package/dist/blocks/ranked-list.d.ts.map +1 -0
- package/dist/blocks/ranked-list.js +32 -0
- package/dist/blocks/ranked-list.js.map +1 -0
- package/dist/blocks/side-nav.d.ts.map +1 -1
- package/dist/blocks/side-nav.js +3 -1
- package/dist/blocks/side-nav.js.map +1 -1
- package/dist/blocks/sign-in-options.d.ts.map +1 -1
- package/dist/blocks/sign-in-options.js +3 -1
- package/dist/blocks/sign-in-options.js.map +1 -1
- package/dist/blocks/stacked-bar.d.ts +64 -0
- package/dist/blocks/stacked-bar.d.ts.map +1 -0
- package/dist/blocks/stacked-bar.js +18 -0
- package/dist/blocks/stacked-bar.js.map +1 -0
- package/dist/blocks/stat-tile.d.ts +42 -1
- package/dist/blocks/stat-tile.d.ts.map +1 -1
- package/dist/blocks/stat-tile.js +23 -4
- package/dist/blocks/stat-tile.js.map +1 -1
- package/dist/blocks/tab-bar.d.ts.map +1 -1
- package/dist/blocks/tab-bar.js +3 -1
- package/dist/blocks/tab-bar.js.map +1 -1
- package/dist/blocks/timeline.d.ts +53 -0
- package/dist/blocks/timeline.d.ts.map +1 -0
- package/dist/blocks/timeline.js +18 -0
- package/dist/blocks/timeline.js.map +1 -0
- package/dist/blocks/tool-setup.d.ts.map +1 -1
- package/dist/blocks/tool-setup.js +3 -1
- package/dist/blocks/tool-setup.js.map +1 -1
- package/dist/blocks/trend.d.ts +33 -0
- package/dist/blocks/trend.d.ts.map +1 -0
- package/dist/blocks/trend.js +13 -0
- package/dist/blocks/trend.js.map +1 -0
- package/dist/claim-banner/css.d.ts +39 -0
- package/dist/claim-banner/css.d.ts.map +1 -0
- package/dist/claim-banner/css.js +87 -0
- package/dist/claim-banner/css.js.map +1 -0
- package/dist/claim-banner/index.d.ts +122 -0
- package/dist/claim-banner/index.d.ts.map +1 -0
- package/dist/claim-banner/index.js +135 -0
- package/dist/claim-banner/index.js.map +1 -0
- package/dist/docs.css +466 -0
- package/dist/expressive-code-palette.d.ts +5 -0
- package/dist/expressive-code-palette.d.ts.map +1 -0
- package/dist/expressive-code-palette.js +10 -0
- package/dist/expressive-code-palette.js.map +1 -0
- package/dist/expressive-code.d.ts +57 -0
- package/dist/expressive-code.d.ts.map +1 -0
- package/dist/expressive-code.js +101 -0
- package/dist/expressive-code.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/tokens/base.json +13 -0
- package/dist/tokens/surface-ink.json +13 -0
- package/dist/tokens/surface-panel.json +13 -0
- package/dist/tokens.css +36 -0
- package/dist/tokens.json +6 -3
- package/dist/ui/card.d.ts +23 -2
- package/dist/ui/card.d.ts.map +1 -1
- package/dist/ui/card.js +16 -5
- package/dist/ui/card.js.map +1 -1
- package/dist/ui/drawer.d.ts +37 -6
- package/dist/ui/drawer.d.ts.map +1 -1
- package/dist/ui/drawer.js +31 -7
- package/dist/ui/drawer.js.map +1 -1
- package/dist/ui/input-group.d.ts.map +1 -1
- package/dist/ui/input-group.js +3 -1
- package/dist/ui/input-group.js.map +1 -1
- 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("-", "-"));
|
|
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's claimed.</span>` +
|
|
122
|
+
`<a class="cgcb-btn" href="${safe(href(claimUrl))}">${CLAIM_BANNER_LABEL} <span aria-hidden="true">→</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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"}
|