From 4cf97424a1479bf3ead390ffb354a183c220bcac Mon Sep 17 00:00:00 2001 From: Ryan Albrecht Date: Fri, 10 May 2024 07:49:09 -0700 Subject: [PATCH 1/9] docs(feedback): Add migration docs for moving from feedbackIntegration 7.x to 8.0.0 (#11731) As the User Feedback feature moves from alpha/beta to GA some changes to the public API have been made, and version requirements for the server (for on-prem users) are being solidified. Related to https://github.com/getsentry/develop/issues/1206 --------- Co-authored-by: Catherine Lee <55311782+c298lee@users.noreply.github.com> Co-authored-by: Billy Vong --- MIGRATION.md | 7 ++ docs/migration/feedback.md | 149 +++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 docs/migration/feedback.md diff --git a/MIGRATION.md b/MIGRATION.md index ef1f17dcfe7d..d12ed394b05a 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1283,6 +1283,13 @@ export class HeaderComponent { } ``` +# Upgrading Sentry Feedback (beta, 7.x to 8.0) + +For details on upgrading Feedback from the beta 7.x to the release 8.x version, please view the +[dedicated Feedback MIGRATION docs](./docs/migration/feedback.md). + +--- + # Deprecations in 7.x You can use the **Experimental** [@sentry/migr8](https://www.npmjs.com/package/@sentry/migr8) to automatically update diff --git a/docs/migration/feedback.md b/docs/migration/feedback.md new file mode 100644 index 000000000000..6d9c189df1c2 --- /dev/null +++ b/docs/migration/feedback.md @@ -0,0 +1,149 @@ +# End of Feedback Beta + +With the release of 8.0.0, Sentry Feedback is now out of Beta. This means that the usual stabilty guarantees apply. + +Feedback 8.0.0 requires server version 24.4.2 and above. + +Because of experimentation and rapid iteration, during the Beta period some bugs and problems came up which have since +been fixed/improved, as well as API's which have been streamlined and changed. + +Below you can find a list of relevant feedback changes and issues that have been made from 7.x to 8.0.0. + +## Upgrading Feedback from 7.x to 8.0.0 + +We have streamlined the interface for interacting with the Feedback widget. Below is a list of public functions that +existed in 7.x and a description of how they have changed in v8. + +| Method Name | Replacement | Notes | +| ------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Sentry.getClient()?.getIntegration(Feedback)` | `const feedback = Sentry.getFeedback()` | Get a type-safe reference to the configured feedbackIntegration instance. | +| `feedback.getWidget()` | `const widget = feedback.createWidget(); widget.appendToDom()` | The SDK no longer maintains a stack of form instances. If you call `createWidget()` a new widget will be inserted into the DOM and an `ActorComponent` returned allows you control over the lifecycle of the widget. | +| `feedback.openDialog()` | `widget.open()` | Make the form inside the widget visible. | +| `feedback.closeDialog()` | `widget.close()` | Make the form inside the widget hidden in the page. Success/Error messages will still be rendered and will hide themselves if the form was recently submitted. | +| `feedback.removeWidget()` | `widget.removeFromDom()` | Remove the form and widget instance from the page. After calling this `widget.el.parentNode` will be set to null. | +| `feedback.attachTo()` | `const unsubscribe = feedback.attachTo(myButtonElem)` | The `attachTo()` method will create an onClick event listener to your html element that calls `appendToDom()` and `open()`. It returns a callback to remove the event listener. | +| - | `const form = await feedback.createForm()` | A new method `createForm()`, used internally by `createWidget()` and `attachTo()`, returns a `Promise` so you can control showing and hiding of the feedback form directly. | + +### API Examples + +#### Auto injecting Feedback + +The easiest way to get setup is to auto-inject the feedback widget. This will create a floating button which opens the +feedback form. + +In your SDK setup: + +```javascript +Sentry.init({ + integrations: [ + feedbackIntegration({ + autoInject: true, + }), + ], +}); +``` + +`autoInject: true` is the default value. + +#### Attaching feedback to your own button + +If you don't want to have a floating button to trigger the feedback form, you can attach the form to your own DOM +element instead. + +First, get a reference to the feedback integration instance: + +```javascript +// Option 1: Keep a reference when you setup the sdk: +const feedbackInstance = feedbackIntegration(); +Sentry.init({ + integrations: [feedbackInstance], +}); + +// Option 2: Get a reference from the SDK client +const feedbackInstance = getFeedback(); +``` + +Next, call `attachTo()` + +```javascript +const myButton = document.getElementById('my-button'); +const unsubscribe = feedbackInstance.attachTo(myButton); +``` + +This will insert the form into the DOM and show/hide it when the button is clicked. + +Later, if `my-button` is removed from the page be sure to call `unsubscribe()` or `feedbackInstance.remove()` to cleanup +the event listeners. + +#### Manually managing show/hide state and adding/remove the form from the DOM. + +You can manually add/remove the widget from the page, and control when it's shown/hidden by calling the lifecycle +methods directly. + +For example, `attachTo()` is a convenience wrapper over the lifecycle methods. You could re-implement it like this: + +```javascript +function attachTo(button: HTMLElement) { + const handleClick = () => { + const widget = feedbackInstance.createWidget({ + onFormClose: () => { + widget.close(); + }, + onFormSubmited: () => { + widget.removeFromDom(); + } + }); + widget.appendToDom(); + widget.open(); + }; + + button.addEventListener('click', handleClick); + return () => { + button.removeEventListener('click', handleClick) + } +} +``` + +Alternatively you can call `createForm()` and control the form directly: + +```javascript +const formPromise = feedbackInstance.createForm(); + +// Automatically insert and open the dialog after 5 seconds +// then close and remove it after another 10 seconds +setTimeout(() => { + const form = await formPromise; + form.appendToDom(); + form.open(); + + setTimeout(() => { + form.close(); + form.removeFromDom(); + }, 10_000); +}, 5_000); +``` + +## Config changes in v8 + +Added new configuration values + +| Field Name | Type | Description | +| ------------------ | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `enableScreenshot` | `boolean` | Default: true. Enable this option to allow a user to choose to include a screenshot of the webpage with their feedback submission. The user option is not supported on mobile browsers and will have no effect there. | +| `onFormSubmitted` | `() => void` | Callback whenever the feedback form is submitted, but before success/failure is returned. | + +Some new configuration values have been added & changed so you can tweak instructions, or translate labels for your +users. + +| Old Name | New Name | Default Value | +| ---------------- | ----------------------- | ------------------------------ | +| `buttonLabel` | `triggerLabel` | `"Report a bug"` | +| `isRequiredText` | `isRequiredLabel` | `"(required)"` | +| - | `addScreenshotLabel` | `"Add a screenshot"` | +| - | `removeScreenshotLabel` | `"Remove Screenshot"` | +| - | `confirmButtonLabel` | `"Confirm"` | +| - | `successMessageText` | `"Thank you for your report!"` | + +Some theme/color configuration values have been added & changed to make it easier to style the widget. Refer to the +[Feedback Configuration docs](https://docs.sentry.io/platforms/javascript/user-feedback/configuration/#user-feedback-widget) +to see the supported fields and their default values. From deabca378365fffc99cefccfa47ba9d356d8ddb5 Mon Sep 17 00:00:00 2001 From: Andrei <168741329+andreiborza@users.noreply.github.com> Date: Fri, 10 May 2024 16:52:35 +0200 Subject: [PATCH 2/9] ref(docs): Update migration guide to use --require/--import flag (#11970) Co-authored-by: Luca Forstner --- MIGRATION.md | 6 +-- docs/v8-initializing.md | 2 +- docs/v8-node.md | 86 +++++++++++++++++++++++++++++++++-------- 3 files changed, 73 insertions(+), 21 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index d12ed394b05a..938e8cb90f78 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -20,9 +20,9 @@ stable release of `8.x` comes out). ## 1. Version Support changes: -**Node.js**: We now official support Node 14.18+ for our CJS package, and Node 18.8+ for our ESM package. This applies -to `@sentry/node` and all of our node-based server-side sdks (`@sentry/nextjs`, `@sentry/serverless`, etc.). We no -longer test against Node 8, 10, or 12 and cannot guarantee that the SDK will work as expected on these versions. +**Node.js**: We now officially support Node 14.18+ for our CJS package, and Node 18.19.1+ for our ESM package. This +applies to `@sentry/node` and all of our node-based server-side sdks (`@sentry/nextjs`, `@sentry/serverless`, etc.). We +no longer test against Node 8, 10, or 12 and cannot guarantee that the SDK will work as expected on these versions. **Browser**: Our browser SDKs (`@sentry/browser`, `@sentry/react`, `@sentry/vue`, etc.) now require ES2018+ compatible browsers. This means that we no longer support IE11 (end of an era). This also means that the Browser SDK requires the diff --git a/docs/v8-initializing.md b/docs/v8-initializing.md index 238f02aa1bf9..b5c4bf2a0478 100644 --- a/docs/v8-initializing.md +++ b/docs/v8-initializing.md @@ -21,7 +21,7 @@ In an environment with multiple execution contexts (e.g. Node), you can setup mu different contexts, like this: ```js -import * as Sentry from '@sentry/browser'; +import * as Sentry from '@sentry/node'; // Sets up the _default_ client Sentry.init({ diff --git a/docs/v8-node.md b/docs/v8-node.md index b74266a7fbc1..73339494bbf8 100644 --- a/docs/v8-node.md +++ b/docs/v8-node.md @@ -16,7 +16,8 @@ We support the following Node Frameworks out of the box: - [Express](#express) - [Fastify](#fastify) -- Koa +- [Connect](#connect) +- [Koa](#koa) - Nest.js - Hapi @@ -52,13 +53,36 @@ Sentry.init({ const app = express(); ``` +We recommend creating a file named `instrument.js` that imports and initializes Sentry. + ```js -// In v8, in order to ensure express is instrumented, -// you have to initialize before you import: +// In v8, create a separate file that initializes sentry. +// Then pass the file to Node via --require or --import. const Sentry = require('@sentry/node'); Sentry.init({ // ... }); +``` + +Adjust the Node.js call for your application to use the [--require](https://nodejs.org/api/cli.html#-r---require-module) +or [--import](https://nodejs.org/api/cli.html#--importmodule) parameter and point it at `instrument.js`. Using +`--require` or `--import` is the easiest way to guarantee that Sentry is imported and initialized before any other +modules in your application + +```bash +# If you are using CommonJS (CJS) +node --require ./instrument.js app.js + +# If you are using ECMAScript Modules (ESM) +# Note: This is only available for Node v18.19.0 onwards. +node --import ./instrument.mjs app.mjs +``` + +**Alternatively**, if you cannot run node with `--require` or `--import`, add a top level import of `instrument.js` in +your application. + +```js +require('./instrument.js'); const express = require('express'); const app = express(); @@ -75,8 +99,11 @@ See [New Performance APIs](./v8-new-performance-apis.md) for details. ### ESM Support -For now, ESM support is only experimental. For the time being we only fully support CJS-based Node application - we are -working on this during the v8 alpha/beta cycle. +Instrumentation works out of the box for CommonJS (CJS) applications based on require() calls. This means that as long +as your application is either natively in CJS, or compiled at build time to CJS, everything will work without any +further setup. + +ECMAScript Modules (ESM) are only supported for Node v18.19.0 onwards. ### Using Custom OpenTelemetry Instrumentation @@ -153,12 +180,6 @@ your Express app. ```js const Sentry = require('@sentry/node'); - -Sentry.init({ - dsn: '__DSN__', - tracesSampleRate: 1, -}); - const express = require('express'); const app = express(); @@ -177,12 +198,6 @@ your Fastify app. ```js const Sentry = require('@sentry/node'); - -Sentry.init({ - dsn: '__DSN__', - tracesSampleRate: 1, -}); - const { fastify } = require('fastify'); const app = fastify(); Sentry.setupFastifyErrorHandler(app); @@ -191,3 +206,40 @@ Sentry.setupFastifyErrorHandler(app); app.listen(); ``` + +## Connect + +The following shows how you can setup Connect instrumentation in v8. This will capture performance data & errors for +your Fastify app. + +```js +const connect = require('connect'); +const Sentry = require('@sentry/node'); +const app = connect(); + +Sentry.setupConnectErrorHandler(app); + +// Add your routes, etc. + +app.listen(3030); +``` + +## Koa + +The following shows how you can setup Koa instrumentation in v8. This will capture performance data & errors for your +Fastify app. + +```js +const Koa = require('koa'); +const Router = require('@koa/router'); +const Sentry = require('@sentry/node'); + +const router = new Router(); +const app = new Koa(); + +Sentry.setupKoaErrorHandler(app); + +// Add your routes, etc. + +app.listen(3030); +``` From 1d2602ca30f6db5d9996b117ac4ae6db5e4aca37 Mon Sep 17 00:00:00 2001 From: Ryan Albrecht Date: Sun, 12 May 2024 17:03:58 -0700 Subject: [PATCH 3/9] feat(feedback): Simplify public css configuration for feedback (#11985) --- packages/feedback/src/constants/index.ts | 4 - packages/feedback/src/constants/theme.ts | 58 ------- .../feedback/src/core/components/Actor.css.ts | 15 +- .../feedback/src/core/createMainStyles.ts | 59 +++++-- packages/feedback/src/core/integration.ts | 15 +- .../src/modal/components/Dialog.css.ts | 155 +++++++----------- packages/feedback/src/modal/integration.tsx | 2 +- .../components/ScreenshotEditor.tsx | 4 +- .../components/ScreenshotInput.css.ts | 8 +- packages/types/src/feedback/theme.ts | 155 ++---------------- 10 files changed, 142 insertions(+), 333 deletions(-) delete mode 100644 packages/feedback/src/constants/theme.ts diff --git a/packages/feedback/src/constants/index.ts b/packages/feedback/src/constants/index.ts index a8794ed663b0..2a6bc0d171aa 100644 --- a/packages/feedback/src/constants/index.ts +++ b/packages/feedback/src/constants/index.ts @@ -1,7 +1,5 @@ import { GLOBAL_OBJ } from '@sentry/utils'; -export { DEFAULT_THEME } from './theme'; - // exporting a separate copy of `WINDOW` rather than exporting the one from `@sentry/browser` // prevents the browser package from being bundled in the CDN bundle, and avoids a // circular dependency between the browser and feedback packages @@ -29,5 +27,3 @@ export const FEEDBACK_WIDGET_SOURCE = 'widget'; export const FEEDBACK_API_SOURCE = 'api'; export const SUCCESS_MESSAGE_TIMEOUT = 5000; - -export const CROP_COLOR = '#ffffff'; diff --git a/packages/feedback/src/constants/theme.ts b/packages/feedback/src/constants/theme.ts deleted file mode 100644 index ba8193afd9d0..000000000000 --- a/packages/feedback/src/constants/theme.ts +++ /dev/null @@ -1,58 +0,0 @@ -const INHERIT = 'inherit'; -const PURPLE = 'rgba(88, 74, 192, 1)'; -const PURPLE_HOVER = 'rgba(108, 95, 199, 1)'; - -export const LIGHT_THEME = { - foreground: '#2b2233', - successForeground: '#268d75', - errorForeground: '#df3338', - background: '#ffffff', - border: '1.5px solid rgba(41, 35, 47, 0.13)', - boxShadow: '0px 4px 24px 0px rgba(43, 34, 51, 0.12)', - - inputForeground: INHERIT, - inputBackground: INHERIT, - inputBackgroundHover: INHERIT, - inputBackgroundFocus: INHERIT, - inputBorder: 'var(--border)', - inputBorderRadius: '6px', - inputOutlineFocus: PURPLE_HOVER, - - buttonForeground: INHERIT, - buttonForegroundHover: INHERIT, - buttonBackground: 'var(--background)', - buttonBackgroundHover: '#f6f6f7', - buttonBorder: 'var(--border)', - buttonOutlineFocus: 'var(--input-outline-focus)', - - submitForeground: '#ffffff', - submitForegroundHover: '#ffffff', - submitBackground: PURPLE, - submitBackgroundHover: PURPLE_HOVER, - submitBorder: PURPLE_HOVER, - submitBorderRadius: 'var(--button-border-radius)', - submitOutlineFocus: '#29232f', - - triggerBackground: 'var(--background)', - triggerBackgroundHover: 'var(--button-background-hover)', - triggerBorderRadius: '1.7em/50%', - - dialogBackground: 'var(--background)', - dialogBorderRadius: '20px', -}; - -export const DEFAULT_THEME = { - light: LIGHT_THEME, - dark: { - ...LIGHT_THEME, - - foreground: '#ebe6ef', - successForeground: '#2da98c', - errorForeground: '#f55459', - background: '#29232f', - border: '1.5px solid rgba(235, 230, 239, 0.5)', - boxShadow: '0px 4px 24px 0px rgba(43, 34, 51, 0.12)', - - buttonBackgroundHover: '#352f3b', - }, -}; diff --git a/packages/feedback/src/core/components/Actor.css.ts b/packages/feedback/src/core/components/Actor.css.ts index 3daa58d09a99..60ae7cebd08e 100644 --- a/packages/feedback/src/core/components/Actor.css.ts +++ b/packages/feedback/src/core/components/Actor.css.ts @@ -23,12 +23,12 @@ export function createActorStyles(): HTMLStyleElement { line-height: 1.14em; text-decoration: none; - background-color: var(--trigger-background); - border-radius: var(--trigger-border-radius); - border: var(--border); - box-shadow: var(--box-shadow); - color: var(--foreground); - fill: var(--foreground); + background: var(--actor-background, var(--background)); + border-radius: var(--actor-border-radius, 1.7em/50%); + border: var(--actor-border, var(--border)); + box-shadow: var(--actor-box-shadow, var(--box-shadow)); + color: var(--actor-color, var(--foreground)); + fill: var(--actor-color, var(--foreground)); cursor: pointer; opacity: 1; transition: transform 0.2s ease-in-out; @@ -42,7 +42,8 @@ export function createActorStyles(): HTMLStyleElement { } .widget__actor:hover { - background-color: var(--trigger-background-hover); + background: var(--actor-hover-background, var(--background)); + filter: var(--interactive-filter); } .widget__actor svg { diff --git a/packages/feedback/src/core/createMainStyles.ts b/packages/feedback/src/core/createMainStyles.ts index b60bba37b6e3..95541ab4b06b 100644 --- a/packages/feedback/src/core/createMainStyles.ts +++ b/packages/feedback/src/core/createMainStyles.ts @@ -1,25 +1,50 @@ import type { FeedbackInternalOptions } from '@sentry/types'; import { DOCUMENT } from '../constants'; -function getThemedCssVariables(theme: FeedbackInternalOptions['themeLight']): string { +const PURPLE = 'rgba(88, 74, 192, 1)'; + +interface InternalTheme extends NonNullable { + border: string; + interactiveFilter: string; +} + +const DEFAULT_LIGHT: InternalTheme = { + foreground: '#2b2233', + background: '#ffffff', + accentForeground: 'white', + accentBackground: PURPLE, + successColor: '#268d75', + errorColor: '#df3338', + border: '1.5px solid rgba(41, 35, 47, 0.13)', + boxShadow: '0px 4px 24px 0px rgba(43, 34, 51, 0.12)', + outline: '1px auto var(--accent-background)', + interactiveFilter: 'brightness(95%)', +}; +const DEFAULT_DARK: InternalTheme = { + foreground: '#ebe6ef', + background: '#29232f', + accentForeground: 'white', + accentBackground: PURPLE, + successColor: '#2da98c', + errorColor: '#f55459', + border: '1.5px solid rgba(41, 35, 47, 0.5)', + boxShadow: '0px 4px 24px 0px rgba(43, 34, 51, 0.12)', + outline: '1px auto var(--accent-background)', + interactiveFilter: 'brightness(150%)', +}; + +function getThemedCssVariables(theme: InternalTheme): string { return ` --foreground: ${theme.foreground}; - --success-foreground: ${theme.successForeground}; - --error-foreground: ${theme.errorForeground}; --background: ${theme.background}; + --accent-foreground: ${theme.accentForeground}; + --accent-background: ${theme.accentBackground}; + --success-color: ${theme.successColor}; + --error-color: ${theme.errorColor}; --border: ${theme.border}; --box-shadow: ${theme.boxShadow}; - - --button-foreground: ${theme.buttonForeground}; - --button-foreground-hover: ${theme.buttonForegroundHover}; - --button-background: ${theme.buttonBackground}; - --button-background-hover: ${theme.buttonBackgroundHover}; - --button-border: ${theme.buttonBorder}; - --button-outline-focus: ${theme.buttonOutlineFocus}; - - --trigger-background: ${theme.triggerBackground}; - --trigger-background-hover: ${theme.triggerBackgroundHover}; - --trigger-border-radius: ${theme.triggerBorderRadius}; + --outline: ${theme.outline}; + --interactive-filter: ${theme.interactiveFilter}; `; } @@ -41,7 +66,9 @@ export function createMainStyles({ colorScheme, themeDark, themeLight }: Feedbac font-family: var(--font-family); font-size: var(--font-size); - ${getThemedCssVariables(colorScheme === 'dark' ? themeDark : themeLight)} + ${getThemedCssVariables( + colorScheme === 'dark' ? { ...DEFAULT_DARK, ...themeDark } : { ...DEFAULT_LIGHT, ...themeLight }, + )} } ${ @@ -49,7 +76,7 @@ ${ ? ` @media (prefers-color-scheme: dark) { :host { - ${getThemedCssVariables(themeDark)} + ${getThemedCssVariables({ ...DEFAULT_DARK, ...themeDark })} } }` : '' diff --git a/packages/feedback/src/core/integration.ts b/packages/feedback/src/core/integration.ts index 9247f36b6990..61c8a83ef016 100644 --- a/packages/feedback/src/core/integration.ts +++ b/packages/feedback/src/core/integration.ts @@ -12,7 +12,6 @@ import { ADD_SCREENSHOT_LABEL, CANCEL_BUTTON_LABEL, CONFIRM_BUTTON_LABEL, - DEFAULT_THEME, DOCUMENT, EMAIL_LABEL, EMAIL_PLACEHOLDER, @@ -79,8 +78,8 @@ export const buildFeedbackIntegration = ({ // FeedbackThemeConfiguration colorScheme = 'system', - themeLight, - themeDark, + themeLight = {}, + themeDark = {}, // FeedbackTextConfiguration addScreenshotButtonLabel = ADD_SCREENSHOT_LABEL, @@ -118,14 +117,8 @@ export const buildFeedbackIntegration = ({ useSentryUser, colorScheme, - themeDark: { - ...DEFAULT_THEME.dark, - ...themeDark, - }, - themeLight: { - ...DEFAULT_THEME.light, - ...themeLight, - }, + themeDark, + themeLight, triggerLabel, cancelButtonLabel, diff --git a/packages/feedback/src/modal/components/Dialog.css.ts b/packages/feedback/src/modal/components/Dialog.css.ts index 8e280e6df4da..ddcd90184657 100644 --- a/packages/feedback/src/modal/components/Dialog.css.ts +++ b/packages/feedback/src/modal/components/Dialog.css.ts @@ -1,4 +1,3 @@ -import type { FeedbackInternalOptions } from '@sentry/types'; import { DOCUMENT } from '../../constants'; const DIALOG = ` @@ -15,8 +14,8 @@ const DIALOG = ` height: 100vh; width: 100vw; - color: var(--foreground); - fill: var(--foreground); + color: var(--dialog-color, var(--foreground)); + fill: var(--dialog-color, var(--foreground)); line-height: 1.75em; background-color: rgba(0, 0, 0, 0.05); @@ -59,16 +58,16 @@ const DIALOG = ` display: flex; flex-direction: column; gap: 16px; - padding: var(--dialog-padding); + padding: var(--dialog-padding, 24px); max-width: 100%; width: 100%; max-height: 100%; overflow: auto; - background-color: var(--dialog-background); - border-radius: var(--dialog-border-radius); - border: var(--border); - box-shadow: var(--box-shadow); + background: var(--dialog-background, var(--background)); + border-radius: var(--dialog-border-radius, 20px); + border: var(--dialog-border, var(--border)); + box-shadow: var(--dialog-box-shadow, var(--box-shadow)); transform: translate(0, 0) scale(1); transition: transform 0.2s ease-in-out; } @@ -79,7 +78,7 @@ const DIALOG_HEADER = ` display: flex; align-items: center; justify-content: space-between; - font-weight: 600; + font-weight: var(--dialog-header-weight, 600); margin: 0; } @@ -87,7 +86,7 @@ const DIALOG_HEADER = ` display: inline-flex; } .brand-link:focus-visible { - outline: 1px auto var(--input-outline-focus); + outline: var(--outline); } `; @@ -101,7 +100,7 @@ const FORM = ` } .form__right { - width: 272px; + width: var(--form-width, 272px); display: flex; overflow: auto; flex-direction: column; @@ -147,24 +146,25 @@ const FORM = ` .form__input { font-family: inherit; line-height: inherit; - background-color: var(--input-background); + background: transparent; box-sizing: border-box; - border: var(--input-border); - border-radius: var(--input-border-radius); - color: var(--input-foreground); - fill: var(--input-foreground); - font-size: var(--font-size); - font-weight: 500; + border: var(--input-border, var(--border)); + border-radius: var(--input-border-radius, 6px); + color: var(--input-color, inherit); + fill: var(--input-color, inherit); + font-size: var(--input-font-size, inherit); + font-weight: var(--input-font-weight, 500); padding: 6px 12px; } .form__input::placeholder { - color: var(--input-foreground); opacity: 0.65; + color: var(--input-placeholder-color, inherit); + filter: var(--interactive-filter); } .form__input:focus-visible { - outline: 1px auto var(--input-outline-focus); + outline: var(--input-focus-outline, var(--outline)); } .form__input--textarea { @@ -173,8 +173,8 @@ const FORM = ` } .error { - color: var(--error-foreground); - fill: var(--error-foreground); + color: var(--error-color); + fill: var(--error-color); } `; @@ -186,13 +186,13 @@ const BUTTON = ` .btn { line-height: inherit; - border: var(--cancel-border); - border-radius: var(--form-content-border-radius); + border: var(--button-border, var(--border)); + border-radius: var(--button-border-radius, 6px); cursor: pointer; font-family: inherit; - font-size: var(--font-size); - font-weight: 600; - padding: 6px 16px; + font-size: var(--button-font-size, inherit); + font-weight: var(--button-font-weight, 600); + padding: var(--button-padding, 6px 16px); } .btn[disabled] { opacity: 0.6; @@ -200,37 +200,43 @@ const BUTTON = ` } .btn--primary { - color: var(--submit-foreground); - fill: var(--submit-foreground); - background-color: var(--submit-background); - border: var(--submit-border); - border-radius: var(--input-border-radius); - font-weight: 500; + color: var(--button-primary-color, var(--accent-foreground)); + fill: var(--button-primary-color, var(--accent-foreground)); + background: var(--button-primary-background, var(--accent-background)); + border: var(--button-primary-border, var(--border)); + border-radius: var(--button-primary-border-radius, 6px); + font-weight: var(--button-primary-font-weight, 500); } .btn--primary:hover { - color: var(--submit-foreground-hover); - fill: var(--submit-foreground-hover); - background-color: var(--submit-background-hover); + color: var(--button-primary-hover-color, var(--accent-foreground)); + fill: var(--button-primary-hover-color, var(--accent-foreground)); + background: var(--button-primary-hover-background, var(--accent-background)); + filter: var(--interactive-filter); } .btn--primary:focus-visible { - outline: 1px auto var(--submit-outline-focus); + background: var(--button-primary-hover-background, var(--accent-background)); + filter: var(--interactive-filter); + outline: var(--button-primary-focus-outline, var(--outline)); } .btn--default { - color: var(--button-foreground); - fill: var(--button-foreground); - background-color: var(--button-background); - border: var(--button-border); - border-radius: var(--input-border-radius); - font-weight: 500; + color: var(--button-color, var(--foreground)); + fill: var(--button-color, var(--foreground)); + background: var(--button-background, var(--background)); + border: var(--button-border, var(--border)); + border-radius: var(--button-border-radius, 6px); + font-weight: var(--button-font-weight, 500); } .btn--default:hover { - color: var(--button-foreground-hover); - fill: var(--button-foreground-hover); - background-color: var(--button-background-hover); + color: var(--button-color, var(--foreground)); + fill: var(--button-color, var(--foreground)); + background: var(--button-hover-background, var(--background)); + filter: var(--interactive-filter); } .btn--default:focus-visible { - outline: 1px auto var(--button-outline-focus); + background: var(--button-hover-background, var(--background)); + filter: var(--interactive-filter); + outline: var(--button-focus-outline, var(--outline)); } `; @@ -242,15 +248,16 @@ const SUCCESS = ` z-index: var(--z-index); } .success__content { - background-color: var(--trigger-background); - border: var(--border); - border-radius: var(--trigger-border-radius); - box-shadow: var(--box-shadow); - font-weight: 600; - color: var(--success-foreground); - fill: var(--success-foreground); + background: var(--success-background, var(--background)); + border: var(--success-border, var(--border)); + border-radius: var(--success-border-radius, 1.7em/50%); + box-shadow: var(--success-box-shadow, var(--box-shadow)); + font-weight: var(--success-font-weight, 600); + color: var(--success-color); + fill: var(--success-color); padding: 12px 24px; - line-height: 25px; + line-height: 1.75em; + display: grid; align-items: center; grid-auto-flow: column; @@ -263,51 +270,15 @@ const SUCCESS = ` } `; -function getThemedCssVariables(theme: FeedbackInternalOptions['themeLight']): string { - return ` - --input-border-radius: ${theme.inputBorderRadius}; - --input-background: ${theme.inputBackground}; - --input-background-hover: ${theme.inputBackgroundHover}; - --input-background-focus: ${theme.inputBackgroundFocus}; - --input-foreground: ${theme.inputForeground}; - --input-border: ${theme.inputBorder}; - --input-outline-focus: ${theme.inputOutlineFocus}; - - --submit-foreground: ${theme.submitForeground}; - --submit-foreground-hover: ${theme.submitForegroundHover}; - --submit-background: ${theme.submitBackground}; - --submit-background-hover: ${theme.submitBackgroundHover}; - --submit-border: ${theme.submitBorder}; - --submit-outline-focus: ${theme.submitOutlineFocus}; - - --dialog-background: ${theme.dialogBackground}; - --dialog-border-radius: ${theme.dialogBorderRadius}; - `; -} - /** * Creates