@helpway/react 0.3.0 → 0.3.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helpway/react",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Official React SDK for Helpway — drop-in AI chat support widget with headless hooks for custom UIs.",
5
5
  "keywords": [
6
6
  "helpway",
@@ -40,6 +40,7 @@
40
40
  "scripts": {
41
41
  "build": "tsup",
42
42
  "typecheck": "tsc --noEmit",
43
+ "sync:icons": "bun scripts/sync-icons.ts",
43
44
  "prepublishOnly": "bun run build",
44
45
  "prepack": "bun run build && bun scripts/apply-publish-config.ts",
45
46
  "postpack": "bun scripts/apply-publish-config.ts --restore"
@@ -47,14 +48,15 @@
47
48
  "//": "publishConfig swaps main/types/exports to the built dist on publish. Workspace consumers get raw src (fast HMR, no build step); npm consumers get compiled dist with .d.ts. Standard monorepo pattern.",
48
49
  "dependencies": {
49
50
  "@trpc/client": "^11.5.1",
50
- "zustand": "^5.0.11",
51
- "@central-icons-react/round-outlined-radius-2-stroke-2": "^1.1.178"
51
+ "zustand": "^5.0.11"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@helpway/types": "workspace:*",
55
55
  "@helpway/widget-core": "workspace:*",
56
56
  "@types/react": "^19.1.6",
57
57
  "@types/react-dom": "^19.1.6",
58
+ "@central-icons-react/round-outlined-radius-2-stroke-2": "^1.1.213",
59
+ "react-dom": "^19.1.6",
58
60
  "tsup": "^8.3.5",
59
61
  "typescript": "~5.9.3"
60
62
  },
@@ -45,6 +45,15 @@
45
45
  .hw-widget {
46
46
  --hw-primary: #171717;
47
47
  --hw-primary-hover: #2a2a2a;
48
+ /* Brand surface used by team-avatar fallbacks and primary CTAs.
49
+ In light mode this is the host brand color (default near-black).
50
+ Dark overrides redefine to a high-contrast value so the brand tile
51
+ stays visible on dark surfaces. Customers override --hw-brand to
52
+ theme team avatars + primary CTAs in one shot. */
53
+ --hw-brand: #171717;
54
+ /* Foreground (text/svg) that sits on --hw-brand. Inverts in dark so
55
+ light-on-light never happens after the brand-token flip. */
56
+ --hw-on-brand: #ffffff;
48
57
  /* Focus ring tokens — used by composer + content search + any future
49
58
  input. Kept as variables so the dark override gets a proper contrast
50
59
  pair instead of a one-off per-component tweak. */
@@ -304,6 +313,20 @@
304
313
  flex-direction: column;
305
314
  overflow: hidden;
306
315
  animation: hw-window-open 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
316
+ /* Smooth size transition for reading-mode expansion. The open animation
317
+ is one-shot (transform/opacity) so it doesn't conflict with this. */
318
+ transition:
319
+ width 280ms cubic-bezier(0.4, 0, 0.2, 1),
320
+ height 280ms cubic-bezier(0.4, 0, 0.2, 1);
321
+ }
322
+
323
+ /* Reading mode — applied when the visitor enters a content detail view
324
+ (help article, changelog entry). Wider + taller for comfortable long-form
325
+ reading. Mobile (<480px) ignores this — the @media block forces 100% +
326
+ beats this rule by source order. */
327
+ .hw-window--reading {
328
+ width: min(720px, calc(100vw - 40px));
329
+ height: min(800px, calc(100vh - 100px));
307
330
  }
308
331
 
309
332
  @keyframes hw-window-open {
@@ -536,7 +559,7 @@
536
559
  }
537
560
  .hw-header-bot-icon--team {
538
561
  background: var(--hw-brand, #0a0a0a);
539
- color: #fff;
562
+ color: var(--hw-on-brand, #fff);
540
563
  }
541
564
  .hw-header-agent-img {
542
565
  width: 100%;
@@ -907,7 +930,7 @@
907
930
  }
908
931
  .hw-conv-card-avatar--team {
909
932
  background: var(--hw-brand, #0a0a0a);
910
- color: #fff;
933
+ color: var(--hw-on-brand, #fff);
911
934
  }
912
935
  .hw-conv-card-avatar-initials {
913
936
  font-size: 12px;
@@ -1173,6 +1196,11 @@
1173
1196
  opacity: 0.3;
1174
1197
  }
1175
1198
 
1199
+ .hw-empty-text {
1200
+ font-size: 13px;
1201
+ /* color inherited from .hw-empty (--hw-text-3) — adapts in dark */
1202
+ }
1203
+
1176
1204
  /* ── Date Separator ──────────────────────────── */
1177
1205
  .hw-date-sep {
1178
1206
  display: flex;
@@ -1294,7 +1322,7 @@
1294
1322
  box-sizing: border-box;
1295
1323
  }
1296
1324
 
1297
- /* Meta (clock always visible; sender/copy reveal on hover) */
1325
+ /* Meta (fully hidden; sender label + copy icon reveal on hover) */
1298
1326
  .hw-msg-meta {
1299
1327
  display: inline-flex;
1300
1328
  align-items: center;
@@ -1303,29 +1331,25 @@
1303
1331
  color: var(--hw-text-3);
1304
1332
  margin-top: 3px;
1305
1333
  padding: 0 4px;
1306
- opacity: 1;
1307
- font-variant-numeric: tabular-nums;
1308
- min-height: 20px;
1309
- }
1310
-
1311
- .hw-msg-time {
1312
- opacity: 0.72;
1313
- }
1314
-
1315
- .hw-msg-hover-meta {
1316
- max-width: 0;
1317
1334
  opacity: 0;
1335
+ font-variant-numeric: tabular-nums;
1336
+ min-height: 0;
1337
+ height: 0;
1318
1338
  overflow: hidden;
1319
- white-space: nowrap;
1320
1339
  transition:
1321
- max-width 180ms ease,
1322
- opacity 140ms ease;
1340
+ opacity 140ms ease,
1341
+ height 140ms ease;
1323
1342
  }
1324
1343
 
1325
- .hw-msg:hover .hw-msg-hover-meta,
1326
- .hw-msg:focus-within .hw-msg-hover-meta {
1327
- max-width: 180px;
1344
+ .hw-msg:hover .hw-msg-meta,
1345
+ .hw-msg:focus-within .hw-msg-meta {
1328
1346
  opacity: 1;
1347
+ height: 20px;
1348
+ min-height: 20px;
1349
+ }
1350
+
1351
+ .hw-msg-sender-label {
1352
+ white-space: nowrap;
1329
1353
  }
1330
1354
 
1331
1355
  .hw-msg--visitor .hw-msg-meta {
@@ -1556,6 +1580,59 @@
1556
1580
  background: var(--hw-bg);
1557
1581
  }
1558
1582
 
1583
+ /* ── Skeleton: Article detail ──────────────────
1584
+ Designed to be PIXEL-EQUIVALENT to the rendered article so content can land
1585
+ without reflow. Maps 1:1 to:
1586
+ <div class="hw-content-detail">
1587
+ <div class="hw-content-detail-header"> ← title (22px line) + meta row
1588
+ <div class="hw-content-detail-body">
1589
+ <div class="hw-markdown">
1590
+ <h2>...</h2> ← section header (~16px line)
1591
+ <ul><li>**Bold:** desc...</li></ul> ← bullet item, 2 visible lines
1592
+ <ul><li>...</li></ul>
1593
+ The skeleton uses the SAME outer classes for padding/spacing, then injects
1594
+ shimmer bars where text would render. */
1595
+
1596
+ .hw-skeleton-title {
1597
+ height: 22px;
1598
+ width: 65%;
1599
+ border-radius: 6px;
1600
+ }
1601
+
1602
+ .hw-skeleton-badge {
1603
+ width: 42px;
1604
+ height: 18px;
1605
+ border-radius: 9px;
1606
+ }
1607
+
1608
+ .hw-skeleton-date {
1609
+ width: 60px;
1610
+ height: 12px;
1611
+ border-radius: 6px;
1612
+ }
1613
+
1614
+ .hw-skeleton-h2 {
1615
+ height: 16px;
1616
+ border-radius: 6px;
1617
+ }
1618
+
1619
+ /* Bullet wrapper — emulates <ul> padding-left: 20px so the skeleton bullet
1620
+ text starts at the same x-coordinate as the real <li>'s text content. */
1621
+ .hw-skeleton-bullet {
1622
+ padding-left: 20px;
1623
+ display: flex;
1624
+ flex-direction: column;
1625
+ gap: 8px;
1626
+ }
1627
+
1628
+ /* Each "line" inside a bullet — line-height: 14 matches the visible text mass
1629
+ of body text (font-size 14 × line-height 1.6 = 22.4 visible bbox, but skeleton
1630
+ bar is the visible-character thickness only). */
1631
+ .hw-skeleton-bullet-line {
1632
+ height: 14px;
1633
+ border-radius: 7px;
1634
+ }
1635
+
1559
1636
  /* ════════════════════════════════════════════════
1560
1637
  EMAIL COLLECTION CARD (fixed above composer)
1561
1638
  ════════════════════════════════════════════════ */
@@ -1565,7 +1642,7 @@
1565
1642
  padding: 14px 16px 10px;
1566
1643
  border: 1px solid var(--hw-border, rgba(0,0,0,0.08));
1567
1644
  border-radius: 14px;
1568
- background: var(--hw-surface, #fff);
1645
+ background: var(--hw-bg, #fff);
1569
1646
  box-shadow: 0 1px 4px rgba(0,0,0,0.06), 0 4px 12px rgba(0,0,0,0.03);
1570
1647
  animation: hw-email-slide-in 0.25s ease-out;
1571
1648
  }
@@ -1587,11 +1664,11 @@
1587
1664
  width: 32px;
1588
1665
  height: 32px;
1589
1666
  border-radius: 8px;
1590
- background: var(--hw-brand, #0a0a0a);
1667
+ background: var(--hw-bg-hover, #f5f5f5);
1591
1668
  display: flex;
1592
1669
  align-items: center;
1593
1670
  justify-content: center;
1594
- color: #fff;
1671
+ color: var(--hw-text, #1a1a1a);
1595
1672
  }
1596
1673
 
1597
1674
  .hw-email-card-title {
@@ -1604,7 +1681,7 @@
1604
1681
 
1605
1682
  .hw-email-card-subtitle {
1606
1683
  font-size: 12px;
1607
- color: var(--hw-text-muted, #888);
1684
+ color: var(--hw-text-2, #888);
1608
1685
  line-height: 1.4;
1609
1686
  margin: 0;
1610
1687
  }
@@ -1614,18 +1691,22 @@
1614
1691
  padding: 9px 12px;
1615
1692
  border-radius: 10px;
1616
1693
  border: 1px solid var(--hw-border, rgba(0,0,0,0.1));
1617
- background: var(--hw-bg, #fafafa);
1694
+ background: var(--hw-bg-subtle, #fafafa);
1618
1695
  color: var(--hw-text, #1a1a1a);
1619
1696
  font-size: 13px;
1620
1697
  outline: none;
1621
1698
  margin-bottom: 8px;
1622
1699
  box-sizing: border-box;
1623
- transition: border-color 0.15s;
1700
+ transition: border-color 0.15s, box-shadow 0.15s;
1701
+ }
1702
+
1703
+ .hw-email-card-input::placeholder {
1704
+ color: var(--hw-text-3, #999);
1624
1705
  }
1625
1706
 
1626
1707
  .hw-email-card-input:focus {
1627
- border-color: var(--hw-brand, #0a0a0a);
1628
- box-shadow: 0 0 0 2px rgba(0,0,0,0.06);
1708
+ border-color: var(--hw-focus-border, rgba(0,0,0,0.2));
1709
+ box-shadow: 0 0 0 2px var(--hw-focus-ring, rgba(0,0,0,0.06));
1629
1710
  }
1630
1711
 
1631
1712
  .hw-email-card-input--error {
@@ -1638,7 +1719,7 @@
1638
1719
  border-radius: 10px;
1639
1720
  border: none;
1640
1721
  background: var(--hw-brand, #0a0a0a);
1641
- color: #fff;
1722
+ color: var(--hw-on-brand, #fff);
1642
1723
  font-size: 13px;
1643
1724
  font-weight: 500;
1644
1725
  cursor: pointer;
@@ -1665,7 +1746,7 @@
1665
1746
  gap: 6px;
1666
1747
  padding: 10px 16px;
1667
1748
  font-size: 13px;
1668
- color: var(--hw-text-muted, #888);
1749
+ color: var(--hw-text-2, #888);
1669
1750
  animation: hw-email-slide-in 0.2s ease-out;
1670
1751
  }
1671
1752
 
@@ -2159,8 +2240,8 @@
2159
2240
  width: 32px;
2160
2241
  height: 32px;
2161
2242
  border-radius: 50%;
2162
- background: #e0e0e0;
2163
- color: #555;
2243
+ background: var(--hw-bg-hover, #e0e0e0);
2244
+ color: var(--hw-text-2, #555);
2164
2245
  display: flex;
2165
2246
  align-items: center;
2166
2247
  justify-content: center;
@@ -2685,31 +2766,92 @@
2685
2766
  color: var(--hw-text-1);
2686
2767
  }
2687
2768
 
2688
- /* ── Markdown Rendering ────────────────────────────────── */
2769
+ /* ── Markdown Rendering ──────────────────────────────────
2770
+ Heading vertical spacing is governed by the adjacency rules further
2771
+ down (.hw-markdown > * + h2, etc.). Keeping margin: 0 here prevents
2772
+ the heading's own bottom margin from compounding with the sibling's
2773
+ top margin (e.g. h2 8px + p 12px = 20px instead of 12px). */
2689
2774
  .hw-markdown h1 {
2690
2775
  font-size: 17px;
2691
2776
  font-weight: 700;
2692
- margin: 20px 0 8px;
2777
+ margin: 0;
2693
2778
  }
2694
2779
 
2695
2780
  .hw-markdown h2 {
2696
2781
  font-size: 15px;
2697
2782
  font-weight: 600;
2698
- margin: 16px 0 6px;
2783
+ margin: 0;
2699
2784
  }
2700
2785
 
2701
2786
  .hw-markdown h3 {
2702
2787
  font-size: 14px;
2703
2788
  font-weight: 600;
2704
- margin: 12px 0 4px;
2789
+ margin: 0;
2705
2790
  }
2706
2791
 
2707
2792
  .hw-markdown p {
2708
- margin: 0 0 12px;
2793
+ margin: 0;
2794
+ }
2795
+
2796
+ /* Rich-text editors (PlateJS, Slate, ProseMirror) emit any of:
2797
+ <p><br></p> ← visual line break paragraph
2798
+ <p></p> ← empty paragraph
2799
+ <div><br></div> ← editor block element (PlateJS default)
2800
+ <div></div> ← empty block
2801
+ Each contributes line-height + margin twice → 50-70px gap between items.
2802
+ Hide them all so spacing is governed by the adjacency rules below. */
2803
+ .hw-markdown p:empty,
2804
+ .hw-markdown div:empty,
2805
+ .hw-markdown p:has(> br:only-child),
2806
+ .hw-markdown div:has(> br:only-child) {
2807
+ display: none;
2808
+ }
2809
+
2810
+ /* Adjacency-based spacing — works for any block element type the CMS emits
2811
+ (<p>, <div>, custom). Combined with the JS post-render cleanup in
2812
+ simple-markdown.tsx (which strips whitespace-only blocks like <p>&nbsp;</p>
2813
+ that CSS :empty can't match), this gives consistent spacing regardless of
2814
+ editor format. */
2815
+ .hw-markdown > * + * {
2816
+ margin-top: 12px;
2817
+ }
2818
+
2819
+ .hw-markdown > * + h1,
2820
+ .hw-markdown > * + h2 {
2821
+ margin-top: 20px;
2822
+ }
2823
+
2824
+ .hw-markdown > * + h3 {
2825
+ margin-top: 16px;
2826
+ }
2827
+
2828
+ .hw-markdown > h1 + *,
2829
+ .hw-markdown > h2 + *,
2830
+ .hw-markdown > h3 + * {
2831
+ margin-top: 8px;
2832
+ }
2833
+
2834
+ /* First/last child margin reset so the body's outer padding is the only
2835
+ spacing that surrounds the article. */
2836
+ .hw-markdown > *:first-child {
2837
+ margin-top: 0;
2838
+ }
2839
+
2840
+ .hw-markdown > *:last-child {
2841
+ margin-bottom: 0;
2842
+ }
2843
+
2844
+ /* Better i18n CMS emits standalone <br> elements at the markdown root as
2845
+ visual separators between consecutive <ul> bullet groups. Each <br>
2846
+ contributed ~22px line-height + adjacency margins on both sides, creating
2847
+ the 40-50px gap between items. Hide them — the adjacency rule alone gives
2848
+ the 12px gap we want. */
2849
+ .hw-markdown > br {
2850
+ display: none;
2709
2851
  }
2710
2852
 
2711
2853
  .hw-markdown ul {
2712
- margin: 0 0 12px;
2854
+ margin: 0;
2713
2855
  padding-left: 20px;
2714
2856
  }
2715
2857
 
@@ -3069,6 +3211,9 @@
3069
3211
  }
3070
3212
  .hw-msg-meta {
3071
3213
  opacity: 1;
3214
+ height: auto;
3215
+ min-height: 20px;
3216
+ overflow: visible;
3072
3217
  }
3073
3218
  .hw-msg-copy {
3074
3219
  opacity: 1;
@@ -3524,6 +3669,10 @@
3524
3669
  --hw-visitor-bubble-text: #f5f5f5;
3525
3670
  --hw-focus-border: rgba(255, 255, 255, 0.28);
3526
3671
  --hw-focus-ring: rgba(255, 255, 255, 0.08);
3672
+ /* Brand inverts in dark — light tile on dark surface keeps team avatars
3673
+ and primary CTAs readable. Same pattern as iOS dark mode tints. */
3674
+ --hw-brand: #f5f5f5;
3675
+ --hw-on-brand: #1c1c1e;
3527
3676
  }
3528
3677
 
3529
3678
  @media (prefers-color-scheme: dark) {
@@ -3560,6 +3709,9 @@
3560
3709
  /* Focus ring in dark needs lighter border + subtle halo. */
3561
3710
  --hw-focus-border: rgba(255, 255, 255, 0.28);
3562
3711
  --hw-focus-ring: rgba(255, 255, 255, 0.08);
3712
+ /* See --hw-brand comment in [data-theme="dark"] block. */
3713
+ --hw-brand: #f5f5f5;
3714
+ --hw-on-brand: #1c1c1e;
3563
3715
  }
3564
3716
  }
3565
3717