@kindlyhuman/snippet
v0.4.1
Published
Embeddable Kindly Human floating snippet widget (framework-agnostic script + optional React)
Readme
@kindlyhuman/snippet
Embeddable Kindly Human floating snippet (launcher + modal). Configuration is loaded from the backend so copy and CTAs can be changed without redeploying the host site.
Plain HTML (no bundler)
Paste one script tag (hosted on your CDN after yarn build). Use data-snippet-id and data-api-base-url so the widget initializes automatically:
<script
src="https://cdn.kindlyhuman.io/snippet/v1/kindly-snippet.iife.js"
defer
data-snippet-id="YOUR_SNIPPET_ID"
data-api-base-url="https://api.example.com"
></script>Alternatively, load the script and call init yourself:
<script src="https://cdn.kindlyhuman.io/snippet/v1/kindly-snippet.iife.js" defer></script>
<script>
window.addEventListener("DOMContentLoaded", function () {
window.KindlySnippet.init({
snippetId: "YOUR_SNIPPET_ID",
apiBaseUrl: "https://api.example.com",
});
});
</script>apiBaseUrlmust be the origin only (no trailing slash required); the client requestsGET {apiBaseUrl}/api/v3/public/kindly-snippet/config?snippet_id=…withAccept: application/json.- Destroy a specific instance:
handle.destroy(), or destroy all:window.KindlySnippet.destroy().
npm (React, Vite, Next.js, …)
yarn add @kindlyhuman/snippetimport { KindlySnippet } from "@kindlyhuman/snippet/react";
export function App() {
return (
<>
<YourApp />
<KindlySnippet snippetId="YOUR_SNIPPET_ID" apiBaseUrl={import.meta.env.VITE_API_BASE_URL} />
</>
);
}Core API (any bundler):
import { init, destroy } from "@kindlyhuman/snippet";
const handle = init({ snippetId: "YOUR_SNIPPET_ID", apiBaseUrl: "https://api.example.com" });
// handle.destroy();Local demo
Defaults in demo/.env target snippet_id christian_group_1 against http://127.0.0.1:5001 (local docker nginx → API).
Seed one or more snippets first from backend/:
./manage.py dev _upsert_kindly_snippet_embed --client-name "Christian Group" --snippet-id "christian_group_1" --video-filename "video_1.mp4"
./manage.py dev _upsert_kindly_snippet_embed --client-name "Christian Group" --snippet-id "christian_group_2" --video-filename "video_2.mp4"
# Optional modal placeholder (copy image into UPLOADED_IMAGES_DEST, e.g. build/assets/images):
./manage.py dev _upsert_kindly_snippet_embed --client-name "Christian Group" --snippet-id "christian_group_1" --video-filename "video_1.mp4" --modal-poster-filename "modal-poster.webp"Generate a poster from the first frame of a modal video (requires ffmpeg):
cd kindly-snippet
chmod +x scripts/extract-video-frame.sh
./scripts/extract-video-frame.sh /path/to/modal.mp4 video_1-poster.webp
# or: yarn extract-poster /path/to/modal.mp4 video_1-poster.webp
cp video_1-poster.webp ../backend/build/assets/images/ # local UPLOADED_IMAGES_DESTThen set VITE_SNIPPET_ID in demo/.env or demo/.env.local to whichever snippet you want to preview (for example christian_group_1 or christian_group_2).
Then:
cd kindly-snippet
yarn install
yarn devUse demo/.env.local to override (see demo/.env.local.example).
Example app consuming npm package
To verify the published package works end-to-end via npm install / yarn add, see examples/npm-react-vite.
CI and npm releases (maintainers)
The package is @kindlyhuman/snippet, published to the public npm registry (publishConfig.access: public), not the GitLab package registry.
- On npmjs.com, configure Trusted publishing for
@kindlyhuman/snippetusing GitLab CI/CD (OIDC). The CI file path must match.gitlab-ci.ymlexactly. - CI publishes on version tags (e.g.
v0.1.0) by runningyarn buildthennpm publish --access public. - Ensure the npm org
@kindlyhumanallows this package and the token’s user (or automation user) has publish access.
The component-library repo publishes to GitLab’s npm registry with CI_JOB_TOKEN and publishConfig.access: restricted; this repo intentionally differs so external client sites can npm install / yarn add without GitLab auth.
Build
From this directory:
yarn buildOutputs:
dist/kindly-snippet.iife.js— single file for<script src="…">(includes React).dist/index.mjs/dist/index.cjs—init/destroy(React as peer dependency).dist/react/index.mjs/dist/react/index.cjs—<KindlySnippet />wrapper.
Backend: snippet_embeds
Snippet configs are stored per row in snippet_embeds (client_id FK to clients, unique public snippet_id, enabled, and JSON configuration). The public endpoint returns 404 unless:
- the snippet exists and
enabledis true - the owning client is active and non-archived
- required fields are present in
configuration
| Field | Required | Description |
| --- | --- | --- |
| tagline | yes | Modal subheadline |
| tease_text | yes | Text on launcher hover (e.g. “I’ve been there.”) |
| show_online_indicator | no | Default false; shows pulsing green dot on launcher |
| teaser_video_url | yes | URL for small launcher video (muted, loop) |
| teaser_poster_url | no | Poster for launcher video |
| modal_video_url | yes | URL for modal video |
| modal_poster_url | no | Poster for modal video |
| primary_cta_label | yes | Primary button label |
| primary_cta_url | yes | Primary button URL (https) |
| secondary_cta_label | yes | Secondary button label |
| secondary_cta_url | yes | Secondary button URL |
| client_portal_base_url | yes | Client portal / benefits base URL (used for analytics or future links) |
| logo_url | no | Absolute URL of a PNG/SVG/WebP wordmark for the modal header; omit or null for the default Kindly Human SVG |
The widget resolves the snippet with snippet_id.
