@mrrocket/sdk 0.0.0-stage → 0.1.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/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +250 -2
- package/dist/api.d.ts +113 -0
- package/dist/api.js +71 -0
- package/dist/api.js.map +1 -0
- package/dist/browser.d.ts +11 -0
- package/dist/browser.js +25 -0
- package/dist/browser.js.map +1 -0
- package/dist/client.d.ts +68 -0
- package/dist/client.js +373 -0
- package/dist/client.js.map +1 -0
- package/dist/context.d.ts +31 -0
- package/dist/context.js +15 -0
- package/dist/context.js.map +1 -0
- package/dist/core.d.ts +107 -0
- package/dist/core.js +104 -0
- package/dist/core.js.map +1 -0
- package/dist/feedback.d.ts +28 -0
- package/dist/feedback.js +459 -0
- package/dist/feedback.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/proxy.d.ts +28 -0
- package/dist/proxy.js +12 -0
- package/dist/proxy.js.map +1 -0
- package/dist/referral.d.ts +86 -0
- package/dist/referral.js +128 -0
- package/dist/referral.js.map +1 -0
- package/dist/server.d.ts +47 -0
- package/dist/server.js +43 -0
- package/dist/server.js.map +1 -0
- package/dist/styles.d.ts +4 -0
- package/dist/styles.js +147 -0
- package/dist/styles.js.map +1 -0
- package/dist/survey.d.ts +4 -0
- package/dist/survey.js +201 -0
- package/dist/survey.js.map +1 -0
- package/dist/ui.d.ts +29 -0
- package/dist/ui.js +77 -0
- package/dist/ui.js.map +1 -0
- package/package.json +84 -5
package/dist/styles.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/styles.ts
|
|
4
|
+
import { useEffect } from "react";
|
|
5
|
+
var STYLE_ID = "ckw-styles";
|
|
6
|
+
var CSS = `
|
|
7
|
+
.ckw-root{--ckw-accent:#3f76ff;--ckw-accent-ink:#fff;--ckw-bg:#fff;--ckw-bg2:#f7f7f8;--ckw-bg3:#eeeff1;--ckw-fg:#1c1d20;--ckw-fg2:#5d6068;--ckw-fg3:#8b8e96;--ckw-line:#e4e5e8;--ckw-danger:#d92d20;--ckw-shadow:0 8px 30px rgba(16,18,24,.16);
|
|
8
|
+
font-size:14px;line-height:1.45;font-family:inherit;color:var(--ckw-fg);box-sizing:border-box;-webkit-font-smoothing:antialiased}
|
|
9
|
+
.ckw-root[data-theme=dark]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}
|
|
10
|
+
@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}}
|
|
11
|
+
.ckw-root *,.ckw-root *::before,.ckw-root *::after{box-sizing:border-box}
|
|
12
|
+
.ckw-root button,.ckw-root input,.ckw-root select,.ckw-root textarea{font:inherit;color:inherit;margin:0}
|
|
13
|
+
.ckw-root button{cursor:pointer}
|
|
14
|
+
.ckw-root :focus-visible{outline:2px solid var(--ckw-accent);outline-offset:2px}
|
|
15
|
+
.ckw-board{width:100%;max-width:760px;margin:0 auto;background:var(--ckw-bg);min-width:0}
|
|
16
|
+
.ckw-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:12px}
|
|
17
|
+
.ckw-title{font-size:20px;font-weight:600;margin:0;letter-spacing:-.01em}
|
|
18
|
+
.ckw-sub{color:var(--ckw-fg2);margin:2px 0 0;font-size:13px}
|
|
19
|
+
.ckw-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:32px;padding:0 12px;border-radius:8px;border:1px solid var(--ckw-line);background:var(--ckw-bg);font-weight:500;font-size:13px;white-space:nowrap}
|
|
20
|
+
.ckw-btn:hover{background:var(--ckw-bg2)}
|
|
21
|
+
.ckw-btn[disabled]{opacity:.55;cursor:not-allowed}
|
|
22
|
+
.ckw-btn-primary{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}
|
|
23
|
+
.ckw-btn-primary:hover{background:var(--ckw-accent);filter:brightness(1.08)}
|
|
24
|
+
.ckw-btn-ghost{border-color:transparent;background:transparent;color:var(--ckw-fg2)}
|
|
25
|
+
.ckw-icon{width:16px;height:16px;flex:none;display:block}
|
|
26
|
+
.ckw-tabs{display:flex;gap:4px;border-bottom:1px solid var(--ckw-line);margin-bottom:12px;overflow-x:auto}
|
|
27
|
+
.ckw-tab{background:none;border:0;border-bottom:2px solid transparent;padding:8px 10px;margin-bottom:-1px;color:var(--ckw-fg2);font-weight:500;font-size:13px;white-space:nowrap}
|
|
28
|
+
.ckw-tab[aria-selected=true]{color:var(--ckw-fg);border-bottom-color:var(--ckw-accent)}
|
|
29
|
+
.ckw-tools{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:10px}
|
|
30
|
+
.ckw-input,.ckw-select,.ckw-textarea{width:100%;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);padding:7px 10px;font-size:14px;min-height:34px}
|
|
31
|
+
.ckw-input::placeholder,.ckw-textarea::placeholder{color:var(--ckw-fg3)}
|
|
32
|
+
.ckw-textarea{resize:vertical;min-height:72px}
|
|
33
|
+
.ckw-search{flex:1 1 160px;min-width:0}
|
|
34
|
+
.ckw-seg{display:inline-flex;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}
|
|
35
|
+
.ckw-seg button{border:0;background:var(--ckw-bg);padding:0 10px;height:32px;font-size:13px;color:var(--ckw-fg2)}
|
|
36
|
+
.ckw-seg button[aria-pressed=true]{background:var(--ckw-bg3);color:var(--ckw-fg);font-weight:500}
|
|
37
|
+
.ckw-chips{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}
|
|
38
|
+
.ckw-chip{border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:999px;padding:3px 10px;font-size:12px;color:var(--ckw-fg2)}
|
|
39
|
+
.ckw-chip[aria-pressed=true]{background:var(--ckw-fg);color:var(--ckw-bg);border-color:var(--ckw-fg)}
|
|
40
|
+
.ckw-list{list-style:none;margin:0;padding:0;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}
|
|
41
|
+
.ckw-list>li+li{border-top:1px solid var(--ckw-line)}
|
|
42
|
+
.ckw-row{display:flex;gap:12px;align-items:flex-start;padding:10px 12px;background:var(--ckw-bg)}
|
|
43
|
+
.ckw-row:hover{background:var(--ckw-bg2)}
|
|
44
|
+
.ckw-vote{flex:none;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0;width:44px;min-height:48px;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);font-size:13px;font-weight:600;font-variant-numeric:tabular-nums;padding:4px 0;color:var(--ckw-fg2)}
|
|
45
|
+
.ckw-vote:hover{border-color:var(--ckw-accent);color:var(--ckw-accent)}
|
|
46
|
+
.ckw-vote[aria-pressed=true]{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}
|
|
47
|
+
.ckw-vote-sm{width:auto;min-height:0;flex-direction:row;gap:4px;padding:2px 8px}
|
|
48
|
+
.ckw-main{flex:1;min-width:0;text-align:left}
|
|
49
|
+
.ckw-link{display:block;width:100%;text-align:left;background:none;border:0;padding:0;font-weight:600;font-size:14px;color:var(--ckw-fg);overflow-wrap:anywhere}
|
|
50
|
+
.ckw-link:hover{color:var(--ckw-accent)}
|
|
51
|
+
.ckw-excerpt{margin:2px 0 0;color:var(--ckw-fg2);font-size:13px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}
|
|
52
|
+
.ckw-meta{display:flex;gap:8px;align-items:center;margin-top:6px;color:var(--ckw-fg3);font-size:12px;flex-wrap:wrap}
|
|
53
|
+
.ckw-pill{display:inline-flex;align-items:center;border-radius:999px;padding:1px 8px;font-size:11px;font-weight:600;background:var(--ckw-bg3);color:var(--ckw-fg2)}
|
|
54
|
+
.ckw-pill[data-s=planned]{background:rgba(63,118,255,.14);color:#3f76ff}
|
|
55
|
+
.ckw-pill[data-s=progress]{background:rgba(245,158,11,.16);color:#b45309}
|
|
56
|
+
.ckw-pill[data-s=shipped]{background:rgba(22,163,74,.15);color:#15803d}
|
|
57
|
+
.ckw-root[data-theme=dark] .ckw-pill[data-s=progress]{color:#fbbf24}
|
|
58
|
+
.ckw-root[data-theme=dark] .ckw-pill[data-s=shipped]{color:#4ade80}
|
|
59
|
+
@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto] .ckw-pill[data-s=progress]{color:#fbbf24}.ckw-root[data-theme=auto] .ckw-pill[data-s=shipped]{color:#4ade80}}
|
|
60
|
+
.ckw-stat{display:inline-flex;align-items:center;gap:4px}
|
|
61
|
+
.ckw-empty{border:1px dashed var(--ckw-line);border-radius:8px;padding:28px 16px;text-align:center;color:var(--ckw-fg2)}
|
|
62
|
+
.ckw-error{border:1px solid color-mix(in srgb,var(--ckw-danger) 40%,transparent);background:color-mix(in srgb,var(--ckw-danger) 8%,transparent);color:var(--ckw-danger);border-radius:8px;padding:8px 10px;font-size:13px;display:flex;gap:8px;align-items:center;justify-content:space-between;margin-bottom:10px}
|
|
63
|
+
.ckw-skel{height:68px;border-radius:8px;background:var(--ckw-bg2);animation:ckw-sh 1.2s ease-in-out infinite;margin-bottom:6px}
|
|
64
|
+
@keyframes ckw-sh{50%{opacity:.5}}
|
|
65
|
+
@media (prefers-reduced-motion:reduce){.ckw-skel{animation:none}.ckw-root *{transition:none!important}}
|
|
66
|
+
.ckw-form{display:flex;flex-direction:column;gap:10px;border:1px solid var(--ckw-line);border-radius:8px;padding:12px;margin-bottom:12px;background:var(--ckw-bg)}
|
|
67
|
+
.ckw-field{display:flex;flex-direction:column;gap:4px}
|
|
68
|
+
.ckw-label{font-size:12px;font-weight:600;color:var(--ckw-fg2)}
|
|
69
|
+
.ckw-actions{display:flex;gap:8px;justify-content:flex-end}
|
|
70
|
+
.ckw-dupes{border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg2);padding:8px 10px;font-size:13px}
|
|
71
|
+
.ckw-dupes p{margin:0 0 4px;color:var(--ckw-fg2);font-size:12px}
|
|
72
|
+
.ckw-dupe{display:flex;gap:8px;align-items:center;justify-content:space-between;padding:3px 0}
|
|
73
|
+
.ckw-dupe span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
74
|
+
.ckw-cols{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}
|
|
75
|
+
@media (max-width:640px){.ckw-cols{grid-template-columns:1fr}}
|
|
76
|
+
.ckw-col h3{font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:var(--ckw-fg2);margin:0 0 8px;display:flex;gap:6px;align-items:center}
|
|
77
|
+
.ckw-card{border:1px solid var(--ckw-line);border-radius:8px;padding:10px;margin-bottom:8px;background:var(--ckw-bg)}
|
|
78
|
+
.ckw-card .ckw-link{margin-bottom:6px}
|
|
79
|
+
.ckw-entry{padding:12px 0;border-bottom:1px solid var(--ckw-line)}
|
|
80
|
+
.ckw-entry time{font-size:12px;color:var(--ckw-fg3)}
|
|
81
|
+
.ckw-entry h3{font-size:15px;margin:2px 0 4px}
|
|
82
|
+
.ckw-entry p{margin:0;color:var(--ckw-fg2);white-space:pre-wrap;overflow-wrap:anywhere}
|
|
83
|
+
.ckw-back{margin-bottom:8px;padding-left:4px}
|
|
84
|
+
.ckw-detail-head{display:flex;gap:12px;align-items:flex-start}
|
|
85
|
+
.ckw-detail-head h2{margin:0 0 4px;font-size:18px;overflow-wrap:anywhere}
|
|
86
|
+
.ckw-body{white-space:pre-wrap;overflow-wrap:anywhere;color:var(--ckw-fg2);margin:12px 0}
|
|
87
|
+
.ckw-h{font-size:13px;font-weight:600;margin:16px 0 8px}
|
|
88
|
+
.ckw-comment{padding:8px 0;border-top:1px solid var(--ckw-line)}
|
|
89
|
+
.ckw-comment b{font-size:13px}
|
|
90
|
+
.ckw-comment p{margin:2px 0 0;white-space:pre-wrap;overflow-wrap:anywhere}
|
|
91
|
+
.ckw-team{margin-left:6px;background:var(--ckw-accent);color:var(--ckw-accent-ink);border-radius:4px;padding:0 5px;font-size:10px;font-weight:700;text-transform:uppercase;vertical-align:1px}
|
|
92
|
+
.ckw-time{color:var(--ckw-fg3);font-size:12px;margin-left:6px}
|
|
93
|
+
.ckw-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
|
|
94
|
+
/* Floating widget */
|
|
95
|
+
.ckw-fab{position:fixed;z-index:2147483000;bottom:20px;display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 16px;border-radius:999px;border:0;background:var(--ckw-accent);color:var(--ckw-accent-ink);font-weight:600;font-size:14px;box-shadow:var(--ckw-shadow)}
|
|
96
|
+
.ckw-fab[data-pos=bottom-right],.ckw-pop[data-pos=bottom-right]{right:20px}
|
|
97
|
+
.ckw-fab[data-pos=bottom-left],.ckw-pop[data-pos=bottom-left]{left:20px}
|
|
98
|
+
.ckw-pop{position:fixed;z-index:2147483001;bottom:70px;width:380px;max-width:calc(100vw - 24px);max-height:min(560px,calc(100vh - 90px));display:flex;flex-direction:column;background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);overflow:hidden}
|
|
99
|
+
.ckw-pop-head{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--ckw-line);font-weight:600}
|
|
100
|
+
.ckw-pop-body{padding:12px;overflow-y:auto;flex:1}
|
|
101
|
+
.ckw-pop-foot{padding:8px 12px;border-top:1px solid var(--ckw-line);display:flex;justify-content:space-between;align-items:center;font-size:13px}
|
|
102
|
+
.ckw-pop-foot a{color:var(--ckw-accent);text-decoration:none;font-weight:500}
|
|
103
|
+
.ckw-pop .ckw-list{border:0;border-radius:0}
|
|
104
|
+
.ckw-pop .ckw-row{padding:8px 0}
|
|
105
|
+
.ckw-pop .ckw-form{border:0;padding:0;margin:12px 0 0}
|
|
106
|
+
@media (max-width:520px){.ckw-pop{left:0!important;right:0!important;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0;max-height:85vh}.ckw-fab{bottom:14px}.ckw-fab[data-pos=bottom-right]{right:14px}.ckw-fab[data-pos=bottom-left]{left:14px}}
|
|
107
|
+
/* Survey */
|
|
108
|
+
.ckw-survey{position:fixed;z-index:2147483000;left:20px;bottom:20px;width:360px;max-width:calc(100vw - 24px);background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);padding:14px;animation:ckw-in .2s ease-out}
|
|
109
|
+
@keyframes ckw-in{from{opacity:0;transform:translateY(8px)}}
|
|
110
|
+
@media (max-width:520px){.ckw-survey{left:0;right:0;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0}}
|
|
111
|
+
.ckw-sv-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}
|
|
112
|
+
.ckw-dots{display:flex;gap:4px}
|
|
113
|
+
.ckw-dot{width:6px;height:6px;border-radius:50%;background:var(--ckw-bg3)}
|
|
114
|
+
.ckw-dot[data-on=true]{background:var(--ckw-accent)}
|
|
115
|
+
.ckw-q{font-weight:600;font-size:15px;margin:0 0 2px;border:0;padding:0}
|
|
116
|
+
.ckw-root .ckw-q:focus-visible{outline:none}
|
|
117
|
+
.ckw-qd{color:var(--ckw-fg2);font-size:13px;margin:0 0 10px}
|
|
118
|
+
.ckw-scale{display:flex;gap:4px;flex-wrap:wrap}
|
|
119
|
+
.ckw-num{flex:1 1 0;min-width:26px;height:34px;border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:8px;font-size:13px;font-weight:600;padding:0}
|
|
120
|
+
.ckw-num:hover{border-color:var(--ckw-accent)}
|
|
121
|
+
.ckw-num[aria-pressed=true]{background:var(--ckw-accent);color:var(--ckw-accent-ink);border-color:var(--ckw-accent)}
|
|
122
|
+
.ckw-star{background:none;border:0;padding:2px;color:var(--ckw-bg3)}
|
|
123
|
+
.ckw-star[data-on=true]{color:#f5a524}
|
|
124
|
+
.ckw-star svg{width:28px;height:28px}
|
|
125
|
+
.ckw-lh{display:flex;justify-content:space-between;color:var(--ckw-fg3);font-size:11px;margin-top:4px}
|
|
126
|
+
.ckw-opts{display:flex;flex-direction:column;gap:6px}
|
|
127
|
+
.ckw-opt{display:flex;gap:8px;align-items:center;border:1px solid var(--ckw-line);border-radius:8px;padding:7px 10px;background:var(--ckw-bg);cursor:pointer}
|
|
128
|
+
.ckw-opt:has(input:checked){border-color:var(--ckw-accent);background:color-mix(in srgb,var(--ckw-accent) 8%,transparent)}
|
|
129
|
+
.ckw-opt input{accent-color:var(--ckw-accent)}
|
|
130
|
+
.ckw-sv-foot{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-top:12px}
|
|
131
|
+
.ckw-thanks{text-align:center;padding:14px 0;display:flex;flex-direction:column;align-items:center;gap:8px}
|
|
132
|
+
.ckw-tick{width:32px;height:32px;border-radius:50%;background:var(--ckw-accent);color:var(--ckw-accent-ink);display:flex;align-items:center;justify-content:center}
|
|
133
|
+
`;
|
|
134
|
+
var useStyles = () => {
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
137
|
+
const el = document.createElement("style");
|
|
138
|
+
el.id = STYLE_ID;
|
|
139
|
+
el.textContent = CSS;
|
|
140
|
+
document.head.appendChild(el);
|
|
141
|
+
}, []);
|
|
142
|
+
};
|
|
143
|
+
export {
|
|
144
|
+
CSS,
|
|
145
|
+
useStyles
|
|
146
|
+
};
|
|
147
|
+
//# sourceMappingURL=styles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/styles.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\n\nconst STYLE_ID = \"ckw-styles\";\n\n/** The one stylesheet for every Cockpit widget. Everything is scoped under `.ckw-root` and prefixed `ckw-`. */\nexport const CSS = `\n.ckw-root{--ckw-accent:#3f76ff;--ckw-accent-ink:#fff;--ckw-bg:#fff;--ckw-bg2:#f7f7f8;--ckw-bg3:#eeeff1;--ckw-fg:#1c1d20;--ckw-fg2:#5d6068;--ckw-fg3:#8b8e96;--ckw-line:#e4e5e8;--ckw-danger:#d92d20;--ckw-shadow:0 8px 30px rgba(16,18,24,.16);\nfont-size:14px;line-height:1.45;font-family:inherit;color:var(--ckw-fg);box-sizing:border-box;-webkit-font-smoothing:antialiased}\n.ckw-root[data-theme=dark]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}\n@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}}\n.ckw-root *,.ckw-root *::before,.ckw-root *::after{box-sizing:border-box}\n.ckw-root button,.ckw-root input,.ckw-root select,.ckw-root textarea{font:inherit;color:inherit;margin:0}\n.ckw-root button{cursor:pointer}\n.ckw-root :focus-visible{outline:2px solid var(--ckw-accent);outline-offset:2px}\n.ckw-board{width:100%;max-width:760px;margin:0 auto;background:var(--ckw-bg);min-width:0}\n.ckw-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:12px}\n.ckw-title{font-size:20px;font-weight:600;margin:0;letter-spacing:-.01em}\n.ckw-sub{color:var(--ckw-fg2);margin:2px 0 0;font-size:13px}\n.ckw-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:32px;padding:0 12px;border-radius:8px;border:1px solid var(--ckw-line);background:var(--ckw-bg);font-weight:500;font-size:13px;white-space:nowrap}\n.ckw-btn:hover{background:var(--ckw-bg2)}\n.ckw-btn[disabled]{opacity:.55;cursor:not-allowed}\n.ckw-btn-primary{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}\n.ckw-btn-primary:hover{background:var(--ckw-accent);filter:brightness(1.08)}\n.ckw-btn-ghost{border-color:transparent;background:transparent;color:var(--ckw-fg2)}\n.ckw-icon{width:16px;height:16px;flex:none;display:block}\n.ckw-tabs{display:flex;gap:4px;border-bottom:1px solid var(--ckw-line);margin-bottom:12px;overflow-x:auto}\n.ckw-tab{background:none;border:0;border-bottom:2px solid transparent;padding:8px 10px;margin-bottom:-1px;color:var(--ckw-fg2);font-weight:500;font-size:13px;white-space:nowrap}\n.ckw-tab[aria-selected=true]{color:var(--ckw-fg);border-bottom-color:var(--ckw-accent)}\n.ckw-tools{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:10px}\n.ckw-input,.ckw-select,.ckw-textarea{width:100%;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);padding:7px 10px;font-size:14px;min-height:34px}\n.ckw-input::placeholder,.ckw-textarea::placeholder{color:var(--ckw-fg3)}\n.ckw-textarea{resize:vertical;min-height:72px}\n.ckw-search{flex:1 1 160px;min-width:0}\n.ckw-seg{display:inline-flex;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}\n.ckw-seg button{border:0;background:var(--ckw-bg);padding:0 10px;height:32px;font-size:13px;color:var(--ckw-fg2)}\n.ckw-seg button[aria-pressed=true]{background:var(--ckw-bg3);color:var(--ckw-fg);font-weight:500}\n.ckw-chips{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}\n.ckw-chip{border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:999px;padding:3px 10px;font-size:12px;color:var(--ckw-fg2)}\n.ckw-chip[aria-pressed=true]{background:var(--ckw-fg);color:var(--ckw-bg);border-color:var(--ckw-fg)}\n.ckw-list{list-style:none;margin:0;padding:0;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}\n.ckw-list>li+li{border-top:1px solid var(--ckw-line)}\n.ckw-row{display:flex;gap:12px;align-items:flex-start;padding:10px 12px;background:var(--ckw-bg)}\n.ckw-row:hover{background:var(--ckw-bg2)}\n.ckw-vote{flex:none;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0;width:44px;min-height:48px;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);font-size:13px;font-weight:600;font-variant-numeric:tabular-nums;padding:4px 0;color:var(--ckw-fg2)}\n.ckw-vote:hover{border-color:var(--ckw-accent);color:var(--ckw-accent)}\n.ckw-vote[aria-pressed=true]{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}\n.ckw-vote-sm{width:auto;min-height:0;flex-direction:row;gap:4px;padding:2px 8px}\n.ckw-main{flex:1;min-width:0;text-align:left}\n.ckw-link{display:block;width:100%;text-align:left;background:none;border:0;padding:0;font-weight:600;font-size:14px;color:var(--ckw-fg);overflow-wrap:anywhere}\n.ckw-link:hover{color:var(--ckw-accent)}\n.ckw-excerpt{margin:2px 0 0;color:var(--ckw-fg2);font-size:13px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}\n.ckw-meta{display:flex;gap:8px;align-items:center;margin-top:6px;color:var(--ckw-fg3);font-size:12px;flex-wrap:wrap}\n.ckw-pill{display:inline-flex;align-items:center;border-radius:999px;padding:1px 8px;font-size:11px;font-weight:600;background:var(--ckw-bg3);color:var(--ckw-fg2)}\n.ckw-pill[data-s=planned]{background:rgba(63,118,255,.14);color:#3f76ff}\n.ckw-pill[data-s=progress]{background:rgba(245,158,11,.16);color:#b45309}\n.ckw-pill[data-s=shipped]{background:rgba(22,163,74,.15);color:#15803d}\n.ckw-root[data-theme=dark] .ckw-pill[data-s=progress]{color:#fbbf24}\n.ckw-root[data-theme=dark] .ckw-pill[data-s=shipped]{color:#4ade80}\n@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto] .ckw-pill[data-s=progress]{color:#fbbf24}.ckw-root[data-theme=auto] .ckw-pill[data-s=shipped]{color:#4ade80}}\n.ckw-stat{display:inline-flex;align-items:center;gap:4px}\n.ckw-empty{border:1px dashed var(--ckw-line);border-radius:8px;padding:28px 16px;text-align:center;color:var(--ckw-fg2)}\n.ckw-error{border:1px solid color-mix(in srgb,var(--ckw-danger) 40%,transparent);background:color-mix(in srgb,var(--ckw-danger) 8%,transparent);color:var(--ckw-danger);border-radius:8px;padding:8px 10px;font-size:13px;display:flex;gap:8px;align-items:center;justify-content:space-between;margin-bottom:10px}\n.ckw-skel{height:68px;border-radius:8px;background:var(--ckw-bg2);animation:ckw-sh 1.2s ease-in-out infinite;margin-bottom:6px}\n@keyframes ckw-sh{50%{opacity:.5}}\n@media (prefers-reduced-motion:reduce){.ckw-skel{animation:none}.ckw-root *{transition:none!important}}\n.ckw-form{display:flex;flex-direction:column;gap:10px;border:1px solid var(--ckw-line);border-radius:8px;padding:12px;margin-bottom:12px;background:var(--ckw-bg)}\n.ckw-field{display:flex;flex-direction:column;gap:4px}\n.ckw-label{font-size:12px;font-weight:600;color:var(--ckw-fg2)}\n.ckw-actions{display:flex;gap:8px;justify-content:flex-end}\n.ckw-dupes{border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg2);padding:8px 10px;font-size:13px}\n.ckw-dupes p{margin:0 0 4px;color:var(--ckw-fg2);font-size:12px}\n.ckw-dupe{display:flex;gap:8px;align-items:center;justify-content:space-between;padding:3px 0}\n.ckw-dupe span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.ckw-cols{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}\n@media (max-width:640px){.ckw-cols{grid-template-columns:1fr}}\n.ckw-col h3{font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:var(--ckw-fg2);margin:0 0 8px;display:flex;gap:6px;align-items:center}\n.ckw-card{border:1px solid var(--ckw-line);border-radius:8px;padding:10px;margin-bottom:8px;background:var(--ckw-bg)}\n.ckw-card .ckw-link{margin-bottom:6px}\n.ckw-entry{padding:12px 0;border-bottom:1px solid var(--ckw-line)}\n.ckw-entry time{font-size:12px;color:var(--ckw-fg3)}\n.ckw-entry h3{font-size:15px;margin:2px 0 4px}\n.ckw-entry p{margin:0;color:var(--ckw-fg2);white-space:pre-wrap;overflow-wrap:anywhere}\n.ckw-back{margin-bottom:8px;padding-left:4px}\n.ckw-detail-head{display:flex;gap:12px;align-items:flex-start}\n.ckw-detail-head h2{margin:0 0 4px;font-size:18px;overflow-wrap:anywhere}\n.ckw-body{white-space:pre-wrap;overflow-wrap:anywhere;color:var(--ckw-fg2);margin:12px 0}\n.ckw-h{font-size:13px;font-weight:600;margin:16px 0 8px}\n.ckw-comment{padding:8px 0;border-top:1px solid var(--ckw-line)}\n.ckw-comment b{font-size:13px}\n.ckw-comment p{margin:2px 0 0;white-space:pre-wrap;overflow-wrap:anywhere}\n.ckw-team{margin-left:6px;background:var(--ckw-accent);color:var(--ckw-accent-ink);border-radius:4px;padding:0 5px;font-size:10px;font-weight:700;text-transform:uppercase;vertical-align:1px}\n.ckw-time{color:var(--ckw-fg3);font-size:12px;margin-left:6px}\n.ckw-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}\n/* Floating widget */\n.ckw-fab{position:fixed;z-index:2147483000;bottom:20px;display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 16px;border-radius:999px;border:0;background:var(--ckw-accent);color:var(--ckw-accent-ink);font-weight:600;font-size:14px;box-shadow:var(--ckw-shadow)}\n.ckw-fab[data-pos=bottom-right],.ckw-pop[data-pos=bottom-right]{right:20px}\n.ckw-fab[data-pos=bottom-left],.ckw-pop[data-pos=bottom-left]{left:20px}\n.ckw-pop{position:fixed;z-index:2147483001;bottom:70px;width:380px;max-width:calc(100vw - 24px);max-height:min(560px,calc(100vh - 90px));display:flex;flex-direction:column;background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);overflow:hidden}\n.ckw-pop-head{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--ckw-line);font-weight:600}\n.ckw-pop-body{padding:12px;overflow-y:auto;flex:1}\n.ckw-pop-foot{padding:8px 12px;border-top:1px solid var(--ckw-line);display:flex;justify-content:space-between;align-items:center;font-size:13px}\n.ckw-pop-foot a{color:var(--ckw-accent);text-decoration:none;font-weight:500}\n.ckw-pop .ckw-list{border:0;border-radius:0}\n.ckw-pop .ckw-row{padding:8px 0}\n.ckw-pop .ckw-form{border:0;padding:0;margin:12px 0 0}\n@media (max-width:520px){.ckw-pop{left:0!important;right:0!important;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0;max-height:85vh}.ckw-fab{bottom:14px}.ckw-fab[data-pos=bottom-right]{right:14px}.ckw-fab[data-pos=bottom-left]{left:14px}}\n/* Survey */\n.ckw-survey{position:fixed;z-index:2147483000;left:20px;bottom:20px;width:360px;max-width:calc(100vw - 24px);background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);padding:14px;animation:ckw-in .2s ease-out}\n@keyframes ckw-in{from{opacity:0;transform:translateY(8px)}}\n@media (max-width:520px){.ckw-survey{left:0;right:0;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0}}\n.ckw-sv-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}\n.ckw-dots{display:flex;gap:4px}\n.ckw-dot{width:6px;height:6px;border-radius:50%;background:var(--ckw-bg3)}\n.ckw-dot[data-on=true]{background:var(--ckw-accent)}\n.ckw-q{font-weight:600;font-size:15px;margin:0 0 2px;border:0;padding:0}\n.ckw-root .ckw-q:focus-visible{outline:none}\n.ckw-qd{color:var(--ckw-fg2);font-size:13px;margin:0 0 10px}\n.ckw-scale{display:flex;gap:4px;flex-wrap:wrap}\n.ckw-num{flex:1 1 0;min-width:26px;height:34px;border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:8px;font-size:13px;font-weight:600;padding:0}\n.ckw-num:hover{border-color:var(--ckw-accent)}\n.ckw-num[aria-pressed=true]{background:var(--ckw-accent);color:var(--ckw-accent-ink);border-color:var(--ckw-accent)}\n.ckw-star{background:none;border:0;padding:2px;color:var(--ckw-bg3)}\n.ckw-star[data-on=true]{color:#f5a524}\n.ckw-star svg{width:28px;height:28px}\n.ckw-lh{display:flex;justify-content:space-between;color:var(--ckw-fg3);font-size:11px;margin-top:4px}\n.ckw-opts{display:flex;flex-direction:column;gap:6px}\n.ckw-opt{display:flex;gap:8px;align-items:center;border:1px solid var(--ckw-line);border-radius:8px;padding:7px 10px;background:var(--ckw-bg);cursor:pointer}\n.ckw-opt:has(input:checked){border-color:var(--ckw-accent);background:color-mix(in srgb,var(--ckw-accent) 8%,transparent)}\n.ckw-opt input{accent-color:var(--ckw-accent)}\n.ckw-sv-foot{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-top:12px}\n.ckw-thanks{text-align:center;padding:14px 0;display:flex;flex-direction:column;align-items:center;gap:8px}\n.ckw-tick{width:32px;height:32px;border-radius:50%;background:var(--ckw-accent);color:var(--ckw-accent-ink);display:flex;align-items:center;justify-content:center}\n`;\n\n/** Puts the stylesheet into the page once, however many widgets mount. */\nexport const useStyles = () => {\n useEffect(() => {\n if (document.getElementById(STYLE_ID)) return;\n const el = document.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = CSS;\n document.head.appendChild(el);\n }, []);\n};\n"],"mappings":";;;AAEA,SAAS,iBAAiB;AAE1B,IAAM,WAAW;AAGV,IAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkIZ,IAAM,YAAY,MAAM;AAC3B,YAAU,MAAM;AACZ,QAAI,SAAS,eAAe,QAAQ,EAAG;AACvC,UAAM,KAAK,SAAS,cAAc,OAAO;AACzC,OAAG,KAAK;AACR,OAAG,cAAc;AACjB,aAAS,KAAK,YAAY,EAAE;AAAA,EAChC,GAAG,CAAC,CAAC;AACT;","names":[]}
|
package/dist/survey.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Consent for storage was withdrawn: move the stored dismissals into memory for this page load and delete them. */
|
|
2
|
+
export declare const forgetStoredDismissals: () => void;
|
|
3
|
+
/** Asks the visitor the survey Cockpit picked for this page. Mounted by CockpitProvider unless `surveys={false}`. */
|
|
4
|
+
export declare const Survey: () => import("react").JSX.Element | null;
|
package/dist/survey.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/survey.tsx
|
|
4
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
5
|
+
import { useApi } from "./api.js";
|
|
6
|
+
import { useCtx } from "./context.js";
|
|
7
|
+
import { Check, Cross, Root, Star } from "./ui.js";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
var DISMISS_PREFIX = "ck_survey_";
|
|
10
|
+
var dismissed = /* @__PURE__ */ new Set();
|
|
11
|
+
var remember = (id, persist) => {
|
|
12
|
+
dismissed.add(id);
|
|
13
|
+
if (!persist) return;
|
|
14
|
+
try {
|
|
15
|
+
localStorage.setItem(`${DISMISS_PREFIX}${id}`, "1");
|
|
16
|
+
} catch {
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var wasDismissed = (id, persist) => {
|
|
20
|
+
if (dismissed.has(id)) return true;
|
|
21
|
+
if (!persist) return false;
|
|
22
|
+
try {
|
|
23
|
+
return !!localStorage.getItem(`${DISMISS_PREFIX}${id}`);
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var forgetStoredDismissals = () => {
|
|
29
|
+
try {
|
|
30
|
+
for (let i = localStorage.length - 1; i >= 0; i--) {
|
|
31
|
+
const key = localStorage.key(i);
|
|
32
|
+
if (!key?.startsWith(DISMISS_PREFIX)) continue;
|
|
33
|
+
dismissed.add(key.slice(DISMISS_PREFIX.length));
|
|
34
|
+
localStorage.removeItem(key);
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var isAnswered = (a) => Array.isArray(a) ? a.length > 0 : typeof a === "number" ? true : !!a?.trim();
|
|
40
|
+
var Question = ({ q, value, onChange, onEnter }) => {
|
|
41
|
+
if (q.kind === "nps" || q.kind === "rating") {
|
|
42
|
+
const scale = q.kind === "nps" ? 11 : Math.min(Math.max(q.scale ?? 5, 2), 10);
|
|
43
|
+
const start = q.kind === "nps" ? 0 : 1;
|
|
44
|
+
const items = Array.from({ length: scale }, (_, i) => i + start);
|
|
45
|
+
const stars = q.kind === "rating" && scale <= 5;
|
|
46
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
47
|
+
/* @__PURE__ */ jsx("div", { className: "ckw-scale", role: "group", "aria-label": q.title, children: items.map(
|
|
48
|
+
(n) => stars ? /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-star", "data-on": typeof value === "number" && n <= value, "aria-pressed": value === n, "aria-label": `${n} of ${scale}`, onClick: () => onChange(n), children: /* @__PURE__ */ jsx(Star, {}) }, n) : /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-num", "aria-pressed": value === n, onClick: () => onChange(n), children: n }, n)
|
|
49
|
+
) }),
|
|
50
|
+
(q.low || q.high || q.kind === "nps") && /* @__PURE__ */ jsxs("div", { className: "ckw-lh", children: [
|
|
51
|
+
/* @__PURE__ */ jsx("span", { children: q.low ?? (q.kind === "nps" ? "Not likely" : "") }),
|
|
52
|
+
/* @__PURE__ */ jsx("span", { children: q.high ?? (q.kind === "nps" ? "Very likely" : "") })
|
|
53
|
+
] })
|
|
54
|
+
] });
|
|
55
|
+
}
|
|
56
|
+
if (q.kind === "single" || q.kind === "multi") {
|
|
57
|
+
const multi = q.kind === "multi";
|
|
58
|
+
const picked = multi ? value ?? [] : [];
|
|
59
|
+
return /* @__PURE__ */ jsx("div", { className: "ckw-opts", role: multi ? "group" : "radiogroup", "aria-label": q.title, children: (q.options ?? []).map((o) => /* @__PURE__ */ jsxs("label", { className: "ckw-opt", children: [
|
|
60
|
+
/* @__PURE__ */ jsx(
|
|
61
|
+
"input",
|
|
62
|
+
{
|
|
63
|
+
type: multi ? "checkbox" : "radio",
|
|
64
|
+
name: q.key,
|
|
65
|
+
checked: multi ? picked.includes(o) : value === o,
|
|
66
|
+
onChange: () => onChange(multi ? picked.includes(o) ? picked.filter((x) => x !== o) : [...picked, o] : o)
|
|
67
|
+
}
|
|
68
|
+
),
|
|
69
|
+
/* @__PURE__ */ jsx("span", { children: o })
|
|
70
|
+
] }, o)) });
|
|
71
|
+
}
|
|
72
|
+
return /* @__PURE__ */ jsx(
|
|
73
|
+
"textarea",
|
|
74
|
+
{
|
|
75
|
+
className: "ckw-textarea",
|
|
76
|
+
"aria-label": q.title,
|
|
77
|
+
placeholder: q.placeholder ?? "Type your answer",
|
|
78
|
+
value: value ?? "",
|
|
79
|
+
maxLength: 2e3,
|
|
80
|
+
onChange: (e) => onChange(e.target.value),
|
|
81
|
+
onKeyDown: (e) => (e.metaKey || e.ctrlKey) && e.key === "Enter" && onEnter()
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
var Card = ({ survey, onClose }) => {
|
|
86
|
+
const api = useApi();
|
|
87
|
+
const persist = useCtx().effectiveConsent === "cookies";
|
|
88
|
+
const [step, setStep] = useState(0);
|
|
89
|
+
const [answers, setAnswers] = useState({});
|
|
90
|
+
const [done, setDone] = useState(false);
|
|
91
|
+
const [error, setError] = useState("");
|
|
92
|
+
const started = useRef(Date.now());
|
|
93
|
+
const finished = useRef(false);
|
|
94
|
+
const live = useRef(answers);
|
|
95
|
+
live.current = answers;
|
|
96
|
+
const heading = useRef(null);
|
|
97
|
+
const q = survey.questions[step];
|
|
98
|
+
const last = step === survey.questions.length - 1;
|
|
99
|
+
const has = isAnswered(answers[q?.key]);
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
api.seen(survey.id).catch(() => void 0);
|
|
102
|
+
}, [api, survey.id]);
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
heading.current?.focus();
|
|
105
|
+
}, [step]);
|
|
106
|
+
const finish = useCallback(
|
|
107
|
+
async (completed) => {
|
|
108
|
+
if (finished.current) return;
|
|
109
|
+
finished.current = true;
|
|
110
|
+
remember(survey.id, persist);
|
|
111
|
+
api.respond(survey.id, { answers: live.current, completed, seconds: Math.round((Date.now() - started.current) / 1e3) }).catch(() => void 0);
|
|
112
|
+
},
|
|
113
|
+
[api, survey.id, persist]
|
|
114
|
+
);
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (!done) return;
|
|
117
|
+
const t = window.setTimeout(onClose, 2500);
|
|
118
|
+
return () => window.clearTimeout(t);
|
|
119
|
+
}, [done, onClose]);
|
|
120
|
+
const dismiss = useCallback(() => {
|
|
121
|
+
if (Object.values(live.current).some(isAnswered)) void finish(false);
|
|
122
|
+
else remember(survey.id, persist);
|
|
123
|
+
onClose();
|
|
124
|
+
}, [finish, onClose, survey.id, persist]);
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
const h = (e) => e.key === "Escape" && !done && dismiss();
|
|
127
|
+
document.addEventListener("keydown", h);
|
|
128
|
+
return () => document.removeEventListener("keydown", h);
|
|
129
|
+
}, [dismiss, done]);
|
|
130
|
+
const next = async () => {
|
|
131
|
+
if (q.required && !has) {
|
|
132
|
+
setError("Please answer this question.");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
setError("");
|
|
136
|
+
if (!last) return setStep(step + 1);
|
|
137
|
+
await finish(true);
|
|
138
|
+
setDone(true);
|
|
139
|
+
};
|
|
140
|
+
if (done)
|
|
141
|
+
return /* @__PURE__ */ jsxs("div", { className: "ckw-thanks", role: "status", children: [
|
|
142
|
+
/* @__PURE__ */ jsx("span", { className: "ckw-tick", children: /* @__PURE__ */ jsx(Check, {}) }),
|
|
143
|
+
/* @__PURE__ */ jsx("b", { children: survey.thanks || "Thanks for your feedback." })
|
|
144
|
+
] });
|
|
145
|
+
return /* @__PURE__ */ jsxs(
|
|
146
|
+
"form",
|
|
147
|
+
{
|
|
148
|
+
onSubmit: (e) => {
|
|
149
|
+
e.preventDefault();
|
|
150
|
+
void next();
|
|
151
|
+
},
|
|
152
|
+
children: [
|
|
153
|
+
/* @__PURE__ */ jsxs("div", { className: "ckw-sv-top", children: [
|
|
154
|
+
/* @__PURE__ */ jsx("div", { className: "ckw-dots", role: "img", "aria-label": `Question ${step + 1} of ${survey.questions.length}`, children: survey.questions.map((x, i) => /* @__PURE__ */ jsx("span", { className: "ckw-dot", "data-on": i <= step }, x.key)) }),
|
|
155
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "ckw-btn ckw-btn-ghost", onClick: dismiss, "aria-label": "Close survey", style: { height: 24, padding: "0 4px" }, children: /* @__PURE__ */ jsx(Cross, {}) })
|
|
156
|
+
] }),
|
|
157
|
+
/* @__PURE__ */ jsxs("h3", { className: "ckw-q", tabIndex: -1, ref: heading, children: [
|
|
158
|
+
q.title,
|
|
159
|
+
!q.required && /* @__PURE__ */ jsx("span", { className: "ckw-time", children: " optional" })
|
|
160
|
+
] }),
|
|
161
|
+
q.description && /* @__PURE__ */ jsx("p", { className: "ckw-qd", children: q.description }),
|
|
162
|
+
/* @__PURE__ */ jsx(Question, { q, value: answers[q.key], onChange: (v) => (setAnswers((a) => ({ ...a, [q.key]: v })), setError("")), onEnter: () => void next() }),
|
|
163
|
+
/* @__PURE__ */ jsx("div", { "aria-live": "polite", children: error && /* @__PURE__ */ jsx("p", { className: "ckw-qd", style: { color: "var(--ckw-danger)", margin: "8px 0 0" }, children: error }) }),
|
|
164
|
+
/* @__PURE__ */ jsxs("div", { className: "ckw-sv-foot", children: [
|
|
165
|
+
/* @__PURE__ */ jsx("span", {}),
|
|
166
|
+
/* @__PURE__ */ jsx("button", { type: "submit", className: "ckw-btn ckw-btn-primary", children: last ? "Send" : "Next" })
|
|
167
|
+
] })
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
};
|
|
172
|
+
var Survey = () => {
|
|
173
|
+
const { embedded, distinctId, effectiveConsent } = useCtx();
|
|
174
|
+
const api = useApi();
|
|
175
|
+
const [survey, setSurvey] = useState(null);
|
|
176
|
+
const off = embedded || effectiveConsent === "off" || distinctId === "anonymous";
|
|
177
|
+
const persist = effectiveConsent === "cookies";
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
if (off) return;
|
|
180
|
+
const ctl = new AbortController();
|
|
181
|
+
const timer = window.setTimeout(() => {
|
|
182
|
+
api.survey(window.location.href, ctl.signal).then((r) => {
|
|
183
|
+
const s = r.survey;
|
|
184
|
+
if (!s || s.questions.length === 0 || wasDismissed(s.id, persist)) return;
|
|
185
|
+
setSurvey(s);
|
|
186
|
+
}).catch(() => void 0);
|
|
187
|
+
}, 3e3);
|
|
188
|
+
return () => {
|
|
189
|
+
window.clearTimeout(timer);
|
|
190
|
+
ctl.abort();
|
|
191
|
+
};
|
|
192
|
+
}, [api, off, persist]);
|
|
193
|
+
const close = useCallback(() => setSurvey(null), []);
|
|
194
|
+
if (!survey || off) return null;
|
|
195
|
+
return /* @__PURE__ */ jsx(Root, { children: /* @__PURE__ */ jsx("div", { className: "ckw-survey", role: "dialog", "aria-label": survey.name, children: /* @__PURE__ */ jsx(Card, { survey, onClose: close }, survey.id) }) });
|
|
196
|
+
};
|
|
197
|
+
export {
|
|
198
|
+
Survey,
|
|
199
|
+
forgetStoredDismissals
|
|
200
|
+
};
|
|
201
|
+
//# sourceMappingURL=survey.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/survey.tsx"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { type SurveyAnswer, type SurveyDefinition, type SurveyQuestion, useApi } from \"./api\";\nimport { useCtx } from \"./context\";\nimport { Check, Cross, Root, Star } from \"./ui\";\n\nconst DISMISS_PREFIX = \"ck_survey_\";\n/** Surveys dismissed or answered during this page load. Only \"cookies\" mode also keeps them in localStorage. */\nconst dismissed = new Set<string>();\n\nconst remember = (id: string, persist: boolean) => {\n dismissed.add(id);\n if (!persist) return;\n try {\n localStorage.setItem(`${DISMISS_PREFIX}${id}`, \"1\");\n } catch {\n // Private mode: the card just may show again next visit.\n }\n};\n\nconst wasDismissed = (id: string, persist: boolean) => {\n if (dismissed.has(id)) return true;\n if (!persist) return false;\n try {\n return !!localStorage.getItem(`${DISMISS_PREFIX}${id}`);\n } catch {\n return false;\n }\n};\n\n/** Consent for storage was withdrawn: move the stored dismissals into memory for this page load and delete them. */\nexport const forgetStoredDismissals = () => {\n try {\n for (let i = localStorage.length - 1; i >= 0; i--) {\n const key = localStorage.key(i);\n if (!key?.startsWith(DISMISS_PREFIX)) continue;\n dismissed.add(key.slice(DISMISS_PREFIX.length));\n localStorage.removeItem(key);\n }\n } catch {\n // No storage: nothing to delete.\n }\n};\n\nconst isAnswered = (a: SurveyAnswer | undefined) => (Array.isArray(a) ? a.length > 0 : typeof a === \"number\" ? true : !!a?.trim());\n\nconst Question = ({ q, value, onChange, onEnter }: { q: SurveyQuestion; value: SurveyAnswer | undefined; onChange: (v: SurveyAnswer) => void; onEnter: () => void }) => {\n if (q.kind === \"nps\" || q.kind === \"rating\") {\n const scale = q.kind === \"nps\" ? 11 : Math.min(Math.max(q.scale ?? 5, 2), 10);\n const start = q.kind === \"nps\" ? 0 : 1;\n const items = Array.from({ length: scale }, (_, i) => i + start);\n const stars = q.kind === \"rating\" && scale <= 5;\n return (\n <div>\n <div className=\"ckw-scale\" role=\"group\" aria-label={q.title}>\n {items.map((n) =>\n stars ? (\n <button key={n} type=\"button\" className=\"ckw-star\" data-on={typeof value === \"number\" && n <= value} aria-pressed={value === n} aria-label={`${n} of ${scale}`} onClick={() => onChange(n)}>\n <Star />\n </button>\n ) : (\n <button key={n} type=\"button\" className=\"ckw-num\" aria-pressed={value === n} onClick={() => onChange(n)}>\n {n}\n </button>\n ),\n )}\n </div>\n {(q.low || q.high || q.kind === \"nps\") && (\n <div className=\"ckw-lh\">\n <span>{q.low ?? (q.kind === \"nps\" ? \"Not likely\" : \"\")}</span>\n <span>{q.high ?? (q.kind === \"nps\" ? \"Very likely\" : \"\")}</span>\n </div>\n )}\n </div>\n );\n }\n if (q.kind === \"single\" || q.kind === \"multi\") {\n const multi = q.kind === \"multi\";\n const picked = multi ? ((value as string[] | undefined) ?? []) : [];\n return (\n <div className=\"ckw-opts\" role={multi ? \"group\" : \"radiogroup\"} aria-label={q.title}>\n {(q.options ?? []).map((o) => (\n <label className=\"ckw-opt\" key={o}>\n <input\n type={multi ? \"checkbox\" : \"radio\"}\n name={q.key}\n checked={multi ? picked.includes(o) : value === o}\n onChange={() => onChange(multi ? (picked.includes(o) ? picked.filter((x) => x !== o) : [...picked, o]) : o)}\n />\n <span>{o}</span>\n </label>\n ))}\n </div>\n );\n }\n return (\n <textarea\n className=\"ckw-textarea\"\n aria-label={q.title}\n placeholder={q.placeholder ?? \"Type your answer\"}\n value={(value as string | undefined) ?? \"\"}\n maxLength={2000}\n onChange={(e) => onChange(e.target.value)}\n onKeyDown={(e) => (e.metaKey || e.ctrlKey) && e.key === \"Enter\" && onEnter()}\n />\n );\n};\n\nconst Card = ({ survey, onClose }: { survey: SurveyDefinition; onClose: () => void }) => {\n const api = useApi();\n const persist = useCtx().effectiveConsent === \"cookies\";\n const [step, setStep] = useState(0);\n const [answers, setAnswers] = useState<Record<string, SurveyAnswer>>({});\n const [done, setDone] = useState(false);\n const [error, setError] = useState(\"\");\n const started = useRef(Date.now());\n const finished = useRef(false);\n const live = useRef(answers);\n live.current = answers;\n const heading = useRef<HTMLHeadingElement>(null);\n const q = survey.questions[step];\n const last = step === survey.questions.length - 1;\n const has = isAnswered(answers[q?.key]);\n\n useEffect(() => {\n api.seen(survey.id).catch(() => undefined);\n }, [api, survey.id]);\n useEffect(() => {\n heading.current?.focus();\n }, [step]);\n\n const finish = useCallback(\n async (completed: boolean) => {\n if (finished.current) return;\n finished.current = true;\n remember(survey.id, persist);\n api.respond(survey.id, { answers: live.current, completed, seconds: Math.round((Date.now() - started.current) / 1000) }).catch(() => undefined);\n },\n [api, survey.id, persist],\n );\n\n // After the thank-you screen the card closes by itself.\n useEffect(() => {\n if (!done) return;\n const t = window.setTimeout(onClose, 2500);\n return () => window.clearTimeout(t);\n }, [done, onClose]);\n\n const dismiss = useCallback(() => {\n // Partial answers still count, so nothing the visitor typed is lost.\n if (Object.values(live.current).some(isAnswered)) void finish(false);\n else remember(survey.id, persist);\n onClose();\n }, [finish, onClose, survey.id, persist]);\n\n useEffect(() => {\n const h = (e: KeyboardEvent) => e.key === \"Escape\" && !done && dismiss();\n document.addEventListener(\"keydown\", h);\n return () => document.removeEventListener(\"keydown\", h);\n }, [dismiss, done]);\n\n const next = async () => {\n if (q.required && !has) {\n setError(\"Please answer this question.\");\n return;\n }\n setError(\"\");\n if (!last) return setStep(step + 1);\n await finish(true);\n setDone(true);\n };\n\n if (done)\n return (\n <div className=\"ckw-thanks\" role=\"status\">\n <span className=\"ckw-tick\">\n <Check />\n </span>\n <b>{survey.thanks || \"Thanks for your feedback.\"}</b>\n </div>\n );\n\n return (\n <form\n onSubmit={(e) => {\n e.preventDefault();\n void next();\n }}\n >\n <div className=\"ckw-sv-top\">\n <div className=\"ckw-dots\" role=\"img\" aria-label={`Question ${step + 1} of ${survey.questions.length}`}>\n {survey.questions.map((x, i) => (\n <span className=\"ckw-dot\" key={x.key} data-on={i <= step} />\n ))}\n </div>\n <button type=\"button\" className=\"ckw-btn ckw-btn-ghost\" onClick={dismiss} aria-label=\"Close survey\" style={{ height: 24, padding: \"0 4px\" }}>\n <Cross />\n </button>\n </div>\n <h3 className=\"ckw-q\" tabIndex={-1} ref={heading}>\n {q.title}\n {!q.required && <span className=\"ckw-time\"> optional</span>}\n </h3>\n {q.description && <p className=\"ckw-qd\">{q.description}</p>}\n <Question q={q} value={answers[q.key]} onChange={(v) => (setAnswers((a) => ({ ...a, [q.key]: v })), setError(\"\"))} onEnter={() => void next()} />\n <div aria-live=\"polite\">\n {error && (\n <p className=\"ckw-qd\" style={{ color: \"var(--ckw-danger)\", margin: \"8px 0 0\" }}>\n {error}\n </p>\n )}\n </div>\n <div className=\"ckw-sv-foot\">\n <span />\n <button type=\"submit\" className=\"ckw-btn ckw-btn-primary\">\n {last ? \"Send\" : \"Next\"}\n </button>\n </div>\n </form>\n );\n};\n\n/** Asks the visitor the survey Cockpit picked for this page. Mounted by CockpitProvider unless `surveys={false}`. */\nexport const Survey = () => {\n const { embedded, distinctId, effectiveConsent } = useCtx();\n const api = useApi();\n const [survey, setSurvey] = useState<SurveyDefinition | null>(null);\n // Never inside Cockpit previews, never without consent, and not before the visitor id is known.\n const off = embedded || effectiveConsent === \"off\" || distinctId === \"anonymous\";\n const persist = effectiveConsent === \"cookies\";\n\n useEffect(() => {\n if (off) return;\n const ctl = new AbortController();\n const timer = window.setTimeout(() => {\n api.survey(window.location.href, ctl.signal)\n .then((r) => {\n const s = r.survey;\n if (!s || s.questions.length === 0 || wasDismissed(s.id, persist)) return;\n setSurvey(s);\n })\n .catch(() => undefined);\n }, 3000);\n return () => {\n window.clearTimeout(timer);\n ctl.abort();\n };\n }, [api, off, persist]);\n\n const close = useCallback(() => setSurvey(null), []);\n if (!survey || off) return null;\n return (\n <Root>\n <div className=\"ckw-survey\" role=\"dialog\" aria-label={survey.name}>\n <Card key={survey.id} survey={survey} onClose={close} />\n </div>\n </Root>\n );\n};\n"],"mappings":"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","names":[]}
|
package/dist/ui.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type FeedbackPost, type PostStatus } from "./api.js";
|
|
3
|
+
export type Theme = "light" | "dark" | "auto";
|
|
4
|
+
/** The scoped wrapper every widget renders inside. Carries the accent and the theme. */
|
|
5
|
+
export declare const Root: ({ theme, accent, className, children }: {
|
|
6
|
+
theme?: Theme;
|
|
7
|
+
accent?: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
}) => import("react").JSX.Element;
|
|
11
|
+
export declare const ChevronUp: () => import("react").JSX.Element;
|
|
12
|
+
export declare const Plus: () => import("react").JSX.Element;
|
|
13
|
+
export declare const Cross: () => import("react").JSX.Element;
|
|
14
|
+
export declare const Check: () => import("react").JSX.Element;
|
|
15
|
+
export declare const Chat: () => import("react").JSX.Element;
|
|
16
|
+
export declare const Star: () => import("react").JSX.Element;
|
|
17
|
+
export declare const StatusPill: ({ status }: {
|
|
18
|
+
status: PostStatus;
|
|
19
|
+
}) => import("react").JSX.Element;
|
|
20
|
+
export declare const VoteButton: ({ post, onVote, small }: {
|
|
21
|
+
post: FeedbackPost;
|
|
22
|
+
onVote: (p: FeedbackPost) => void;
|
|
23
|
+
small?: boolean;
|
|
24
|
+
}) => import("react").JSX.Element;
|
|
25
|
+
export declare const timeAgo: (iso: string) => string;
|
|
26
|
+
/** Calls `onClose` on Esc while mounted. */
|
|
27
|
+
export declare const useEscape: (onClose: () => void) => void;
|
|
28
|
+
/** Up to three existing posts that share words with what the visitor is typing. */
|
|
29
|
+
export declare const similarPosts: (title: string, posts: FeedbackPost[]) => FeedbackPost[];
|
package/dist/ui.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/ui.tsx
|
|
4
|
+
import { useEffect, useRef } from "react";
|
|
5
|
+
import { STATUS_LABEL } from "./api.js";
|
|
6
|
+
import { useStyles } from "./styles.js";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
var Root = ({ theme = "auto", accent = "#3f76ff", className, children }) => {
|
|
9
|
+
useStyles();
|
|
10
|
+
return /* @__PURE__ */ jsx("div", { className: `ckw-root${className ? ` ${className}` : ""}`, "data-theme": theme, style: { ["--ckw-accent"]: accent }, children });
|
|
11
|
+
};
|
|
12
|
+
var Svg = ({ children, size = 16 }) => /* @__PURE__ */ jsx("svg", { className: "ckw-icon", width: size, height: size, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children });
|
|
13
|
+
var ChevronUp = () => /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "M3.5 10 8 5.5 12.5 10" }) });
|
|
14
|
+
var Plus = () => /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "M8 3v10M3 8h10" }) });
|
|
15
|
+
var Cross = () => /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "m4 4 8 8M12 4l-8 8" }) });
|
|
16
|
+
var Check = () => /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "m3 8.5 3.2 3.2L13 4.8" }) });
|
|
17
|
+
var Chat = () => /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "M2.5 4.5a2 2 0 0 1 2-2h7a2 2 0 0 1 2 2v4.5a2 2 0 0 1-2 2H7l-3 2.5V11a2 2 0 0 1-1.5-2z" }) });
|
|
18
|
+
var Star = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "m12 2.8 2.8 5.8 6.4.9-4.6 4.5 1.1 6.3L12 17.2l-5.7 3.1 1.1-6.3L2.8 9.5l6.4-.9z" }) });
|
|
19
|
+
var StatusPill = ({ status }) => /* @__PURE__ */ jsx("span", { className: "ckw-pill", "data-s": status, children: STATUS_LABEL[status] ?? status });
|
|
20
|
+
var VoteButton = ({ post, onVote, small }) => /* @__PURE__ */ jsxs(
|
|
21
|
+
"button",
|
|
22
|
+
{
|
|
23
|
+
type: "button",
|
|
24
|
+
className: `ckw-vote${small ? " ckw-vote-sm" : ""}`,
|
|
25
|
+
"aria-pressed": post.voted,
|
|
26
|
+
"aria-label": `${post.voted ? "Remove your vote from" : "Upvote"} ${post.title}. ${post.votes} votes`,
|
|
27
|
+
onClick: () => onVote(post),
|
|
28
|
+
children: [
|
|
29
|
+
/* @__PURE__ */ jsx(ChevronUp, {}),
|
|
30
|
+
/* @__PURE__ */ jsx("span", { children: post.votes })
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
var timeAgo = (iso) => {
|
|
35
|
+
const t = new Date(iso).getTime();
|
|
36
|
+
if (Number.isNaN(t)) return "";
|
|
37
|
+
const d = Math.max(0, Date.now() - t) / 1e3;
|
|
38
|
+
if (d < 60) return "just now";
|
|
39
|
+
if (d < 3600) return `${Math.floor(d / 60)}m ago`;
|
|
40
|
+
if (d < 86400) return `${Math.floor(d / 3600)}h ago`;
|
|
41
|
+
if (d < 86400 * 30) return `${Math.floor(d / 86400)}d ago`;
|
|
42
|
+
return new Date(iso).toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" });
|
|
43
|
+
};
|
|
44
|
+
var useEscape = (onClose) => {
|
|
45
|
+
const ref = useRef(onClose);
|
|
46
|
+
ref.current = onClose;
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const h = (e) => e.key === "Escape" && ref.current();
|
|
49
|
+
document.addEventListener("keydown", h);
|
|
50
|
+
return () => document.removeEventListener("keydown", h);
|
|
51
|
+
}, []);
|
|
52
|
+
};
|
|
53
|
+
var words = (s) => s.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length > 2 && !["the", "and", "for", "with", "add", "can", "please", "want", "would", "our", "you", "have"].includes(w));
|
|
54
|
+
var similarPosts = (title, posts) => {
|
|
55
|
+
const mine = words(title);
|
|
56
|
+
if (mine.length === 0 || title.trim().length < 4) return [];
|
|
57
|
+
return posts.map((p) => {
|
|
58
|
+
const theirs = new Set(words(`${p.title} ${p.body}`.slice(0, 300)));
|
|
59
|
+
const hit = mine.filter((w) => theirs.has(w) || [...theirs].some((t) => t.startsWith(w) || w.startsWith(t))).length;
|
|
60
|
+
return { p, score: hit / mine.length };
|
|
61
|
+
}).filter((x) => x.score >= 0.5).sort((a, b) => b.score - a.score || b.p.votes - a.p.votes).slice(0, 3).map((x) => x.p);
|
|
62
|
+
};
|
|
63
|
+
export {
|
|
64
|
+
Chat,
|
|
65
|
+
Check,
|
|
66
|
+
ChevronUp,
|
|
67
|
+
Cross,
|
|
68
|
+
Plus,
|
|
69
|
+
Root,
|
|
70
|
+
Star,
|
|
71
|
+
StatusPill,
|
|
72
|
+
VoteButton,
|
|
73
|
+
similarPosts,
|
|
74
|
+
timeAgo,
|
|
75
|
+
useEscape
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=ui.js.map
|
package/dist/ui.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ui.tsx"],"sourcesContent":["\"use client\";\n\nimport { type ReactNode, useEffect, useRef } from \"react\";\nimport { type FeedbackPost, type PostStatus, STATUS_LABEL } from \"./api\";\nimport { useStyles } from \"./styles\";\n\nexport type Theme = \"light\" | \"dark\" | \"auto\";\n\n/** The scoped wrapper every widget renders inside. Carries the accent and the theme. */\nexport const Root = ({ theme = \"auto\", accent = \"#3f76ff\", className, children }: { theme?: Theme; accent?: string; className?: string; children: ReactNode }) => {\n useStyles();\n return (\n <div className={`ckw-root${className ? ` ${className}` : \"\"}`} data-theme={theme} style={{ [\"--ckw-accent\" as string]: accent }}>\n {children}\n </div>\n );\n};\n\nconst Svg = ({ children, size = 16 }: { children: ReactNode; size?: number }) => (\n <svg className=\"ckw-icon\" width={size} height={size} viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n {children}\n </svg>\n);\n\nexport const ChevronUp = () => (\n <Svg>\n <path d=\"M3.5 10 8 5.5 12.5 10\" />\n </Svg>\n);\nexport const Plus = () => (\n <Svg>\n <path d=\"M8 3v10M3 8h10\" />\n </Svg>\n);\nexport const Cross = () => (\n <Svg>\n <path d=\"m4 4 8 8M12 4l-8 8\" />\n </Svg>\n);\nexport const Check = () => (\n <Svg>\n <path d=\"m3 8.5 3.2 3.2L13 4.8\" />\n </Svg>\n);\nexport const Chat = () => (\n <Svg>\n <path d=\"M2.5 4.5a2 2 0 0 1 2-2h7a2 2 0 0 1 2 2v4.5a2 2 0 0 1-2 2H7l-3 2.5V11a2 2 0 0 1-1.5-2z\" />\n </Svg>\n);\nexport const Star = () => (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"m12 2.8 2.8 5.8 6.4.9-4.6 4.5 1.1 6.3L12 17.2l-5.7 3.1 1.1-6.3L2.8 9.5l6.4-.9z\" />\n </svg>\n);\n\nexport const StatusPill = ({ status }: { status: PostStatus }) => (\n <span className=\"ckw-pill\" data-s={status}>\n {STATUS_LABEL[status] ?? status}\n </span>\n);\n\nexport const VoteButton = ({ post, onVote, small }: { post: FeedbackPost; onVote: (p: FeedbackPost) => void; small?: boolean }) => (\n <button\n type=\"button\"\n className={`ckw-vote${small ? \" ckw-vote-sm\" : \"\"}`}\n aria-pressed={post.voted}\n aria-label={`${post.voted ? \"Remove your vote from\" : \"Upvote\"} ${post.title}. ${post.votes} votes`}\n onClick={() => onVote(post)}\n >\n <ChevronUp />\n <span>{post.votes}</span>\n </button>\n);\n\nexport const timeAgo = (iso: string) => {\n const t = new Date(iso).getTime();\n if (Number.isNaN(t)) return \"\";\n const d = Math.max(0, Date.now() - t) / 1000;\n if (d < 60) return \"just now\";\n if (d < 3600) return `${Math.floor(d / 60)}m ago`;\n if (d < 86400) return `${Math.floor(d / 3600)}h ago`;\n if (d < 86400 * 30) return `${Math.floor(d / 86400)}d ago`;\n return new Date(iso).toLocaleDateString(undefined, { month: \"short\", day: \"numeric\", year: \"numeric\" });\n};\n\n/** Calls `onClose` on Esc while mounted. */\nexport const useEscape = (onClose: () => void) => {\n const ref = useRef(onClose);\n ref.current = onClose;\n useEffect(() => {\n const h = (e: KeyboardEvent) => e.key === \"Escape\" && ref.current();\n document.addEventListener(\"keydown\", h);\n return () => document.removeEventListener(\"keydown\", h);\n }, []);\n};\n\n/** Words of a title, lowercased, without filler. */\nconst words = (s: string) =>\n s\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter((w) => w.length > 2 && ![\"the\", \"and\", \"for\", \"with\", \"add\", \"can\", \"please\", \"want\", \"would\", \"our\", \"you\", \"have\"].includes(w));\n\n/** Up to three existing posts that share words with what the visitor is typing. */\nexport const similarPosts = (title: string, posts: FeedbackPost[]) => {\n const mine = words(title);\n if (mine.length === 0 || title.trim().length < 4) return [];\n return posts\n .map((p) => {\n const theirs = new Set(words(`${p.title} ${p.body}`.slice(0, 300)));\n const hit = mine.filter((w) => theirs.has(w) || [...theirs].some((t) => t.startsWith(w) || w.startsWith(t))).length;\n return { p, score: hit / mine.length };\n })\n .filter((x) => x.score >= 0.5)\n .sort((a, b) => b.score - a.score || b.p.votes - a.p.votes)\n .slice(0, 3)\n .map((x) => x.p);\n};\n"],"mappings":"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","names":[]}
|