-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
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
✨ feat: Add Message Feedback with Tag Options #5878
base: main
Are you sure you want to change the base?
Changes from 6 commits
8bee34e
439b88a
c43ef59
fba5263
6bb348d
aa651bd
9a07e10
fdd6977
580e2ce
a924986
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,150 @@ | ||
import React, { useState } from 'react'; | ||
import { cn } from '~/utils'; | ||
import { | ||
OGDialog, | ||
OGDialogContent, | ||
OGDialogTrigger, | ||
OGDialogHeader, | ||
OGDialogTitle, | ||
} from '~/components'; | ||
|
||
type FeedbackTagOptionsProps = { | ||
tagChoices: string[]; | ||
onSelectTag: (tag: string, text?: string) => void; | ||
}; | ||
|
||
const FeedbackTagOptions: React.FC<FeedbackTagOptionsProps> = ({ tagChoices, onSelectTag }) => { | ||
const [isDialogOpen, setIsDialogOpen] = useState(false); | ||
const [selectedTag, setSelectedTag] = useState<string | null>(null); | ||
const [text, setText] = useState(''); | ||
const [isDismissed, setIsDismissed] = useState(false); | ||
|
||
const inlineOptions = tagChoices.slice(0, 3); | ||
const hasMore = tagChoices.length > 3; | ||
|
||
const handleInlineTagClick = (tag: string) => { | ||
onSelectTag(tag); | ||
}; | ||
|
||
const handleSubmit = () => { | ||
if (selectedTag) { | ||
onSelectTag(selectedTag, text); | ||
setIsDialogOpen(false); | ||
} | ||
}; | ||
|
||
return ( | ||
<> | ||
{!isDismissed && ( | ||
<div className="mt-3 w-full relative"> | ||
<div className="min-h-[96px] w-full"> | ||
<div className="relative mt-2 flex w-full flex-col gap-3 rounded-lg border border-token-border-light p-4"> | ||
<div className="flex justify-between items-center"> | ||
<div className="text-sm text-token-text-secondary">Tell us more:</div> | ||
<button | ||
type="button" | ||
onClick={() => setIsDismissed(true)} | ||
className="text-xl text-token-text-secondary hover:text-token-text-primary" | ||
aria-label="Dismiss feedback options" | ||
> | ||
× | ||
</button> | ||
</div> | ||
<div className="flex flex-wrap gap-3"> | ||
{inlineOptions.map((tag) => ( | ||
<button | ||
key={tag} | ||
type="button" | ||
onClick={() => handleInlineTagClick(tag)} | ||
className="rounded-lg border border-token-border-light px-3 py-1 text-sm text-token-text-secondary hover:text-token-text-primary hover:bg-token-main-surface-secondary" | ||
> | ||
{tag} | ||
</button> | ||
))} | ||
{hasMore && ( | ||
<button | ||
type="button" | ||
onClick={() => { | ||
setIsDialogOpen(true); | ||
setSelectedTag(null); | ||
setText(''); | ||
}} | ||
className="rounded-lg border border-token-border-light px-3 py-1 text-sm text-token-text-secondary hover:text-token-text-primary hover:bg-token-main-surface-secondary" | ||
> | ||
More... | ||
</button> | ||
)} | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
)} | ||
|
||
{/* Dialog for additional feedback */} | ||
<OGDialog open={isDialogOpen} onOpenChange={setIsDialogOpen}> | ||
<OGDialogTrigger asChild> | ||
{/* Invisible trigger */} | ||
<span /> | ||
</OGDialogTrigger> | ||
<OGDialogContent className="w-11/12 max-w-xl"> | ||
<OGDialogHeader> | ||
<OGDialogTitle className="text-lg font-semibold leading-6 text-token-text-primary"> | ||
Provide additional feedback | ||
</OGDialogTitle> | ||
Comment on lines
+91
to
+93
Check failure Code scanning / ESLint disallow literal string Error
disallow literal string:
Provide additional feedback |
||
</OGDialogHeader> | ||
<div className="flex-grow overflow-y-auto p-4 sm:p-6"> | ||
<div className="flex flex-col gap-6"> | ||
<div className="flex flex-wrap gap-3"> | ||
{tagChoices.map((tag) => ( | ||
<button | ||
key={tag} | ||
type="button" | ||
onClick={() => setSelectedTag(tag)} | ||
className={cn( | ||
'relative rounded-lg border border-token-border-light px-3 py-1 text-sm', | ||
selectedTag === tag | ||
? 'bg-token-main-surface-secondary text-token-text-primary' | ||
: 'text-token-text-secondary hover:text-token-text-primary hover:bg-token-main-surface-secondary' | ||
)} | ||
> | ||
{tag} | ||
{selectedTag === tag && ( | ||
<span className="absolute -top-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full bg-blue-500 text-white text-xs"> | ||
✓ | ||
</span> | ||
Comment on lines
+112
to
+114
Check failure Code scanning / ESLint disallow literal string Error
disallow literal string:
✓ |
||
)} | ||
</button> | ||
))} | ||
</div> | ||
</div> | ||
<div className="mt-6"> | ||
<input | ||
id="feedback" | ||
aria-label="Additional feedback" | ||
type="text" | ||
placeholder="Additional Feedback (Optional)" | ||
className="block w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-gray-900 placeholder-gray-400 shadow-sm focus:border-blue-500 focus:outline-none focus:ring focus:ring-blue-200" | ||
value={text} | ||
onChange={(e) => setText(e.target.value)} | ||
/> | ||
</div> | ||
</div> | ||
<div className="flex w-full flex-row items-center justify-end p-4 border-t border-token-border-light"> | ||
<div className="flex flex-col gap-3 sm:flex-row-reverse"> | ||
<button | ||
type="button" | ||
onClick={handleSubmit} | ||
className={cn('btn btn-primary', !selectedTag && 'opacity-50 cursor-not-allowed')} | ||
disabled={!selectedTag} | ||
> | ||
Submit | ||
</button> | ||
Comment on lines
+139
to
+141
Check failure Code scanning / ESLint disallow literal string Error
disallow literal string: <button
type="button" onClick={handleSubmit} className={cn('btn btn-primary', !selectedTag && 'opacity-50 cursor-not-allowed')} disabled={!selectedTag} > Submit |
||
</div> | ||
</div> | ||
</OGDialogContent> | ||
</OGDialog> | ||
</> | ||
); | ||
}; | ||
|
||
export default FeedbackTagOptions; |
Check failure
Code scanning / ESLint
disallow literal string Error