npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@casecore/support-widget

v0.7.0

Published

Embeddable CaseCore support ticket form for React applications

Readme

@casecore/support-widget

React 18/19 support form. It only calls an authenticated same-origin BFF; the CaseCore credential must stay in the host application's backend.

Installation

pnpm add @casecore/support-widget

The package is public and installs from npm without a registry token. Never place a CaseCore intake credential in browser environment variables.

import { SupportWidget } from '@casecore/support-widget';

<SupportWidget
  endpoint="/api/support/tickets"
  locale="ru"
  defaultContact={{ email: session.user.email, preferredChannel: 'email' }}
  request={authenticatedProductFetch}
/>;

The endpoint must be a same-origin path. The host BFF authenticates the current user and maps the widget payload to @casecore/intake-client. A host that uses bearer-token sessions can provide the narrow request(endpoint, init) adapter shown above. That adapter may add only the product session; a CaseCore intake credential must never be attached in browser code.

When the BFF response includes data.portalUrl, the success state renders an “open ticket” link. The widget never constructs the URL itself and keeps the case-number-only success state when the field is absent. CaseCore Intake returns an absolute path such as /portal/case/...; when the BFF and CaseCore Web use different public origins, the BFF must resolve that path against the configured public CaseCore Web origin before returning it to the widget. Do this server-side and do not log the resulting URL because it contains the reporter's bearer token.

Contact fields

defaultContact?: { email?: string; phone?: string; preferredChannel?: 'email' | 'phone' } pre-fills the Email/Phone inputs and preferred-contact-method choice. The user can still edit or clear any of it before submit — whatever is currently in the form is what gets sent, the default is never silently re-merged server-side.

Attachments

The dialog accepts files via a visible file picker, drag-and-drop onto the dialog, and pasting an image from the clipboard (Ctrl+V). Selected files get a name/size preview with a per-file remove control. Client-side validation uses the package-owned supportAttachmentLimits export and blocks submit with an inline error before any network call. This is a UX convenience only; the CaseCore API performs the authoritative check.

When at least one file is attached, the widget POSTs multipart/form-data to endpoint with a payload field (the same JSON body sent when there are no attachments, as a string) and one files part per attachment, so the host app's BFF can forward each file to intake-client.uploadAttachment() and pass the resulting tokens as attachments on createTicket(). With no attachments, the request body is unchanged (application/json).

Host styling

The dialog carries no inline styles. It ships one stylesheet of single-class selectors (support-form__*) reading CSS custom properties, injected into document.head on first open. Set any of the tokens below anywhere in the host's own CSS — they are declared inside :where(:root), which has zero specificity, so a plain :root { --support-accent: … } wins without !important:

:root {
  --support-accent: #0e7ab0;
  --support-accent-ink: #ffffff;
  --support-radius: 4px;
  --support-font: 'Inter', sans-serif;
}

The full list is exported as supportThemeTokens. A dark palette is applied under prefers-color-scheme: dark, and at 640px and below the dialog becomes a bottom sheet with a pinned header and action bar.

A host that owns the look entirely passes injectStyles={false} and ships its own rules against the same class names; supportFormStyles exports the default sheet if it wants to start from it. trigger, triggerClassName, and triggerStyle still control the opening button, and contextLabel puts a short "where this came from" chip in the footer.

The dialog is portaled to document.body, so transformed, clipped, or narrow host containers cannot constrain its fixed backdrop. It remains owned by CaseCore so responsive layout, focus handling, validation, locale copy, accessibility behavior, the self-service portal link, and the BFF contract change in one package.

Building your own form on the same contract

A product whose design system will not accept a foreign dialog imports the headless layer instead of the widget and renders its own markup. Validation, the attachment limits, the idempotency key, the multipart assembly and the BFF contract stay in this package:

import { useSupportForm } from '@casecore/support-widget';

const form = useSupportForm({ endpoint: '/api/support/tickets', context });

<input value={form.title} onChange={(event) => form.setTitle(event.target.value)} />;
{
  form.titleError ? <p>{form.titleError}</p> : null;
}
<button onClick={() => void form.submit()} disabled={form.state === 'submitting'}>
  Send
</button>;

form.state moves through idle → submitting → success | error. After an error the draft is kept: form.submit() retries under the same idempotency key, so a send that actually reached CaseCore before the connection dropped returns the original case instead of filing a second one. form.reset() clears the draft and takes a new key for the next delivery.

Несовместимые изменения в 0.7.0

Имена, которые виджет оставлял в чужом приложении, больше не содержат названия системы поддержки — оно описывает CaseCore, а не функцию, которую встраивает продукт:

| Было | Стало | | --------------------------------- | ------------------------- | | CaseCoreSupportWidget | SupportWidget | | CaseCoreContactWidget | ContactWidget | | useCaseCoreSupportForm | useSupportForm | | caseCoreSupportAttachmentLimits | supportAttachmentLimits | | caseCoreSupportWidgetVersion | supportWidgetVersion | | .casecore-support__* (классы) | .support-form__* | | --cc-* (переменные) | --support-* |

Совместимости со старыми именами нет намеренно: продукт правит импорты один раз, а не носит два словаря. HTTP-контракт с BFF не изменился.

Public contact form

ContactWidget is the anonymous marketing-site variant. It renders name, email, company, topic, message, consent, and a hidden website bot-trap field. Supply the current public Turnstile result through turnstileToken; the same-origin BFF passes it to intake-client.createLead().

<ContactWidget
  endpoint="/api/contact"
  locale="en"
  turnstileToken={turnstileToken}
  pageUrl={window.location.href}
  campaign="summer-demo"
/>

The component exposes no CaseCore credential prop.