@adres/mgda-ui 0.0.0-stage → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/brand-knowledge.json +451 -0
- package/knowledge/accordion.md +135 -0
- package/knowledge/activity-rings.md +123 -0
- package/knowledge/alert.md +184 -0
- package/knowledge/answer-actions.md +217 -0
- package/knowledge/aspect-ratio.md +82 -0
- package/knowledge/avatar.md +158 -0
- package/knowledge/badge.md +172 -0
- package/knowledge/bar-graph.md +167 -0
- package/knowledge/bento-grid.md +115 -0
- package/knowledge/blockquote.md +153 -0
- package/knowledge/box-list.md +177 -0
- package/knowledge/bullet-list.md +128 -0
- package/knowledge/button-group.md +179 -0
- package/knowledge/button.md +231 -0
- package/knowledge/calendar.md +168 -0
- package/knowledge/card.md +321 -0
- package/knowledge/cards-nav.md +170 -0
- package/knowledge/chain-of-thought.md +164 -0
- package/knowledge/chat-container.md +135 -0
- package/knowledge/chat-message.md +224 -0
- package/knowledge/checkbox-card.md +172 -0
- package/knowledge/checkbox.md +111 -0
- package/knowledge/citations.md +241 -0
- package/knowledge/combobox.md +265 -0
- package/knowledge/countdown-pill.md +153 -0
- package/knowledge/cta.md +118 -0
- package/knowledge/data-list.md +173 -0
- package/knowledge/data-table.md +220 -0
- package/knowledge/dialog.md +249 -0
- package/knowledge/drawer.md +236 -0
- package/knowledge/dropdown-menu.md +238 -0
- package/knowledge/dropzone.md +153 -0
- package/knowledge/email-chips-input.md +161 -0
- package/knowledge/empty-state.md +106 -0
- package/knowledge/entity-header.md +166 -0
- package/knowledge/feature-grid.md +120 -0
- package/knowledge/feedback-bar.md +127 -0
- package/knowledge/footer.md +116 -0
- package/knowledge/heading.md +189 -0
- package/knowledge/hero.md +361 -0
- package/knowledge/input-button.md +187 -0
- package/knowledge/input-group.md +185 -0
- package/knowledge/input.md +256 -0
- package/knowledge/inset.md +177 -0
- package/knowledge/kpi-card.md +206 -0
- package/knowledge/list-nav.md +181 -0
- package/knowledge/logo-cloud.md +102 -0
- package/knowledge/media-gallery.md +158 -0
- package/knowledge/message-actions.md +175 -0
- package/knowledge/nav-sidebar.md +225 -0
- package/knowledge/number-stepper.md +159 -0
- package/knowledge/page-header.md +109 -0
- package/knowledge/pagination.md +225 -0
- package/knowledge/popover.md +159 -0
- package/knowledge/pricing.md +132 -0
- package/knowledge/progress.md +156 -0
- package/knowledge/prompt-input.md +337 -0
- package/knowledge/prompt-suggestions.md +168 -0
- package/knowledge/quota-bar.md +160 -0
- package/knowledge/quote.md +116 -0
- package/knowledge/radio-card.md +172 -0
- package/knowledge/radio-group.md +135 -0
- package/knowledge/reasoning.md +142 -0
- package/knowledge/search.md +131 -0
- package/knowledge/section-nav.md +162 -0
- package/knowledge/segmented-control.md +181 -0
- package/knowledge/select-button.md +205 -0
- package/knowledge/select.md +210 -0
- package/knowledge/separator.md +75 -0
- package/knowledge/shimmer-text.md +140 -0
- package/knowledge/sidebar.md +227 -0
- package/knowledge/skeleton.md +142 -0
- package/knowledge/slider.md +133 -0
- package/knowledge/slideshow.md +159 -0
- package/knowledge/source.md +156 -0
- package/knowledge/spinner.md +120 -0
- package/knowledge/stat-strip.md +128 -0
- package/knowledge/stats.md +117 -0
- package/knowledge/step-indicator.md +157 -0
- package/knowledge/switch.md +88 -0
- package/knowledge/table.md +190 -0
- package/knowledge/tabs.md +169 -0
- package/knowledge/testimonials.md +115 -0
- package/knowledge/text-link.md +192 -0
- package/knowledge/text.md +194 -0
- package/knowledge/thinking-bar.md +115 -0
- package/knowledge/thinking-steps.md +140 -0
- package/knowledge/toaster.md +127 -0
- package/knowledge/toggle.md +113 -0
- package/knowledge/tool-call-card.md +241 -0
- package/knowledge/tooltip.md +120 -0
- package/knowledge/top-nav.md +138 -0
- package/knowledge.json +19854 -0
- package/package.json +94 -4
- package/src/components/accordion.stories.tsx +46 -0
- package/src/components/accordion.tsx +74 -0
- package/src/components/activity-rings.stories.tsx +73 -0
- package/src/components/activity-rings.tsx +121 -0
- package/src/components/alert.stories.tsx +43 -0
- package/src/components/alert.tsx +132 -0
- package/src/components/answer-actions.stories.tsx +54 -0
- package/src/components/answer-actions.test.tsx +37 -0
- package/src/components/answer-actions.tsx +313 -0
- package/src/components/aspect-ratio.stories.tsx +32 -0
- package/src/components/aspect-ratio.tsx +5 -0
- package/src/components/avatar.stories.tsx +40 -0
- package/src/components/avatar.tsx +154 -0
- package/src/components/badge.stories.tsx +27 -0
- package/src/components/badge.tsx +63 -0
- package/src/components/bar-graph.stories.tsx +28 -0
- package/src/components/bar-graph.tsx +204 -0
- package/src/components/bento-grid.stories.tsx +23 -0
- package/src/components/bento-grid.tsx +67 -0
- package/src/components/blockquote.stories.tsx +28 -0
- package/src/components/blockquote.tsx +88 -0
- package/src/components/box-list.stories.tsx +25 -0
- package/src/components/box-list.tsx +108 -0
- package/src/components/bullet-list.stories.tsx +22 -0
- package/src/components/bullet-list.tsx +101 -0
- package/src/components/button-group.stories.tsx +31 -0
- package/src/components/button-group.tsx +156 -0
- package/src/components/button.stories.tsx +66 -0
- package/src/components/button.test.ts +17 -0
- package/src/components/button.tsx +139 -0
- package/src/components/calendar.stories.tsx +25 -0
- package/src/components/calendar.tsx +367 -0
- package/src/components/card.stories.tsx +44 -0
- package/src/components/card.tsx +392 -0
- package/src/components/cards-nav.stories.tsx +35 -0
- package/src/components/cards-nav.tsx +115 -0
- package/src/components/chain-of-thought.stories.tsx +67 -0
- package/src/components/chain-of-thought.test.tsx +50 -0
- package/src/components/chain-of-thought.tsx +170 -0
- package/src/components/chat-container.stories.tsx +47 -0
- package/src/components/chat-container.test.tsx +49 -0
- package/src/components/chat-container.tsx +222 -0
- package/src/components/chat-message.stories.tsx +52 -0
- package/src/components/chat-message.test.tsx +36 -0
- package/src/components/chat-message.tsx +163 -0
- package/src/components/checkbox-card.stories.tsx +27 -0
- package/src/components/checkbox-card.tsx +200 -0
- package/src/components/checkbox.stories.tsx +26 -0
- package/src/components/checkbox.tsx +61 -0
- package/src/components/citations.stories.tsx +64 -0
- package/src/components/citations.test.tsx +61 -0
- package/src/components/citations.tsx +220 -0
- package/src/components/combobox.stories.tsx +184 -0
- package/src/components/combobox.test.tsx +82 -0
- package/src/components/combobox.tsx +407 -0
- package/src/components/countdown-pill.stories.tsx +24 -0
- package/src/components/countdown-pill.tsx +129 -0
- package/src/components/cta.stories.tsx +29 -0
- package/src/components/cta.tsx +52 -0
- package/src/components/data-list.stories.tsx +50 -0
- package/src/components/data-list.test.tsx +35 -0
- package/src/components/data-list.tsx +181 -0
- package/src/components/data-table.stories.tsx +29 -0
- package/src/components/data-table.tsx +200 -0
- package/src/components/dialog.stories.tsx +42 -0
- package/src/components/dialog.tsx +200 -0
- package/src/components/drawer.stories.tsx +48 -0
- package/src/components/drawer.tsx +385 -0
- package/src/components/dropdown-menu.stories.tsx +41 -0
- package/src/components/dropdown-menu.tsx +252 -0
- package/src/components/dropzone.stories.tsx +19 -0
- package/src/components/dropzone.tsx +250 -0
- package/src/components/email-chips-input.stories.tsx +135 -0
- package/src/components/email-chips-input.tsx +210 -0
- package/src/components/empty-state.stories.tsx +22 -0
- package/src/components/empty-state.tsx +35 -0
- package/src/components/entity-header.stories.tsx +43 -0
- package/src/components/entity-header.test.tsx +35 -0
- package/src/components/entity-header.tsx +92 -0
- package/src/components/feature-grid.stories.tsx +25 -0
- package/src/components/feature-grid.tsx +87 -0
- package/src/components/feedback-bar.stories.tsx +25 -0
- package/src/components/feedback-bar.test.tsx +24 -0
- package/src/components/feedback-bar.tsx +62 -0
- package/src/components/footer.stories.tsx +23 -0
- package/src/components/footer.tsx +78 -0
- package/src/components/heading.stories.tsx +45 -0
- package/src/components/heading.test.tsx +22 -0
- package/src/components/heading.tsx +77 -0
- package/src/components/hero.stories.tsx +38 -0
- package/src/components/hero.tsx +498 -0
- package/src/components/input-button.stories.tsx +19 -0
- package/src/components/input-button.tsx +112 -0
- package/src/components/input-group.stories.tsx +38 -0
- package/src/components/input-group.tsx +157 -0
- package/src/components/input.stories.tsx +31 -0
- package/src/components/input.tsx +284 -0
- package/src/components/inset.stories.tsx +29 -0
- package/src/components/inset.tsx +70 -0
- package/src/components/kpi-card.stories.tsx +29 -0
- package/src/components/kpi-card.tsx +284 -0
- package/src/components/list-nav.stories.tsx +22 -0
- package/src/components/list-nav.tsx +145 -0
- package/src/components/logo-cloud.stories.tsx +23 -0
- package/src/components/logo-cloud.tsx +42 -0
- package/src/components/media-gallery.stories.tsx +25 -0
- package/src/components/media-gallery.tsx +347 -0
- package/src/components/message-actions.stories.tsx +45 -0
- package/src/components/message-actions.test.tsx +32 -0
- package/src/components/message-actions.tsx +119 -0
- package/src/components/nav-sidebar.stories.tsx +58 -0
- package/src/components/nav-sidebar.test.tsx +54 -0
- package/src/components/nav-sidebar.tsx +462 -0
- package/src/components/number-stepper.stories.tsx +19 -0
- package/src/components/number-stepper.tsx +158 -0
- package/src/components/page-header.stories.tsx +25 -0
- package/src/components/page-header.tsx +64 -0
- package/src/components/pagination.stories.tsx +110 -0
- package/src/components/pagination.test.tsx +46 -0
- package/src/components/pagination.tsx +144 -0
- package/src/components/popover.stories.tsx +26 -0
- package/src/components/popover.tsx +62 -0
- package/src/components/pricing.stories.tsx +42 -0
- package/src/components/pricing.tsx +130 -0
- package/src/components/progress.stories.tsx +22 -0
- package/src/components/progress.tsx +189 -0
- package/src/components/prompt-input.stories.tsx +62 -0
- package/src/components/prompt-input.test.tsx +106 -0
- package/src/components/prompt-input.tsx +343 -0
- package/src/components/prompt-suggestions.stories.tsx +53 -0
- package/src/components/prompt-suggestions.test.tsx +37 -0
- package/src/components/prompt-suggestions.tsx +122 -0
- package/src/components/quota-bar.stories.tsx +19 -0
- package/src/components/quota-bar.tsx +191 -0
- package/src/components/quote.stories.tsx +18 -0
- package/src/components/quote.tsx +43 -0
- package/src/components/radio-card.stories.tsx +27 -0
- package/src/components/radio-card.tsx +225 -0
- package/src/components/radio-group.stories.tsx +32 -0
- package/src/components/radio-group.tsx +68 -0
- package/src/components/reasoning.stories.tsx +50 -0
- package/src/components/reasoning.test.tsx +48 -0
- package/src/components/reasoning.tsx +143 -0
- package/src/components/search.stories.tsx +18 -0
- package/src/components/search.tsx +133 -0
- package/src/components/section-nav.stories.tsx +59 -0
- package/src/components/section-nav.test.tsx +36 -0
- package/src/components/section-nav.tsx +146 -0
- package/src/components/segmented-control.stories.tsx +22 -0
- package/src/components/segmented-control.tsx +301 -0
- package/src/components/select-button.stories.tsx +27 -0
- package/src/components/select-button.tsx +174 -0
- package/src/components/select.stories.tsx +27 -0
- package/src/components/select.tsx +198 -0
- package/src/components/separator.stories.tsx +22 -0
- package/src/components/separator.tsx +23 -0
- package/src/components/shimmer-text.stories.tsx +23 -0
- package/src/components/shimmer-text.test.tsx +35 -0
- package/src/components/shimmer-text.tsx +43 -0
- package/src/components/sidebar.stories.tsx +25 -0
- package/src/components/sidebar.tsx +234 -0
- package/src/components/skeleton.stories.tsx +22 -0
- package/src/components/skeleton.tsx +96 -0
- package/src/components/slider.stories.tsx +20 -0
- package/src/components/slider.tsx +176 -0
- package/src/components/slideshow.stories.tsx +30 -0
- package/src/components/slideshow.tsx +291 -0
- package/src/components/source.stories.tsx +32 -0
- package/src/components/source.test.tsx +40 -0
- package/src/components/source.tsx +161 -0
- package/src/components/spinner.stories.tsx +19 -0
- package/src/components/spinner.tsx +76 -0
- package/src/components/stat-strip.stories.tsx +36 -0
- package/src/components/stat-strip.test.tsx +25 -0
- package/src/components/stat-strip.tsx +85 -0
- package/src/components/stats.stories.tsx +24 -0
- package/src/components/stats.tsx +83 -0
- package/src/components/step-indicator.stories.tsx +18 -0
- package/src/components/step-indicator.test.tsx +18 -0
- package/src/components/step-indicator.tsx +286 -0
- package/src/components/switch.stories.tsx +26 -0
- package/src/components/switch.tsx +32 -0
- package/src/components/table.stories.tsx +38 -0
- package/src/components/table.tsx +220 -0
- package/src/components/tabs.stories.tsx +25 -0
- package/src/components/tabs.tsx +276 -0
- package/src/components/testimonials.stories.tsx +22 -0
- package/src/components/testimonials.tsx +109 -0
- package/src/components/text-link.stories.tsx +47 -0
- package/src/components/text-link.test.tsx +18 -0
- package/src/components/text-link.tsx +88 -0
- package/src/components/text.stories.tsx +49 -0
- package/src/components/text.tsx +105 -0
- package/src/components/thinking-bar.stories.tsx +19 -0
- package/src/components/thinking-bar.test.tsx +27 -0
- package/src/components/thinking-bar.tsx +64 -0
- package/src/components/thinking-steps.stories.tsx +44 -0
- package/src/components/thinking-steps.test.tsx +42 -0
- package/src/components/thinking-steps.tsx +92 -0
- package/src/components/toaster.stories.tsx +80 -0
- package/src/components/toaster.tsx +36 -0
- package/src/components/toggle.stories.tsx +21 -0
- package/src/components/toggle.tsx +103 -0
- package/src/components/tool-call-card.stories.tsx +97 -0
- package/src/components/tool-call-card.test.tsx +88 -0
- package/src/components/tool-call-card.tsx +231 -0
- package/src/components/tooltip.stories.tsx +25 -0
- package/src/components/tooltip.tsx +43 -0
- package/src/components/top-nav.stories.tsx +20 -0
- package/src/components/top-nav.tsx +76 -0
- package/src/docs/Colors.stories.tsx +192 -0
- package/src/docs/GettingStarted.mdx +72 -0
- package/src/docs/Mcp.mdx +52 -0
- package/src/docs/Typography.stories.tsx +114 -0
- package/src/hooks/use-in-view.ts +32 -0
- package/src/hooks/use-reduced-motion.ts +26 -0
- package/src/index.ts +343 -0
- package/src/lib/cn.test.ts +67 -0
- package/src/lib/cn.ts +81 -0
- package/src/lib/motion.ts +109 -0
- package/src/lib/prefix-utilities.test.ts +56 -0
- package/src/lib/prefix-utilities.ts +50 -0
- package/src/styles/base.css +489 -0
- package/src/styles/motion.css +69 -0
- package/src/styles/overrides.css +44 -0
- package/src/styles/typography.css +173 -0
- package/src/theme/theme.css +391 -0
- package/src/theme/tokens.ts +29 -0
- package/src/theme/types.ts +26 -0
- package/README.md +0 -3
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Check, ChevronLeft, ChevronRight, Flag, Link2, MoreHorizontal } from "lucide-react"
|
|
5
|
+
import { cn } from "../lib/cn"
|
|
6
|
+
import { Button } from "./button"
|
|
7
|
+
import {
|
|
8
|
+
Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle,
|
|
9
|
+
} from "./dialog"
|
|
10
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "./dropdown-menu"
|
|
11
|
+
import { Label, Textarea } from "./input"
|
|
12
|
+
import { MessageActions, type MessageRating } from "./message-actions"
|
|
13
|
+
import { RadioGroup, RadioGroupItem } from "./radio-group"
|
|
14
|
+
import { Text } from "./text"
|
|
15
|
+
import { TextLink } from "./text-link"
|
|
16
|
+
|
|
17
|
+
export interface AnswerFeedback {
|
|
18
|
+
rating: MessageRating
|
|
19
|
+
reasons: string[]
|
|
20
|
+
comment: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AnswerReport {
|
|
24
|
+
reason: string
|
|
25
|
+
details: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface FeedbackReasonChipsProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onToggle"> {
|
|
29
|
+
reasons: string[]
|
|
30
|
+
selected: string[]
|
|
31
|
+
onToggle: (reason: string) => void
|
|
32
|
+
/** Accessible name of the group. */
|
|
33
|
+
label: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Multi-select reason chips: toggle buttons with aria-pressed. */
|
|
37
|
+
export function FeedbackReasonChips({ reasons, selected, onToggle, label, className, ...props }: FeedbackReasonChipsProps) {
|
|
38
|
+
return (
|
|
39
|
+
<div role="group" aria-label={label} className={cn("ax:flex ax:flex-wrap ax:gap-xs", className)} {...props}>
|
|
40
|
+
{reasons.map((reason) => {
|
|
41
|
+
const on = selected.includes(reason)
|
|
42
|
+
return (
|
|
43
|
+
<Button
|
|
44
|
+
key={reason}
|
|
45
|
+
type="button"
|
|
46
|
+
variant={on ? "secondary" : "outline"}
|
|
47
|
+
size="sm"
|
|
48
|
+
aria-pressed={on}
|
|
49
|
+
onClick={() => onToggle(reason)}
|
|
50
|
+
className="ax:rounded-full"
|
|
51
|
+
>
|
|
52
|
+
{on && <Check />}
|
|
53
|
+
{reason}
|
|
54
|
+
</Button>
|
|
55
|
+
)
|
|
56
|
+
})}
|
|
57
|
+
</div>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Opens after a rating: why, in a tap, plus an optional comment. Down asks; up only offers. */
|
|
62
|
+
function FeedbackForm({ rating, reasons, onSubmit, onCancel }: {
|
|
63
|
+
rating: MessageRating
|
|
64
|
+
reasons: string[]
|
|
65
|
+
onSubmit: (feedback: AnswerFeedback) => void
|
|
66
|
+
onCancel: () => void
|
|
67
|
+
}) {
|
|
68
|
+
const id = React.useId()
|
|
69
|
+
const [selected, setSelected] = React.useState<string[]>([])
|
|
70
|
+
const [comment, setComment] = React.useState("")
|
|
71
|
+
const firstRef = React.useRef<HTMLDivElement>(null)
|
|
72
|
+
|
|
73
|
+
// Move focus into the form so keyboard users land on the reasons.
|
|
74
|
+
React.useEffect(() => {
|
|
75
|
+
firstRef.current?.querySelector("button")?.focus()
|
|
76
|
+
}, [])
|
|
77
|
+
|
|
78
|
+
const title = rating === "down" ? "What went wrong?" : "What was good?"
|
|
79
|
+
return (
|
|
80
|
+
<div className="ax:flex ax:flex-col ax:gap-sm ax:rounded-md ax:border ax:border-border ax:bg-card ax:p-md">
|
|
81
|
+
<form
|
|
82
|
+
aria-labelledby={`${id}-title`}
|
|
83
|
+
className="ax:flex ax:flex-col ax:gap-sm"
|
|
84
|
+
onSubmit={(event) => {
|
|
85
|
+
event.preventDefault()
|
|
86
|
+
onSubmit({ rating, reasons: selected, comment: comment.trim() })
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
<Text as="p" id={`${id}-title`} size="2" weight="medium">{title}</Text>
|
|
90
|
+
<div ref={firstRef}>
|
|
91
|
+
<FeedbackReasonChips
|
|
92
|
+
reasons={reasons}
|
|
93
|
+
selected={selected}
|
|
94
|
+
label={title}
|
|
95
|
+
onToggle={(reason) => setSelected((prev) => prev.includes(reason) ? prev.filter((r) => r !== reason) : [...prev, reason])}
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
<div className="ax:flex ax:flex-col ax:gap-1.5">
|
|
99
|
+
<Label htmlFor={`${id}-comment`} className="ax:text-muted-foreground">Anything else? (optional)</Label>
|
|
100
|
+
<Textarea id={`${id}-comment`} value={comment} onChange={(event) => setComment(event.target.value)} rows={2} resize="none" size="sm" />
|
|
101
|
+
</div>
|
|
102
|
+
<div className="ax:flex ax:gap-xs">
|
|
103
|
+
<Button type="submit" size="sm" className="ax:rounded-full" disabled={rating === "down" && selected.length === 0 && !comment.trim()}>
|
|
104
|
+
Send feedback
|
|
105
|
+
</Button>
|
|
106
|
+
<Button type="button" variant="ghost" size="sm" className="ax:rounded-full" onClick={onCancel}>{rating === "down" ? "Cancel" : "Skip"}</Button>
|
|
107
|
+
</div>
|
|
108
|
+
</form>
|
|
109
|
+
</div>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function ReportDialog({ open, onOpenChange, reasons, description, onReport }: {
|
|
114
|
+
open: boolean
|
|
115
|
+
onOpenChange: (open: boolean) => void
|
|
116
|
+
reasons: { value: string; label: string }[]
|
|
117
|
+
description: string
|
|
118
|
+
onReport?: (report: AnswerReport) => void
|
|
119
|
+
}) {
|
|
120
|
+
const id = React.useId()
|
|
121
|
+
const [reason, setReason] = React.useState("")
|
|
122
|
+
const [details, setDetails] = React.useState("")
|
|
123
|
+
return (
|
|
124
|
+
<Dialog open={open} onOpenChange={(next) => {
|
|
125
|
+
onOpenChange(next)
|
|
126
|
+
if (!next) { setReason(""); setDetails("") }
|
|
127
|
+
}}>
|
|
128
|
+
<DialogContent size="sm">
|
|
129
|
+
<form onSubmit={(event) => {
|
|
130
|
+
event.preventDefault()
|
|
131
|
+
onReport?.({ reason, details: details.trim() })
|
|
132
|
+
onOpenChange(false)
|
|
133
|
+
setReason("")
|
|
134
|
+
setDetails("")
|
|
135
|
+
}}>
|
|
136
|
+
<DialogHeader>
|
|
137
|
+
<DialogTitle>Report a problem</DialogTitle>
|
|
138
|
+
<DialogDescription>{description}</DialogDescription>
|
|
139
|
+
</DialogHeader>
|
|
140
|
+
<div className="ax:flex ax:flex-col ax:gap-md ax:py-md">
|
|
141
|
+
<RadioGroup value={reason} onValueChange={setReason} aria-label="Reason">
|
|
142
|
+
{reasons.map((option) => (
|
|
143
|
+
<div key={option.value} className="ax:flex ax:items-center ax:gap-sm">
|
|
144
|
+
<RadioGroupItem value={option.value} id={`${id}-${option.value}`} />
|
|
145
|
+
<Label htmlFor={`${id}-${option.value}`}>{option.label}</Label>
|
|
146
|
+
</div>
|
|
147
|
+
))}
|
|
148
|
+
</RadioGroup>
|
|
149
|
+
<div className="ax:flex ax:flex-col ax:gap-1.5">
|
|
150
|
+
<Label htmlFor={`${id}-details`}>Details (optional)</Label>
|
|
151
|
+
<Textarea id={`${id}-details`} value={details} onChange={(event) => setDetails(event.target.value)} rows={3} resize="none" />
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
<DialogFooter>
|
|
155
|
+
<DialogClose asChild><Button type="button" variant="outline">Cancel</Button></DialogClose>
|
|
156
|
+
<Button type="submit" disabled={!reason}>Send report</Button>
|
|
157
|
+
</DialogFooter>
|
|
158
|
+
</form>
|
|
159
|
+
</DialogContent>
|
|
160
|
+
</Dialog>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface AnswerActionsProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onCopy"> {
|
|
165
|
+
/** Text the Copy action copies (the visible version). */
|
|
166
|
+
text: string
|
|
167
|
+
/** Reasons offered after thumbs down; one or a comment is required to send. */
|
|
168
|
+
negativeReasons: string[]
|
|
169
|
+
/** Reasons offered after thumbs up; optional to send. */
|
|
170
|
+
positiveReasons: string[]
|
|
171
|
+
/** Sent feedback, for logging. */
|
|
172
|
+
onFeedback?: (feedback: AnswerFeedback) => void
|
|
173
|
+
/** The "Undo" link after sending was used. */
|
|
174
|
+
onUndo?: () => void
|
|
175
|
+
/** Zero-based index of the visible version. */
|
|
176
|
+
version?: number
|
|
177
|
+
versionCount?: number
|
|
178
|
+
onVersionChange?: (version: number) => void
|
|
179
|
+
onRegenerate?: () => void
|
|
180
|
+
regenerating?: boolean
|
|
181
|
+
/** Keeps Regenerate in place but disabled, e.g. when no more versions can be made. */
|
|
182
|
+
regenerateDisabled?: boolean
|
|
183
|
+
/** Adds "Copy link" to the More menu. */
|
|
184
|
+
onCopyLink?: () => void
|
|
185
|
+
/** Adds "Report a problem" to the More menu, with these reasons. */
|
|
186
|
+
reportReasons?: { value: string; label: string }[]
|
|
187
|
+
/** Who reads reports and what happens next. */
|
|
188
|
+
reportDescription?: string
|
|
189
|
+
onReport?: (report: AnswerReport) => void
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* AnswerActions - the action row under an AI answer: copy, rate, regenerate
|
|
194
|
+
* with version history, and report. Rating opens a short reasons form;
|
|
195
|
+
* sent feedback can be undone.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* <AnswerActions text={answer} negativeReasons={["Wrong price"]} positiveReasons={["Clear"]} onFeedback={log} />
|
|
199
|
+
*/
|
|
200
|
+
export const AnswerActions = React.forwardRef<HTMLDivElement, AnswerActionsProps>(
|
|
201
|
+
(
|
|
202
|
+
{
|
|
203
|
+
text, negativeReasons, positiveReasons, onFeedback, onUndo, version = 0, versionCount = 1, onVersionChange,
|
|
204
|
+
onRegenerate, regenerating, regenerateDisabled, onCopyLink, reportReasons = [],
|
|
205
|
+
reportDescription = "Tell us what's wrong with this answer.", onReport, className, ...props
|
|
206
|
+
},
|
|
207
|
+
ref,
|
|
208
|
+
) => {
|
|
209
|
+
const [rating, setRating] = React.useState<MessageRating | null>(null)
|
|
210
|
+
const [formOpen, setFormOpen] = React.useState(false)
|
|
211
|
+
const [sent, setSent] = React.useState<AnswerFeedback | null>(null)
|
|
212
|
+
const [reportOpen, setReportOpen] = React.useState(false)
|
|
213
|
+
const canReport = reportReasons.length > 0
|
|
214
|
+
|
|
215
|
+
function rate(next: MessageRating) {
|
|
216
|
+
if (rating === next) {
|
|
217
|
+
setRating(null)
|
|
218
|
+
setFormOpen(false)
|
|
219
|
+
setSent(null)
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
setRating(next)
|
|
223
|
+
setSent(null)
|
|
224
|
+
setFormOpen(true)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function undo() {
|
|
228
|
+
setRating(null)
|
|
229
|
+
setSent(null)
|
|
230
|
+
setFormOpen(false)
|
|
231
|
+
onUndo?.()
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<div ref={ref} data-slot="answer-actions" className={cn("ax:flex ax:flex-col ax:gap-sm", className)} {...props}>
|
|
236
|
+
<MessageActions
|
|
237
|
+
role="toolbar"
|
|
238
|
+
aria-label="Answer actions"
|
|
239
|
+
className="ax:gap-2xs"
|
|
240
|
+
copyText={text}
|
|
241
|
+
rating={rating}
|
|
242
|
+
onRate={rate}
|
|
243
|
+
onRegenerate={onRegenerate}
|
|
244
|
+
regenerating={regenerating}
|
|
245
|
+
regenerateDisabled={regenerateDisabled}
|
|
246
|
+
>
|
|
247
|
+
{versionCount > 1 && onVersionChange && (
|
|
248
|
+
<div className="ax:flex ax:items-center" role="group" aria-label="Response versions">
|
|
249
|
+
<Button variant="ghost" size="icon-sm" className="ax:rounded-full" aria-label="Previous version" disabled={version === 0} onClick={() => onVersionChange(version - 1)}>
|
|
250
|
+
<ChevronLeft />
|
|
251
|
+
</Button>
|
|
252
|
+
<Text size="1" color="muted" className="ax:tabular-nums" aria-live="polite">{version + 1} of {versionCount}</Text>
|
|
253
|
+
<Button variant="ghost" size="icon-sm" className="ax:rounded-full" aria-label="Next version" disabled={version === versionCount - 1} onClick={() => onVersionChange(version + 1)}>
|
|
254
|
+
<ChevronRight />
|
|
255
|
+
</Button>
|
|
256
|
+
</div>
|
|
257
|
+
)}
|
|
258
|
+
{(onCopyLink || canReport) && (
|
|
259
|
+
<DropdownMenu>
|
|
260
|
+
<DropdownMenuTrigger asChild>
|
|
261
|
+
<Button variant="ghost" size="icon-sm" className="ax:rounded-full ax:text-muted-foreground ax:hover:text-foreground" aria-label="More actions">
|
|
262
|
+
<MoreHorizontal />
|
|
263
|
+
</Button>
|
|
264
|
+
</DropdownMenuTrigger>
|
|
265
|
+
<DropdownMenuContent align="start">
|
|
266
|
+
{onCopyLink && <DropdownMenuItem onSelect={onCopyLink}><Link2 />Copy link</DropdownMenuItem>}
|
|
267
|
+
{canReport && <DropdownMenuItem variant="destructive" onSelect={() => setReportOpen(true)}><Flag />Report a problem</DropdownMenuItem>}
|
|
268
|
+
</DropdownMenuContent>
|
|
269
|
+
</DropdownMenu>
|
|
270
|
+
)}
|
|
271
|
+
</MessageActions>
|
|
272
|
+
|
|
273
|
+
{formOpen && rating && !sent && (
|
|
274
|
+
<FeedbackForm
|
|
275
|
+
key={rating}
|
|
276
|
+
rating={rating}
|
|
277
|
+
reasons={rating === "down" ? negativeReasons : positiveReasons}
|
|
278
|
+
onSubmit={(feedback) => {
|
|
279
|
+
setSent(feedback)
|
|
280
|
+
setFormOpen(false)
|
|
281
|
+
onFeedback?.(feedback)
|
|
282
|
+
}}
|
|
283
|
+
onCancel={() => {
|
|
284
|
+
setFormOpen(false)
|
|
285
|
+
if (rating === "down") setRating(null)
|
|
286
|
+
}}
|
|
287
|
+
/>
|
|
288
|
+
)}
|
|
289
|
+
|
|
290
|
+
<Text as="p" size="1" color="muted" aria-live="polite" className="ax:flex ax:flex-wrap ax:items-center ax:gap-x-xs">
|
|
291
|
+
{sent && (
|
|
292
|
+
<>
|
|
293
|
+
Thanks, this helps improve answers.
|
|
294
|
+
<TextLink asChild size="1"><button type="button" onClick={undo}>Undo</button></TextLink>
|
|
295
|
+
</>
|
|
296
|
+
)}
|
|
297
|
+
{!sent && rating === "up" && !formOpen && "Thanks for the feedback."}
|
|
298
|
+
</Text>
|
|
299
|
+
|
|
300
|
+
{canReport && (
|
|
301
|
+
<ReportDialog
|
|
302
|
+
open={reportOpen}
|
|
303
|
+
onOpenChange={setReportOpen}
|
|
304
|
+
reasons={reportReasons}
|
|
305
|
+
description={reportDescription}
|
|
306
|
+
onReport={onReport}
|
|
307
|
+
/>
|
|
308
|
+
)}
|
|
309
|
+
</div>
|
|
310
|
+
)
|
|
311
|
+
},
|
|
312
|
+
)
|
|
313
|
+
AnswerActions.displayName = "AnswerActions"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { AspectRatio } from "./aspect-ratio"
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof AspectRatio> = {
|
|
6
|
+
title: "Components/AspectRatio",
|
|
7
|
+
component: AspectRatio,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default meta
|
|
12
|
+
type Story = StoryObj<typeof AspectRatio>
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
render: () => (
|
|
16
|
+
<div className="ax:w-[320px]">
|
|
17
|
+
<AspectRatio ratio={16 / 9} className="ax:bg-muted ax:rounded-md ax:overflow-hidden">
|
|
18
|
+
<div className="ax:flex ax:h-full ax:w-full ax:items-center ax:justify-center ax:text-muted-foreground">16:9</div>
|
|
19
|
+
</AspectRatio>
|
|
20
|
+
</div>
|
|
21
|
+
),
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const Square: Story = {
|
|
25
|
+
render: () => (
|
|
26
|
+
<div className="ax:w-[200px]">
|
|
27
|
+
<AspectRatio ratio={1} className="ax:bg-muted ax:rounded-md ax:overflow-hidden">
|
|
28
|
+
<div className="ax:flex ax:h-full ax:w-full ax:items-center ax:justify-center ax:text-muted-foreground">1:1</div>
|
|
29
|
+
</AspectRatio>
|
|
30
|
+
</div>
|
|
31
|
+
),
|
|
32
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { Avatar, AvatarFallback, AvatarImage, AvatarStack } from "./avatar"
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Avatar> = {
|
|
6
|
+
title: "Components/Avatar",
|
|
7
|
+
component: Avatar,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default meta
|
|
12
|
+
type Story = StoryObj<typeof Avatar>
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
render: () => (
|
|
16
|
+
<Avatar>
|
|
17
|
+
<AvatarImage src="https://api.dicebear.com/7.x/avataaars/svg?seed=Adres" alt="User" />
|
|
18
|
+
<AvatarFallback>JD</AvatarFallback>
|
|
19
|
+
</Avatar>
|
|
20
|
+
),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const Fallback: Story = {
|
|
24
|
+
render: () => (
|
|
25
|
+
<Avatar>
|
|
26
|
+
<AvatarFallback>AB</AvatarFallback>
|
|
27
|
+
</Avatar>
|
|
28
|
+
),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const Stack: Story = {
|
|
32
|
+
render: () => (
|
|
33
|
+
<AvatarStack max={3}>
|
|
34
|
+
<Avatar><AvatarFallback>A</AvatarFallback></Avatar>
|
|
35
|
+
<Avatar><AvatarFallback>B</AvatarFallback></Avatar>
|
|
36
|
+
<Avatar><AvatarFallback>C</AvatarFallback></Avatar>
|
|
37
|
+
<Avatar><AvatarFallback>D</AvatarFallback></Avatar>
|
|
38
|
+
</AvatarStack>
|
|
39
|
+
),
|
|
40
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
6
|
+
import { cn } from "../lib/cn"
|
|
7
|
+
|
|
8
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
export type AvatarSize = "xs" | "sm" | "md" | "lg" | "xl" | "2xl"
|
|
11
|
+
|
|
12
|
+
const avatarVariants = cva(
|
|
13
|
+
"ax:relative ax:flex ax:shrink-0 ax:overflow-hidden ax:rounded-full",
|
|
14
|
+
{
|
|
15
|
+
variants: {
|
|
16
|
+
size: {
|
|
17
|
+
xs: "ax:size-6 ax:text-[10px]",
|
|
18
|
+
sm: "ax:size-8 ax:text-xs",
|
|
19
|
+
md: "ax:size-10 ax:text-sm",
|
|
20
|
+
lg: "ax:size-12 ax:text-base",
|
|
21
|
+
xl: "ax:size-16 ax:text-lg",
|
|
22
|
+
"2xl": "ax:size-20 ax:text-xl",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: { size: "md" },
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
// ─── Context (used by AvatarStack to propagate size to children) ──────────────
|
|
30
|
+
|
|
31
|
+
const AvatarStackContext = React.createContext<{ size?: AvatarSize }>({ size: undefined })
|
|
32
|
+
|
|
33
|
+
// ─── Avatar ──────────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
export interface AvatarProps
|
|
36
|
+
extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>,
|
|
37
|
+
VariantProps<typeof avatarVariants> {}
|
|
38
|
+
|
|
39
|
+
export const Avatar = React.forwardRef<
|
|
40
|
+
React.ElementRef<typeof AvatarPrimitive.Root>,
|
|
41
|
+
AvatarProps
|
|
42
|
+
>(({ className, size: sizeProp, ...props }, ref) => {
|
|
43
|
+
const { size: ctxSize } = React.useContext(AvatarStackContext)
|
|
44
|
+
const size = sizeProp ?? ctxSize
|
|
45
|
+
return (
|
|
46
|
+
<AvatarPrimitive.Root
|
|
47
|
+
ref={ref}
|
|
48
|
+
data-slot="avatar"
|
|
49
|
+
className={cn(avatarVariants({ size }), className)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
)
|
|
53
|
+
})
|
|
54
|
+
Avatar.displayName = AvatarPrimitive.Root.displayName
|
|
55
|
+
|
|
56
|
+
// ─── AvatarImage ─────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
export type AvatarImageProps = React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
|
59
|
+
|
|
60
|
+
export const AvatarImage = React.forwardRef<
|
|
61
|
+
React.ElementRef<typeof AvatarPrimitive.Image>,
|
|
62
|
+
AvatarImageProps
|
|
63
|
+
>(({ className, ...props }, ref) => (
|
|
64
|
+
<AvatarPrimitive.Image
|
|
65
|
+
ref={ref}
|
|
66
|
+
className={cn("ax:aspect-square ax:h-full ax:w-full ax:object-cover", className)}
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
))
|
|
70
|
+
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
|
71
|
+
|
|
72
|
+
// ─── AvatarFallback ──────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
export type AvatarFallbackProps = React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
|
75
|
+
|
|
76
|
+
export const AvatarFallback = React.forwardRef<
|
|
77
|
+
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
|
78
|
+
AvatarFallbackProps
|
|
79
|
+
>(({ className, ...props }, ref) => (
|
|
80
|
+
<AvatarPrimitive.Fallback
|
|
81
|
+
ref={ref}
|
|
82
|
+
className={cn(
|
|
83
|
+
"ax:flex ax:h-full ax:w-full ax:items-center ax:justify-center ax:rounded-full",
|
|
84
|
+
"ax:bg-muted ax:text-muted-foreground ax:font-medium",
|
|
85
|
+
className
|
|
86
|
+
)}
|
|
87
|
+
{...props}
|
|
88
|
+
/>
|
|
89
|
+
))
|
|
90
|
+
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
|
91
|
+
|
|
92
|
+
// ─── AvatarStack ─────────────────────────────────────────────────────────────
|
|
93
|
+
//
|
|
94
|
+
// Overlapping horizontal group of avatars (collaborators, attendees, contacts).
|
|
95
|
+
// Children must be <Avatar> elements; each one picks up:
|
|
96
|
+
// • the stack's `size` via context (so consumers don't have to set it
|
|
97
|
+
// individually on every child),
|
|
98
|
+
// • a background-coloured ring via the `*:` direct-children selector, gives
|
|
99
|
+
// the classic "white-ring between overlapping circles" effect on any
|
|
100
|
+
// surface, since the ring uses `--background`.
|
|
101
|
+
//
|
|
102
|
+
// When `max` is provided and the visible count exceeds it, the last position
|
|
103
|
+
// is replaced by a "+N" fallback chip. Without `max`, every child renders.
|
|
104
|
+
|
|
105
|
+
const STACK_OVERLAP: Record<AvatarSize, string> = {
|
|
106
|
+
xs: "ax:-space-x-1",
|
|
107
|
+
sm: "ax:-space-x-2",
|
|
108
|
+
md: "ax:-space-x-2",
|
|
109
|
+
lg: "ax:-space-x-3",
|
|
110
|
+
xl: "ax:-space-x-4",
|
|
111
|
+
"2xl": "ax:-space-x-5",
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface AvatarStackProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
115
|
+
/** Size applied to every Avatar child via context. Individual children can
|
|
116
|
+
* still override with their own `size` prop. */
|
|
117
|
+
size?: AvatarSize
|
|
118
|
+
/** Maximum number of children to render before collapsing the remainder into
|
|
119
|
+
* a `+N` fallback chip. When omitted, every child is shown. */
|
|
120
|
+
max?: number
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export const AvatarStack = React.forwardRef<HTMLDivElement, AvatarStackProps>(
|
|
124
|
+
({ className, size = "md", max, children, ...props }, ref) => {
|
|
125
|
+
const childArray = React.Children.toArray(children)
|
|
126
|
+
const total = childArray.length
|
|
127
|
+
const shouldOverflow = max != null && total > max
|
|
128
|
+
const visibleCount = shouldOverflow ? max - 1 : total
|
|
129
|
+
const overflowCount = total - visibleCount
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<AvatarStackContext.Provider value={{ size }}>
|
|
133
|
+
<div
|
|
134
|
+
ref={ref}
|
|
135
|
+
role="group"
|
|
136
|
+
className={cn(
|
|
137
|
+
"ax:flex *:ring-2 *:ring-background *:rounded-full",
|
|
138
|
+
STACK_OVERLAP[size],
|
|
139
|
+
className
|
|
140
|
+
)}
|
|
141
|
+
{...props}
|
|
142
|
+
>
|
|
143
|
+
{childArray.slice(0, visibleCount)}
|
|
144
|
+
{shouldOverflow && (
|
|
145
|
+
<Avatar aria-label={`${overflowCount} more`}>
|
|
146
|
+
<AvatarFallback>+{overflowCount}</AvatarFallback>
|
|
147
|
+
</Avatar>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
</AvatarStackContext.Provider>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
AvatarStack.displayName = "AvatarStack"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { Badge } from "./badge"
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Badge> = {
|
|
6
|
+
title: "Components/Badge",
|
|
7
|
+
component: Badge,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
args: {
|
|
10
|
+
children: "Badge",
|
|
11
|
+
variant: "default",
|
|
12
|
+
size: "base",
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default meta
|
|
17
|
+
type Story = StoryObj<typeof Badge>
|
|
18
|
+
|
|
19
|
+
export const Default: Story = {}
|
|
20
|
+
|
|
21
|
+
export const Secondary: Story = {
|
|
22
|
+
args: { variant: "secondary", children: "Secondary" },
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const Success: Story = {
|
|
26
|
+
args: { variant: "success", children: "Active" },
|
|
27
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
|
+
import { cn } from "../lib/cn"
|
|
6
|
+
|
|
7
|
+
export const badgeVariants = cva(
|
|
8
|
+
[
|
|
9
|
+
"ax:inline-flex ax:items-center ax:justify-center ax:gap-1 ax:rounded-[var(--radius-badge)] ax:whitespace-nowrap",
|
|
10
|
+
// Reserved 1px transparent border on every variant so the `outline`
|
|
11
|
+
// variant's visible border doesn't make it 2px taller than the others.
|
|
12
|
+
"ax:border ax:border-transparent",
|
|
13
|
+
"ax:transition-colors",
|
|
14
|
+
"ax:focus-visible:outline-none ax:focus-visible:ring-2 ax:focus-visible:ring-ring ax:focus-visible:ring-offset-2",
|
|
15
|
+
],
|
|
16
|
+
{
|
|
17
|
+
variants: {
|
|
18
|
+
variant: {
|
|
19
|
+
// Soft filled chips using the `weakest` emphasis alias: a pale tint in light
|
|
20
|
+
// mode that auto-inverts to a deep tint in dark, so `-foreground` reads
|
|
21
|
+
// correctly on it in both modes, no dark: override. `muted`/`outline`
|
|
22
|
+
// already use mode-aware semantic tokens.
|
|
23
|
+
default: "ax:bg-primary-weakest ax:text-primary-foreground ax:dark:bg-primary-strong ax:dark:text-primary-weaker",
|
|
24
|
+
secondary: "ax:bg-secondary-weaker ax:text-secondary-foreground ax:dark:bg-secondary-strong ax:dark:text-secondary-muted",
|
|
25
|
+
outline: "ax:border-border ax:text-stronger",
|
|
26
|
+
muted: "ax:bg-neutral-weakest ax:text-neutral-foreground",
|
|
27
|
+
success: "ax:bg-success-weakest ax:text-success-foreground ax:dark:bg-success-weak ax:dark:text-success-muted",
|
|
28
|
+
warning: "ax:bg-warning-weakest ax:text-warning-foreground ax:dark:bg-warning-weak ax:dark:text-warning-muted",
|
|
29
|
+
destructive: "ax:bg-destructive-weakest ax:text-destructive-foreground ax:dark:bg-destructive ax:dark:text-destructive-muted",
|
|
30
|
+
accent: "ax:bg-accent-weakest ax:text-accent-foreground ax:dark:bg-accent ax:dark:text-accent-muted",
|
|
31
|
+
},
|
|
32
|
+
// `min-w-*` equals each size's box height (line-height + padding + border)
|
|
33
|
+
// so single-character content renders as a full circle under the pill
|
|
34
|
+
// radius; multi-character content expands past it into a stadium.
|
|
35
|
+
size: {
|
|
36
|
+
sm: "ax:px-xs ax:py-[3px] ax:t-label-xs ax:min-w-5", /* h 20px */
|
|
37
|
+
base: "ax:px-xs ax:py-[4px] ax:t-label-xs ax:min-w-6", /* h 24px */
|
|
38
|
+
lg: "ax:px-sm ax:py-[5px] ax:t-label-sm ax:min-w-6.5", /* h 26px */
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
defaultVariants: {
|
|
42
|
+
variant: "default",
|
|
43
|
+
size: "base",
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
export interface BadgeProps
|
|
49
|
+
extends React.HTMLAttributes<HTMLSpanElement>,
|
|
50
|
+
VariantProps<typeof badgeVariants> {}
|
|
51
|
+
|
|
52
|
+
export const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
|
|
53
|
+
({ className, variant, size, ...props }, ref) => (
|
|
54
|
+
<span
|
|
55
|
+
ref={ref}
|
|
56
|
+
data-slot="badge"
|
|
57
|
+
data-variant={variant ?? "default"}
|
|
58
|
+
className={cn(badgeVariants({ variant, size }), className)}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
Badge.displayName = "Badge"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { BarGraph } from "./bar-graph"
|
|
4
|
+
|
|
5
|
+
const sampleBars = [
|
|
6
|
+
{ label: "Compliant", value: 72, color: "ax:bg-success" },
|
|
7
|
+
{ label: "Pending", value: 18, color: "ax:bg-warning" },
|
|
8
|
+
{ label: "Overdue", value: 10, color: "ax:bg-destructive" },
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
const meta: Meta<typeof BarGraph> = {
|
|
12
|
+
title: "Components/BarGraph",
|
|
13
|
+
component: BarGraph,
|
|
14
|
+
tags: ["autodocs"],
|
|
15
|
+
args: {
|
|
16
|
+
bars: sampleBars,
|
|
17
|
+
size: "md",
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default meta
|
|
22
|
+
type Story = StoryObj<typeof BarGraph>
|
|
23
|
+
|
|
24
|
+
export const Default: Story = {}
|
|
25
|
+
|
|
26
|
+
export const Vertical: Story = {
|
|
27
|
+
args: { direction: "vertical" },
|
|
28
|
+
}
|