Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bin/check-gt-navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ function assertUiStrings(): void {
for (const section of [
"account",
"actions",
"feedback",
"navigation",
"search",
"status",
Expand Down
16 changes: 16 additions & 0 deletions i18n/en/ui.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,22 @@
"view": "View",
"viewMarkdown": "View as Markdown"
},
"feedback": {
"additionalFeedback": "Additional feedback (optional)",
"close": "Close feedback form",
"confusing": "Hard to follow or confusing",
"error": "Feedback could not be sent. Please try again.",
"inaccurate": "Inaccurate, out of date, or doesn't work",
"missingInformation": "Missing information",
"negativeLabel": "This page could be improved",
"negativeQuestion": "Why was it not helpful?",
"other": "Something else",
"positiveLabel": "This page was helpful",
"question": "Was this page helpful?",
"submit": "Submit",
"submitting": "Sending…",
"thankYou": "Thanks for your feedback!"
},
"navigation": {
"breadcrumb": "Breadcrumb",
"chooseLanguage": "Choose documentation language",
Expand Down
288 changes: 288 additions & 0 deletions src/components/Feedback.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
---
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { uiStrings } from "@/lib/ui-strings.server";

interface Props {
currentLocale?: string;
}

const { currentLocale } = Astro.props;
const messages = uiStrings(currentLocale).feedback;
---

<section
data-ch-feedback
data-submitting-label={messages.submitting}
data-thank-you-label={messages.thankYou}
data-error-label={messages.error}
class="ch-feedback"
aria-labelledby="ch-feedback-question"
>
<p id="ch-feedback-question" data-ch-feedback-question aria-live="polite">
{messages.question}
</p>

<div class="ch-feedback-actions">
<button
type="button"
class="ch-feedback-button"
data-ch-feedback-positive
aria-label={messages.positiveLabel}
aria-pressed="false"
>
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path d="M2 6.5h3V13H2a.5.5 0 0 1-.5-.5V7a.5.5 0 0 1 .5-.5Z" />
<path d="m5 6.5 2.5-5a2 2 0 0 1 2 2V5h4a1 1 0 0 1 .992 1.125l-.75 6a1 1 0 0 1-.992.875H5" />
</svg>
</button>

<Popover class="ch-feedback-popover">
<PopoverTrigger
class="ch-feedback-button"
data-ch-feedback-negative
aria-label={messages.negativeLabel}
aria-pressed="false"
>
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path d="M2 3h3v6.5H2a.5.5 0 0 1-.5-.5V3.5A.5.5 0 0 1 2 3Z" />
<path d="m5 9.5 2.5 5a2 2 0 0 0 2-2V11h4a1 1 0 0 0 .992-1.125l-.75-6A1 1 0 0 0 12.75 3H5" />
</svg>
</PopoverTrigger>

<PopoverContent placement="bottom-end" class="ch-feedback-form-panel">
<form data-ch-feedback-form>
<div class="ch-feedback-form-heading">
<h2>{messages.negativeQuestion}</h2>
<button
type="button"
class="ch-feedback-close"
data-ch-feedback-close
aria-label={messages.close}
>
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path d="m4 4 8 8M12 4l-8 8" />
</svg>
</button>
</div>

<fieldset>
<legend class="sr-only">{messages.negativeQuestion}</legend>
<label><input type="radio" name="reason" value="missing_info" /> {messages.missingInformation}</label>
<label><input type="radio" name="reason" value="confusing" /> {messages.confusing}</label>
<label><input type="radio" name="reason" value="inaccurate" /> {messages.inaccurate}</label>
<label><input type="radio" name="reason" value="other" /> {messages.other}</label>
</fieldset>

<label class="sr-only" for="ch-feedback-comment">{messages.additionalFeedback}</label>
<textarea
id="ch-feedback-comment"
name="comment"
rows="3"
placeholder={messages.additionalFeedback}
></textarea>

<button type="submit" class="ch-feedback-submit">{messages.submit}</button>
</form>
</PopoverContent>
</Popover>
</div>

<p data-ch-feedback-status class="ch-feedback-status" role="status" aria-live="polite"></p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Status hidden under feedback popover

Medium Severity

Moving [data-ch-feedback-status] out of the negative-feedback form places submitting and error copy under the open bottom-end popover. Those messages stay obscured while the form is still up, so a failed thumbs-down submit can look like it did nothing.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2c789f4. Configure here.

</section>

<script>
import "./feedback.client";
</script>

<style>
.ch-feedback {
margin-top: 16px;
padding: 12px;
border: 1px solid var(--nb-border);
border-radius: 6px;
background: var(--nb-background);
}

.ch-feedback > [data-ch-feedback-question] {
margin: 0;
color: var(--nb-muted-foreground);
font-size: 13px;
font-weight: 600;
line-height: 18px;
}

.ch-feedback-actions {
display: flex;
gap: 8px;
margin-top: 6px;
}

.ch-feedback-popover {
display: flex;
}

.ch-feedback-button {
width: 26px;
height: 26px;
display: grid;
place-items: center;
padding: 0;
border: 1px solid var(--nb-border-strong);
border-radius: 4px;
color: var(--nb-muted-foreground);
background: transparent;
cursor: pointer;
}

.ch-feedback-button:hover {
color: var(--nb-heading);
background: var(--nb-accent);
}

.ch-feedback-button[data-selected] {
border-color: color-mix(in srgb, var(--nb-heading) 24%, var(--nb-border-strong));
color: var(--nb-heading);
background: var(--nb-accent);
}

.ch-feedback-button:disabled {
cursor: default;
opacity: 0.6;
}

.ch-feedback-button svg {
width: 14px;
height: 14px;
stroke: currentColor;
stroke-width: 1.25;
stroke-linecap: round;
stroke-linejoin: round;
}

.ch-feedback-form-panel {
width: min(320px, calc(100vw - 16px));
padding: 16px;
}

.ch-feedback-form-heading {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}

.ch-feedback-form-heading h2 {
margin: 0;
color: var(--nb-heading);
font-size: 15px;
font-weight: 650;
line-height: 20px;
}

.ch-feedback-close {
width: 24px;
height: 24px;
display: grid;
flex: 0 0 auto;
place-items: center;
margin: -4px -4px 0 0;
padding: 0;
border: 0;
border-radius: 4px;
color: var(--nb-muted-foreground);
background: transparent;
cursor: pointer;
}

.ch-feedback-close:hover {
color: var(--nb-heading);
background: var(--nb-accent);
}

.ch-feedback-close svg {
width: 14px;
height: 14px;
stroke: currentColor;
stroke-width: 1.5;
stroke-linecap: round;
}

.ch-feedback-form-panel fieldset {
display: flex;
flex-direction: column;
gap: 7px;
margin: 0;
padding: 0;
border: 0;
}

.ch-feedback-form-panel fieldset label {
display: flex;
align-items: flex-start;
gap: 8px;
color: var(--nb-foreground);
font-size: 13px;
line-height: 18px;
cursor: pointer;
}

.ch-feedback-form-panel input[type="radio"] {
margin: 2px 0 0;
accent-color: var(--nb-primary);
}

.ch-feedback-form-panel textarea {
width: 100%;
min-height: 72px;
resize: vertical;
margin-top: 12px;
padding: 8px 10px;
border: 1px solid var(--nb-border-strong);
border-radius: 5px;
color: var(--nb-foreground);
background: var(--nb-background);
font: inherit;
font-size: 13px;
line-height: 18px;
}

.ch-feedback-form-panel textarea::placeholder {
color: var(--nb-muted-foreground);
}

.ch-feedback-submit {
width: 100%;
min-height: 34px;
margin-top: 10px;
padding: 6px 12px;
border: 1px solid #d5d95c;
border-radius: 4px;
color: #1c1c1c;
background: #fdff75;
font-size: 13px;
font-weight: 650;
cursor: pointer;
}

.ch-feedback-submit:hover {
background: #f1f36e;
}

.ch-feedback-submit:disabled {
cursor: wait;
opacity: 0.65;
}

.ch-feedback-status {
min-height: 0;
margin: 6px 0 0;
color: #b42318;
font-size: 12px;
line-height: 16px;
}

.ch-feedback-status:empty {
display: none;
}
</style>
Loading