-
Notifications
You must be signed in to change notification settings - Fork 8.5k
[Shared UX] Implement side nav v2 feedback snippet #234187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b278382
12113ea
e3be22d
3b309e9
8bc98f4
ae47b4c
975645c
83c6cc3
e4b8419
f587dcf
eddf696
a47b2b8
cc1cdf9
70665a8
8cb28e1
0b7538f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the "Elastic License | ||
| * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side | ||
| * Public License v 1"; you may not use this file except in compliance with, at | ||
| * your election, the "Elastic License 2.0", the "GNU Affero General Public | ||
| * License v3.0 only", or the "Server Side Public License, v 1". | ||
| */ | ||
|
|
||
| import React from 'react'; | ||
| import { FeedbackSnippet } from '@kbn/shared-ux-feedback-snippet'; | ||
| import type { SolutionId } from '@kbn/core-chrome-browser'; | ||
| import { FormattedMessage } from '@kbn/i18n-react'; | ||
|
|
||
| interface NavigationFeedbackSnippetProps { | ||
| solutionId: SolutionId; | ||
| } | ||
|
|
||
| const feedbackSnippetId = 'sideNavigationFeedback'; | ||
|
|
||
| const feedbackUrls: { [id in SolutionId]: string } = { | ||
| es: 'https://ela.st/search-nav-feedback', | ||
| chat: 'https://ela.st/search-nav-feedback', | ||
| oblt: 'https://ela.st/o11y-nav-feedback', | ||
| security: 'https://ela.st/security-nav-feedback', | ||
| }; | ||
|
|
||
| const feedbackButtonMessage = ( | ||
| <FormattedMessage | ||
| id="core.ui.chrome.sideNavigation.sideNavigation.feedbackButtonText" | ||
| defaultMessage="Navigation feedback" | ||
| /> | ||
| ); | ||
|
|
||
| const promptViewMessage = ( | ||
| <FormattedMessage | ||
| id="core.ui.chrome.sideNavigation.feedbackPanel.promptTitle" | ||
| defaultMessage="How's the navigation working for you?" | ||
| /> | ||
| ); | ||
|
|
||
| export const NavigationFeedbackSnippet = ({ solutionId }: NavigationFeedbackSnippetProps) => { | ||
| const feedbackSurveyUrl = feedbackUrls[solutionId]; | ||
|
|
||
| return ( | ||
| <FeedbackSnippet | ||
| feedbackButtonMessage={feedbackButtonMessage} | ||
| feedbackSnippetId={feedbackSnippetId} | ||
| promptViewMessage={promptViewMessage} | ||
| surveyUrl={feedbackSurveyUrl} | ||
| /> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| # @kbn/shared-ux-feedback-snippet | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we don't really need
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good to know, I can remove it in an upcoming PR 👍 |
||
|
|
||
| --- | ||
| id: sharedUX/Components/FeedbackSnippet | ||
| slug: /shared-ux/components/feedback_snippet | ||
| title: Feedback Snippet | ||
| summary: A component to gather user feedback that initially renders as a panel and becomes a button after interaction. | ||
| tags: ['shared-ux', 'component'] | ||
| date: 2025-09-11 | ||
| --- | ||
|
|
||
| # Feedback Snippet | ||
| A snippet to gather user feedback. It initially renders as a panel, and once interacted with, it becomes a persistent button. It manages its own state (panel vs. button) based on user interaction tracked in `localStorage`. | ||
|
|
||
| ## Behavior | ||
| The component has two main states: | ||
| - **Panel:** On its first render for a user, the component displays as a full panel with the `promptViewMessage` and options to provide positive ("Yes") or negative ("No") feedback. A "Dismiss" (x) button is also available. | ||
| - **Button:** The component uses the provided `feedbackSnippetId` to track whether the user has interacted with it. If a value is present for that key, the component will render as a button instead. | ||
|
|
||
| ## Feedback Panel Views | ||
| - **Prompt:** The panel shows a custom `promptViewMessage` to gather feedback from the user. | ||
| - **Positive:** The panel shows a thank you message and then automatically dismisses itself. | ||
| - **Negative:** The panel updates to show a custom `surveyUrl` call-to-action button. The panel remains visible until the user explicitly dismisses it or navigates to the survey (which opens in a new tab). | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the "Elastic License | ||
| * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side | ||
| * Public License v 1"; you may not use this file except in compliance with, at | ||
| * your election, the "Elastic License 2.0", the "GNU Affero General Public | ||
| * License v3.0 only", or the "Server Side Public License, v 1". | ||
| */ | ||
|
|
||
| export { FeedbackSnippet } from './src/feedback_snippet'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the "Elastic License | ||
| * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side | ||
| * Public License v 1"; you may not use this file except in compliance with, at | ||
| * your election, the "Elastic License 2.0", the "GNU Affero General Public | ||
| * License v3.0 only", or the "Server Side Public License, v 1". | ||
| */ | ||
|
|
||
| module.exports = { | ||
| preset: '@kbn/test/jest_node', | ||
| rootDir: '../../../../../../..', | ||
| roots: ['<rootDir>/src/platform/packages/shared/shared-ux/feedback_snippet'], | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "type": "shared-common", | ||
| "id": "@kbn/shared-ux-feedback-snippet", | ||
| "owner": "@elastic/appex-sharedux", | ||
| "group": "platform", | ||
| "visibility": "shared" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "name": "@kbn/shared-ux-feedback-snippet", | ||
| "private": true, | ||
| "version": "1.0.0", | ||
| "license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the "Elastic License | ||
| * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side | ||
| * Public License v 1"; you may not use this file except in compliance with, at | ||
| * your election, the "Elastic License 2.0", the "GNU Affero General Public | ||
| * License v3.0 only", or the "Server Side Public License, v 1". | ||
| */ | ||
|
|
||
| import { css } from '@emotion/react'; | ||
| import confetti from 'canvas-confetti'; | ||
| import React, { useEffect, useRef } from 'react'; | ||
|
|
||
| const Confetti = () => { | ||
| const canvasRef = useRef<HTMLCanvasElement>(null); | ||
|
|
||
| useEffect(() => { | ||
| let canvasConfetti: confetti.CreateTypes | null = null; | ||
| if (canvasRef.current) { | ||
| canvasConfetti = confetti.create(canvasRef.current, { | ||
| resize: true, | ||
| useWorker: true, | ||
| }); | ||
| canvasConfetti({ | ||
| origin: { y: 0 }, | ||
| startVelocity: 20, | ||
| spread: 90, | ||
| gravity: 1.3, | ||
| ticks: 250, | ||
| disableForReducedMotion: true, | ||
| }); | ||
| } | ||
| return () => { | ||
| canvasConfetti?.reset(); | ||
| }; | ||
| }, []); | ||
|
|
||
| return ( | ||
| <canvas | ||
| ref={canvasRef} | ||
| css={css` | ||
| position: absolute; | ||
| left: 0; | ||
| top: 0; | ||
| width: 100%; | ||
| height: 100%; | ||
| `} | ||
| /> | ||
| ); | ||
| }; | ||
|
|
||
| // We need to use the default export here because of the way React.lazy works | ||
| // eslint-disable-next-line import/no-default-export | ||
| export default Confetti; |
Uh oh!
There was an error while loading. Please reload this page.