@inui/design-system 0.0.0-stage → 1.22.0
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/README.md +360 -2
- package/dist/components/ui/accordion.d.ts +44 -0
- package/dist/components/ui/accordion.d.ts.map +1 -0
- package/dist/components/ui/accordion.js +86 -0
- package/dist/components/ui/accordion.js.map +1 -0
- package/dist/components/ui/ai-avatar.d.ts +46 -0
- package/dist/components/ui/ai-avatar.d.ts.map +1 -0
- package/dist/components/ui/ai-avatar.js +43 -0
- package/dist/components/ui/ai-avatar.js.map +1 -0
- package/dist/components/ui/ai-label.d.ts +118 -0
- package/dist/components/ui/ai-label.d.ts.map +1 -0
- package/dist/components/ui/ai-label.js +184 -0
- package/dist/components/ui/ai-label.js.map +1 -0
- package/dist/components/ui/ai-sparkle.d.ts +91 -0
- package/dist/components/ui/ai-sparkle.d.ts.map +1 -0
- package/dist/components/ui/ai-sparkle.js +200 -0
- package/dist/components/ui/ai-sparkle.js.map +1 -0
- package/dist/components/ui/alert-dialog.d.ts +79 -0
- package/dist/components/ui/alert-dialog.d.ts.map +1 -0
- package/dist/components/ui/alert-dialog.js +147 -0
- package/dist/components/ui/alert-dialog.js.map +1 -0
- package/dist/components/ui/alert.d.ts +71 -0
- package/dist/components/ui/alert.d.ts.map +1 -0
- package/dist/components/ui/alert.js +108 -0
- package/dist/components/ui/alert.js.map +1 -0
- package/dist/components/ui/aspect-ratio.d.ts +30 -0
- package/dist/components/ui/aspect-ratio.d.ts.map +1 -0
- package/dist/components/ui/aspect-ratio.js +40 -0
- package/dist/components/ui/aspect-ratio.js.map +1 -0
- package/dist/components/ui/attachment.d.ts +74 -0
- package/dist/components/ui/attachment.d.ts.map +1 -0
- package/dist/components/ui/attachment.js +142 -0
- package/dist/components/ui/attachment.js.map +1 -0
- package/dist/components/ui/avatar.d.ts +82 -0
- package/dist/components/ui/avatar.d.ts.map +1 -0
- package/dist/components/ui/avatar.js +123 -0
- package/dist/components/ui/avatar.js.map +1 -0
- package/dist/components/ui/badge.d.ts +130 -0
- package/dist/components/ui/badge.d.ts.map +1 -0
- package/dist/components/ui/badge.js +226 -0
- package/dist/components/ui/badge.js.map +1 -0
- package/dist/components/ui/breadcrumb.d.ts +43 -0
- package/dist/components/ui/breadcrumb.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb.js +98 -0
- package/dist/components/ui/breadcrumb.js.map +1 -0
- package/dist/components/ui/browser-tabs.d.ts +218 -0
- package/dist/components/ui/browser-tabs.d.ts.map +1 -0
- package/dist/components/ui/browser-tabs.js +528 -0
- package/dist/components/ui/browser-tabs.js.map +1 -0
- package/dist/components/ui/bubble.d.ts +80 -0
- package/dist/components/ui/bubble.d.ts.map +1 -0
- package/dist/components/ui/bubble.js +235 -0
- package/dist/components/ui/bubble.js.map +1 -0
- package/dist/components/ui/button-group.d.ts +60 -0
- package/dist/components/ui/button-group.d.ts.map +1 -0
- package/dist/components/ui/button-group.js +89 -0
- package/dist/components/ui/button-group.js.map +1 -0
- package/dist/components/ui/button.d.ts +157 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/button.js +144 -0
- package/dist/components/ui/button.js.map +1 -0
- package/dist/components/ui/calendar.d.ts +57 -0
- package/dist/components/ui/calendar.d.ts.map +1 -0
- package/dist/components/ui/calendar.js +163 -0
- package/dist/components/ui/calendar.js.map +1 -0
- package/dist/components/ui/card.d.ts +96 -0
- package/dist/components/ui/card.d.ts.map +1 -0
- package/dist/components/ui/card.js +131 -0
- package/dist/components/ui/card.js.map +1 -0
- package/dist/components/ui/carousel.d.ts +86 -0
- package/dist/components/ui/carousel.d.ts.map +1 -0
- package/dist/components/ui/carousel.js +207 -0
- package/dist/components/ui/carousel.js.map +1 -0
- package/dist/components/ui/checkbox.d.ts +54 -0
- package/dist/components/ui/checkbox.d.ts.map +1 -0
- package/dist/components/ui/checkbox.js +78 -0
- package/dist/components/ui/checkbox.js.map +1 -0
- package/dist/components/ui/collapsible.d.ts +45 -0
- package/dist/components/ui/collapsible.d.ts.map +1 -0
- package/dist/components/ui/collapsible.js +65 -0
- package/dist/components/ui/collapsible.js.map +1 -0
- package/dist/components/ui/combobox.d.ts +111 -0
- package/dist/components/ui/combobox.d.ts.map +1 -0
- package/dist/components/ui/combobox.js +264 -0
- package/dist/components/ui/combobox.js.map +1 -0
- package/dist/components/ui/command.d.ts +71 -0
- package/dist/components/ui/command.d.ts.map +1 -0
- package/dist/components/ui/command.js +133 -0
- package/dist/components/ui/command.js.map +1 -0
- package/dist/components/ui/context-menu.d.ts +70 -0
- package/dist/components/ui/context-menu.d.ts.map +1 -0
- package/dist/components/ui/context-menu.js +163 -0
- package/dist/components/ui/context-menu.js.map +1 -0
- package/dist/components/ui/count-badge.d.ts +40 -0
- package/dist/components/ui/count-badge.d.ts.map +1 -0
- package/dist/components/ui/count-badge.js +43 -0
- package/dist/components/ui/count-badge.js.map +1 -0
- package/dist/components/ui/data-table.d.ts +1026 -0
- package/dist/components/ui/data-table.d.ts.map +1 -0
- package/dist/components/ui/data-table.js +3423 -0
- package/dist/components/ui/data-table.js.map +1 -0
- package/dist/components/ui/dialog.d.ts +73 -0
- package/dist/components/ui/dialog.d.ts.map +1 -0
- package/dist/components/ui/dialog.js +145 -0
- package/dist/components/ui/dialog.js.map +1 -0
- package/dist/components/ui/direction.d.ts +35 -0
- package/dist/components/ui/direction.d.ts.map +1 -0
- package/dist/components/ui/direction.js +35 -0
- package/dist/components/ui/direction.js.map +1 -0
- package/dist/components/ui/drawer.d.ts +53 -0
- package/dist/components/ui/drawer.d.ts.map +1 -0
- package/dist/components/ui/drawer.js +115 -0
- package/dist/components/ui/drawer.js.map +1 -0
- package/dist/components/ui/dropdown-menu.d.ts +75 -0
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu.js +173 -0
- package/dist/components/ui/dropdown-menu.js.map +1 -0
- package/dist/components/ui/dropzone.d.ts +141 -0
- package/dist/components/ui/dropzone.d.ts.map +1 -0
- package/dist/components/ui/dropzone.js +257 -0
- package/dist/components/ui/dropzone.js.map +1 -0
- package/dist/components/ui/empty.d.ts +94 -0
- package/dist/components/ui/empty.d.ts.map +1 -0
- package/dist/components/ui/empty.js +149 -0
- package/dist/components/ui/empty.js.map +1 -0
- package/dist/components/ui/field.d.ts +122 -0
- package/dist/components/ui/field.d.ts.map +1 -0
- package/dist/components/ui/field.js +254 -0
- package/dist/components/ui/field.js.map +1 -0
- package/dist/components/ui/hover-card.d.ts +51 -0
- package/dist/components/ui/hover-card.d.ts.map +1 -0
- package/dist/components/ui/hover-card.js +101 -0
- package/dist/components/ui/hover-card.js.map +1 -0
- package/dist/components/ui/input-group.d.ts +62 -0
- package/dist/components/ui/input-group.d.ts.map +1 -0
- package/dist/components/ui/input-group.js +115 -0
- package/dist/components/ui/input-group.js.map +1 -0
- package/dist/components/ui/input-otp.d.ts +52 -0
- package/dist/components/ui/input-otp.d.ts.map +1 -0
- package/dist/components/ui/input-otp.js +89 -0
- package/dist/components/ui/input-otp.js.map +1 -0
- package/dist/components/ui/input.d.ts +47 -0
- package/dist/components/ui/input.d.ts.map +1 -0
- package/dist/components/ui/input.js +47 -0
- package/dist/components/ui/input.js.map +1 -0
- package/dist/components/ui/item.d.ts +74 -0
- package/dist/components/ui/item.d.ts.map +1 -0
- package/dist/components/ui/item.js +176 -0
- package/dist/components/ui/item.js.map +1 -0
- package/dist/components/ui/kbd.d.ts +41 -0
- package/dist/components/ui/kbd.d.ts.map +1 -0
- package/dist/components/ui/kbd.js +58 -0
- package/dist/components/ui/kbd.js.map +1 -0
- package/dist/components/ui/key-value-pair.d.ts +56 -0
- package/dist/components/ui/key-value-pair.d.ts.map +1 -0
- package/dist/components/ui/key-value-pair.js +110 -0
- package/dist/components/ui/key-value-pair.js.map +1 -0
- package/dist/components/ui/label.d.ts +83 -0
- package/dist/components/ui/label.d.ts.map +1 -0
- package/dist/components/ui/label.js +85 -0
- package/dist/components/ui/label.js.map +1 -0
- package/dist/components/ui/marker.d.ts +54 -0
- package/dist/components/ui/marker.d.ts.map +1 -0
- package/dist/components/ui/marker.js +79 -0
- package/dist/components/ui/marker.js.map +1 -0
- package/dist/components/ui/menubar.d.ts +72 -0
- package/dist/components/ui/menubar.d.ts.map +1 -0
- package/dist/components/ui/menubar.js +174 -0
- package/dist/components/ui/menubar.js.map +1 -0
- package/dist/components/ui/message-scroller.d.ts +53 -0
- package/dist/components/ui/message-scroller.d.ts.map +1 -0
- package/dist/components/ui/message-scroller.js +95 -0
- package/dist/components/ui/message-scroller.js.map +1 -0
- package/dist/components/ui/message.d.ts +63 -0
- package/dist/components/ui/message.d.ts.map +1 -0
- package/dist/components/ui/message.js +92 -0
- package/dist/components/ui/message.js.map +1 -0
- package/dist/components/ui/meta-list.d.ts +55 -0
- package/dist/components/ui/meta-list.d.ts.map +1 -0
- package/dist/components/ui/meta-list.js +75 -0
- package/dist/components/ui/meta-list.js.map +1 -0
- package/dist/components/ui/native-select.d.ts +51 -0
- package/dist/components/ui/native-select.d.ts.map +1 -0
- package/dist/components/ui/native-select.js +75 -0
- package/dist/components/ui/native-select.js.map +1 -0
- package/dist/components/ui/navigation-menu.d.ts +56 -0
- package/dist/components/ui/navigation-menu.d.ts.map +1 -0
- package/dist/components/ui/navigation-menu.js +116 -0
- package/dist/components/ui/navigation-menu.js.map +1 -0
- package/dist/components/ui/page-header.d.ts +223 -0
- package/dist/components/ui/page-header.d.ts.map +1 -0
- package/dist/components/ui/page-header.js +233 -0
- package/dist/components/ui/page-header.js.map +1 -0
- package/dist/components/ui/pagination.d.ts +68 -0
- package/dist/components/ui/pagination.d.ts.map +1 -0
- package/dist/components/ui/pagination.js +145 -0
- package/dist/components/ui/pagination.js.map +1 -0
- package/dist/components/ui/popover.d.ts +61 -0
- package/dist/components/ui/popover.d.ts.map +1 -0
- package/dist/components/ui/popover.js +144 -0
- package/dist/components/ui/popover.js.map +1 -0
- package/dist/components/ui/progress.d.ts +35 -0
- package/dist/components/ui/progress.d.ts.map +1 -0
- package/dist/components/ui/progress.js +46 -0
- package/dist/components/ui/progress.js.map +1 -0
- package/dist/components/ui/questionnaire.d.ts +92 -0
- package/dist/components/ui/questionnaire.d.ts.map +1 -0
- package/dist/components/ui/questionnaire.js +277 -0
- package/dist/components/ui/questionnaire.js.map +1 -0
- package/dist/components/ui/radio-group.d.ts +50 -0
- package/dist/components/ui/radio-group.d.ts.map +1 -0
- package/dist/components/ui/radio-group.js +65 -0
- package/dist/components/ui/radio-group.js.map +1 -0
- package/dist/components/ui/resizable.d.ts +44 -0
- package/dist/components/ui/resizable.d.ts.map +1 -0
- package/dist/components/ui/resizable.js +73 -0
- package/dist/components/ui/resizable.js.map +1 -0
- package/dist/components/ui/scroll-area.d.ts +55 -0
- package/dist/components/ui/scroll-area.d.ts.map +1 -0
- package/dist/components/ui/scroll-area.js +72 -0
- package/dist/components/ui/scroll-area.js.map +1 -0
- package/dist/components/ui/select.d.ts +274 -0
- package/dist/components/ui/select.d.ts.map +1 -0
- package/dist/components/ui/select.js +609 -0
- package/dist/components/ui/select.js.map +1 -0
- package/dist/components/ui/separator.d.ts +31 -0
- package/dist/components/ui/separator.d.ts.map +1 -0
- package/dist/components/ui/separator.js +38 -0
- package/dist/components/ui/separator.js.map +1 -0
- package/dist/components/ui/sheet.d.ts +59 -0
- package/dist/components/ui/sheet.d.ts.map +1 -0
- package/dist/components/ui/sheet.js +128 -0
- package/dist/components/ui/sheet.js.map +1 -0
- package/dist/components/ui/sidebar.d.ts +166 -0
- package/dist/components/ui/sidebar.d.ts.map +1 -0
- package/dist/components/ui/sidebar.js +512 -0
- package/dist/components/ui/sidebar.js.map +1 -0
- package/dist/components/ui/skeleton.d.ts +35 -0
- package/dist/components/ui/skeleton.d.ts.map +1 -0
- package/dist/components/ui/skeleton.js +46 -0
- package/dist/components/ui/skeleton.js.map +1 -0
- package/dist/components/ui/slider.d.ts +39 -0
- package/dist/components/ui/slider.d.ts.map +1 -0
- package/dist/components/ui/slider.js +75 -0
- package/dist/components/ui/slider.js.map +1 -0
- package/dist/components/ui/sonner.d.ts +56 -0
- package/dist/components/ui/sonner.d.ts.map +1 -0
- package/dist/components/ui/sonner.js +96 -0
- package/dist/components/ui/sonner.js.map +1 -0
- package/dist/components/ui/spinner.d.ts +37 -0
- package/dist/components/ui/spinner.d.ts.map +1 -0
- package/dist/components/ui/spinner.js +51 -0
- package/dist/components/ui/spinner.js.map +1 -0
- package/dist/components/ui/stepper.d.ts +84 -0
- package/dist/components/ui/stepper.d.ts.map +1 -0
- package/dist/components/ui/stepper.js +145 -0
- package/dist/components/ui/stepper.js.map +1 -0
- package/dist/components/ui/switch.d.ts +36 -0
- package/dist/components/ui/switch.d.ts.map +1 -0
- package/dist/components/ui/switch.js +44 -0
- package/dist/components/ui/switch.js.map +1 -0
- package/dist/components/ui/table.d.ts +80 -0
- package/dist/components/ui/table.d.ts.map +1 -0
- package/dist/components/ui/table.js +130 -0
- package/dist/components/ui/table.js.map +1 -0
- package/dist/components/ui/tabs.d.ts +87 -0
- package/dist/components/ui/tabs.d.ts.map +1 -0
- package/dist/components/ui/tabs.js +128 -0
- package/dist/components/ui/tabs.js.map +1 -0
- package/dist/components/ui/textarea.d.ts +35 -0
- package/dist/components/ui/textarea.d.ts.map +1 -0
- package/dist/components/ui/textarea.js +38 -0
- package/dist/components/ui/textarea.js.map +1 -0
- package/dist/components/ui/time-picker.d.ts +58 -0
- package/dist/components/ui/time-picker.d.ts.map +1 -0
- package/dist/components/ui/time-picker.js +188 -0
- package/dist/components/ui/time-picker.js.map +1 -0
- package/dist/components/ui/toggle-group.d.ts +52 -0
- package/dist/components/ui/toggle-group.d.ts.map +1 -0
- package/dist/components/ui/toggle-group.js +94 -0
- package/dist/components/ui/toggle-group.js.map +1 -0
- package/dist/components/ui/toggle.d.ts +50 -0
- package/dist/components/ui/toggle.d.ts.map +1 -0
- package/dist/components/ui/toggle.js +70 -0
- package/dist/components/ui/toggle.js.map +1 -0
- package/dist/components/ui/tooltip.d.ts +51 -0
- package/dist/components/ui/tooltip.d.ts.map +1 -0
- package/dist/components/ui/tooltip.js +78 -0
- package/dist/components/ui/tooltip.js.map +1 -0
- package/dist/hooks/use-dark-mode.d.ts +2 -0
- package/dist/hooks/use-dark-mode.d.ts.map +1 -0
- package/dist/hooks/use-dark-mode.js +36 -0
- package/dist/hooks/use-dark-mode.js.map +1 -0
- package/dist/hooks/use-mobile.d.ts +2 -0
- package/dist/hooks/use-mobile.d.ts.map +1 -0
- package/dist/hooks/use-mobile.js +20 -0
- package/dist/hooks/use-mobile.js.map +1 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +77 -0
- package/dist/lib/scope.d.ts +64 -0
- package/dist/lib/scope.d.ts.map +1 -0
- package/dist/lib/scope.js +74 -0
- package/dist/lib/scope.js.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +11 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/styles.css +2 -0
- package/dist/theme-tokens.css +537 -0
- package/dist/theme.css +417 -0
- package/package.json +103 -3
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AILabel — the accessible entry point into an AI explanation.
|
|
3
|
+
*
|
|
4
|
+
* A thin composite over Popover: `AILabelTrigger` is a real, labelled
|
|
5
|
+
* `Button` — a transparent `variant="ghost"` button with a visible
|
|
6
|
+
* `[sparkle] AI` label, not `AISparkle` alone (which is `aria-hidden` and
|
|
7
|
+
* purely a brand mark — see its own file) and not an icon-only button
|
|
8
|
+
* either. Deliberately `ghost`, not `variant="ai"`'s filled AI Chip look
|
|
9
|
+
* (Button's own AI Chip recipe) — the two need to read apart: the Chip is
|
|
10
|
+
* an action, this is a marker/explainability trigger, and a filled pill
|
|
11
|
+
* here would make them visually indistinguishable. The marker has visible
|
|
12
|
+
* "AI" text, not just an accessible name, so the tag itself is the
|
|
13
|
+
* explainability entry point a sighted user can find without hovering.
|
|
14
|
+
* Opens `AILabelContent`, a Popover panel holding a short,
|
|
15
|
+
* first-layer explanation.
|
|
16
|
+
*
|
|
17
|
+
* Marks genuine AI involvement only, never decoration — see manifest.json's
|
|
18
|
+
* `ai_presence` convention for the full rule and every current AI-treatment
|
|
19
|
+
* recipe. This is specifically the "why am I seeing this" affordance; other
|
|
20
|
+
* recipes (Button variant="ai", the AI Card outline, DataTable's row tint)
|
|
21
|
+
* mark AI presence visually but don't need to explain anything on their own.
|
|
22
|
+
*/
|
|
23
|
+
import * as React from "react";
|
|
24
|
+
import { Button } from "../../components/ui/button";
|
|
25
|
+
import { Popover, PopoverContent } from "../../components/ui/popover";
|
|
26
|
+
declare function AILabel({ edited, onRevert, revertLabel, ...props }: React.ComponentProps<typeof Popover> & {
|
|
27
|
+
/** True once the user has changed the AI-filled value away from what AI produced. Morphs AILabelTrigger into a ghost undo button (calling `onRevert`) and hides AILabelContent — one slot, two states. */
|
|
28
|
+
edited?: boolean;
|
|
29
|
+
/** Restores the original AI-filled value. Required for the undo button to render; without it `edited` just hides the marker. */
|
|
30
|
+
onRevert?: () => void;
|
|
31
|
+
/** Accessible name of the undo button; make it specific when there's more than one AI-filled field on the page. */
|
|
32
|
+
revertLabel?: string;
|
|
33
|
+
}): React.JSX.Element;
|
|
34
|
+
type AILabelTriggerBaseProps = Omit<React.ComponentProps<typeof Button>, "variant" | "size" | "children" | "asChild" | "aria-label">;
|
|
35
|
+
type AILabelTriggerSize = "default" | "sm";
|
|
36
|
+
type AILabelTriggerProps = AILabelTriggerBaseProps & {
|
|
37
|
+
size?: AILabelTriggerSize;
|
|
38
|
+
} & ({
|
|
39
|
+
iconOnly: true;
|
|
40
|
+
"aria-label": string;
|
|
41
|
+
} | {
|
|
42
|
+
iconOnly?: false;
|
|
43
|
+
"aria-label"?: string;
|
|
44
|
+
});
|
|
45
|
+
declare function AILabelTrigger({ className, size, iconOnly, ...props }: AILabelTriggerProps): React.JSX.Element | null;
|
|
46
|
+
declare function AILabelContent({ className, ...props }: React.ComponentProps<typeof PopoverContent>): React.JSX.Element | null;
|
|
47
|
+
export { AILabel, AILabelTrigger, AILabelContent };
|
|
48
|
+
/**
|
|
49
|
+
* WHEN TO USE
|
|
50
|
+
* - Any specific AI-generated content or suggestion where a user might reasonably ask "why am I seeing this" — a DataTable AI-tinted row, an AI Card summary, an inline AI suggestion in a form
|
|
51
|
+
* - As the accessible explainability entry point required alongside any other AI-presence styling (Button variant="ai", the AI Card outline, DataTable's row tint) — those mark presence, this explains it
|
|
52
|
+
*
|
|
53
|
+
* NOT WHEN
|
|
54
|
+
* - Marking AI presence with no explanation to give — use plain AISparkle or Button variant="ai" instead; don't add AILabel just to decorate something as AI-flavored
|
|
55
|
+
* - A full walkthrough or multi-step explanation — use a Dialog instead; AILabel's popover is for a short, first-layer summary, not a document
|
|
56
|
+
*
|
|
57
|
+
* KEY RULES
|
|
58
|
+
* - size: "default" | "sm" (default) — the marker's height step, matched to the surrounding size/density; both
|
|
59
|
+
* render the visible `[sparkle] AI` text at Button's matching text size. These are the only two to pick from by default.
|
|
60
|
+
* - iconOnly: drops the "AI" text and leaves just the sparkle, square, at that same size step. OPT-IN ONLY — use it
|
|
61
|
+
* solely when a designer explicitly asks for an icon-only marker, never as an automatic "tight space" fallback
|
|
62
|
+
* (DataTable keeps the visible text at every density). Icon-only is a shape, not a size — same two-axis split as
|
|
63
|
+
* Button's own `size` + `iconOnly`.
|
|
64
|
+
* - Without `iconOnly` no `aria-label` is needed — the visible "AI" text is the accessible name; pass one yourself only
|
|
65
|
+
* for a more specific name. With `iconOnly` there's no visible text, so `aria-label` is required — enforced at the
|
|
66
|
+
* type level, same as Button.
|
|
67
|
+
* - AISparkle inside the trigger stays `aria-hidden` (its own default) — the chip's visible "AI" text is what's
|
|
68
|
+
* announced, or with `iconOnly` the required `aria-label`.
|
|
69
|
+
* - An AI-filled value the user can override is the `edited`/`onRevert` pair on AILabel itself,
|
|
70
|
+
* not a hand-rolled swap: pass `edited={value !== aiValue}` and `onRevert={() => setValue(aiValue)}`,
|
|
71
|
+
* and while edited the trigger morphs into a ghost `Undo2Icon` button at the matching icon size
|
|
72
|
+
* (same footprint, no layout shift) that restores the AI value, and AILabelContent stops
|
|
73
|
+
* rendering — nothing left to explain, only something to restore. `revertLabel` names the undo
|
|
74
|
+
* button (default "Undo edit, restore AI value"); make it specific with several AI-filled fields
|
|
75
|
+
* on one page. Input and Textarea (in the label row, beside FieldLabel), Select (beside the trigger)
|
|
76
|
+
* and Checkbox/RadioGroup (beside the option or legend) all use exactly this — see the AI/* stories.
|
|
77
|
+
* - For a second, deeper layer of explanation, nest Collapsible/CollapsibleTrigger/CollapsibleContent inside AILabelContent (e.g. a "Show more" link) rather than opening a second popover — keep the deeper detail inside the same explanation surface, revealed only on request
|
|
78
|
+
* - PopoverTitle/PopoverDescription inside AILabelContent wire up aria-labelledby/aria-describedby automatically, same as any other Popover
|
|
79
|
+
*
|
|
80
|
+
* USAGE
|
|
81
|
+
* <AILabel>
|
|
82
|
+
* <AILabelTrigger />
|
|
83
|
+
* <AILabelContent>
|
|
84
|
+
* <PopoverHeader>
|
|
85
|
+
* <PopoverTitle>Why you're seeing this</PopoverTitle>
|
|
86
|
+
* </PopoverHeader>
|
|
87
|
+
* <PopoverDescription>
|
|
88
|
+
* Flagged because this patient's last visit was over 90 days ago.
|
|
89
|
+
* </PopoverDescription>
|
|
90
|
+
* </AILabelContent>
|
|
91
|
+
* </AILabel>
|
|
92
|
+
*
|
|
93
|
+
* // Icon-only — only when a designer explicitly asks for it; aria-label required
|
|
94
|
+
* <AILabelTrigger iconOnly aria-label="AI-suggested row" />
|
|
95
|
+
*
|
|
96
|
+
* // AI-filled value the user can override: marker while untouched, undo once edited
|
|
97
|
+
* <AILabel edited={value !== AI_VALUE} onRevert={() => setValue(AI_VALUE)}>
|
|
98
|
+
* <AILabelTrigger aria-label="AI-filled: why this value?" />
|
|
99
|
+
* <AILabelContent>...</AILabelContent>
|
|
100
|
+
* </AILabel>
|
|
101
|
+
*
|
|
102
|
+
* // With a second, deeper explanation layer
|
|
103
|
+
* <AILabelContent>
|
|
104
|
+
* <PopoverHeader>
|
|
105
|
+
* <PopoverTitle>Why you're seeing this</PopoverTitle>
|
|
106
|
+
* </PopoverHeader>
|
|
107
|
+
* <PopoverDescription>Flagged for overdue follow-up.</PopoverDescription>
|
|
108
|
+
* <Collapsible>
|
|
109
|
+
* <CollapsibleTrigger asChild>
|
|
110
|
+
* <Button variant="link" size="sm">Show more</Button>
|
|
111
|
+
* </CollapsibleTrigger>
|
|
112
|
+
* <CollapsibleContent className="text-sm text-muted-foreground">
|
|
113
|
+
* Based on the care-gap model, last-visit date, and current risk score.
|
|
114
|
+
* </CollapsibleContent>
|
|
115
|
+
* </Collapsible>
|
|
116
|
+
* </AILabelContent>
|
|
117
|
+
*/
|
|
118
|
+
//# sourceMappingURL=ai-label.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-label.d.ts","sourceRoot":"","sources":["../../../src/components/ui/ai-label.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAC/C,OAAO,EACL,OAAO,EACP,cAAc,EAEf,MAAM,yBAAyB,CAAA;AAchC,iBAAS,OAAO,CAAC,EACf,MAAc,EACd,QAAQ,EACR,WAA2C,EAC3C,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,OAAO,CAAC,GAAG;IACxC,0MAA0M;IAC1M,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,gIAAgI;IAChI,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,mHAAmH;IACnH,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,qBAUA;AAED,KAAK,uBAAuB,GAAG,IAAI,CACjC,KAAK,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,EACnC,SAAS,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,GAAG,YAAY,CAC3D,CAAA;AAsBD,KAAK,kBAAkB,GAAG,SAAS,GAAG,IAAI,CAAA;AAK1C,KAAK,mBAAmB,GAAG,uBAAuB,GAAG;IACnD,IAAI,CAAC,EAAE,kBAAkB,CAAA;CAC1B,GAAG,CACE;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,QAAQ,CAAC,EAAE,KAAK,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAC9C,CAAA;AAKH,iBAAS,cAAc,CAAC,EACtB,SAAS,EACT,IAAW,EACX,QAAgB,EAChB,GAAG,KAAK,EACT,EAAE,mBAAmB,4BAiFrB;AAED,iBAAS,cAAc,CAAC,EACtB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,4BAW7C;AAED,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,CAAA;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { cn } from "../../lib/utils.js";
|
|
2
|
+
import { AISparkle } from "./ai-sparkle.js";
|
|
3
|
+
import { Button } from "./button.js";
|
|
4
|
+
import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { Undo2Icon } from "lucide-react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
//#region src/components/ui/ai-label.tsx
|
|
9
|
+
/**
|
|
10
|
+
* AILabel — the accessible entry point into an AI explanation.
|
|
11
|
+
*
|
|
12
|
+
* A thin composite over Popover: `AILabelTrigger` is a real, labelled
|
|
13
|
+
* `Button` — a transparent `variant="ghost"` button with a visible
|
|
14
|
+
* `[sparkle] AI` label, not `AISparkle` alone (which is `aria-hidden` and
|
|
15
|
+
* purely a brand mark — see its own file) and not an icon-only button
|
|
16
|
+
* either. Deliberately `ghost`, not `variant="ai"`'s filled AI Chip look
|
|
17
|
+
* (Button's own AI Chip recipe) — the two need to read apart: the Chip is
|
|
18
|
+
* an action, this is a marker/explainability trigger, and a filled pill
|
|
19
|
+
* here would make them visually indistinguishable. The marker has visible
|
|
20
|
+
* "AI" text, not just an accessible name, so the tag itself is the
|
|
21
|
+
* explainability entry point a sighted user can find without hovering.
|
|
22
|
+
* Opens `AILabelContent`, a Popover panel holding a short,
|
|
23
|
+
* first-layer explanation.
|
|
24
|
+
*
|
|
25
|
+
* Marks genuine AI involvement only, never decoration — see manifest.json's
|
|
26
|
+
* `ai_presence` convention for the full rule and every current AI-treatment
|
|
27
|
+
* recipe. This is specifically the "why am I seeing this" affordance; other
|
|
28
|
+
* recipes (Button variant="ai", the AI Card outline, DataTable's row tint)
|
|
29
|
+
* mark AI presence visually but don't need to explain anything on their own.
|
|
30
|
+
*/
|
|
31
|
+
var AILabelContext = React.createContext({
|
|
32
|
+
edited: false,
|
|
33
|
+
revertLabel: "Undo edit, restore AI value"
|
|
34
|
+
});
|
|
35
|
+
function AILabel({ edited = false, onRevert, revertLabel = "Undo edit, restore AI value", ...props }) {
|
|
36
|
+
const context = React.useMemo(() => ({
|
|
37
|
+
edited,
|
|
38
|
+
onRevert,
|
|
39
|
+
revertLabel
|
|
40
|
+
}), [
|
|
41
|
+
edited,
|
|
42
|
+
onRevert,
|
|
43
|
+
revertLabel
|
|
44
|
+
]);
|
|
45
|
+
return /* @__PURE__ */ jsx(AILabelContext.Provider, {
|
|
46
|
+
value: context,
|
|
47
|
+
children: /* @__PURE__ */ jsx(Popover, {
|
|
48
|
+
"data-slot": "ai-label",
|
|
49
|
+
...props
|
|
50
|
+
})
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
var BUTTON_SIZE_BY_AILABEL_TRIGGER_SIZE = {
|
|
54
|
+
default: "default",
|
|
55
|
+
sm: "xs"
|
|
56
|
+
};
|
|
57
|
+
function AILabelTrigger({ className, size = "sm", iconOnly = false, ...props }) {
|
|
58
|
+
const { edited, onRevert, revertLabel } = React.useContext(AILabelContext);
|
|
59
|
+
const buttonSize = BUTTON_SIZE_BY_AILABEL_TRIGGER_SIZE[size];
|
|
60
|
+
if (edited) {
|
|
61
|
+
if (!onRevert) return null;
|
|
62
|
+
const rest = Object.fromEntries(Object.entries(props).filter(([key]) => key !== "aria-label"));
|
|
63
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
64
|
+
type: "button",
|
|
65
|
+
variant: "ghost",
|
|
66
|
+
size: buttonSize,
|
|
67
|
+
iconOnly: true,
|
|
68
|
+
"aria-label": revertLabel,
|
|
69
|
+
"data-slot": "ai-label-revert",
|
|
70
|
+
className,
|
|
71
|
+
onClick: onRevert,
|
|
72
|
+
...rest,
|
|
73
|
+
children: /* @__PURE__ */ jsx(Undo2Icon, {})
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (iconOnly) {
|
|
77
|
+
const { "aria-label": ariaLabel, ...rest } = props;
|
|
78
|
+
return /* @__PURE__ */ jsx(PopoverTrigger, {
|
|
79
|
+
asChild: true,
|
|
80
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
81
|
+
variant: "ghost",
|
|
82
|
+
size: buttonSize,
|
|
83
|
+
iconOnly: true,
|
|
84
|
+
"aria-label": ariaLabel,
|
|
85
|
+
className,
|
|
86
|
+
...rest,
|
|
87
|
+
children: /* @__PURE__ */ jsx(AISparkle, {})
|
|
88
|
+
})
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return /* @__PURE__ */ jsx(PopoverTrigger, {
|
|
92
|
+
asChild: true,
|
|
93
|
+
children: /* @__PURE__ */ jsxs(Button, {
|
|
94
|
+
variant: "ghost",
|
|
95
|
+
size: buttonSize,
|
|
96
|
+
className,
|
|
97
|
+
...props,
|
|
98
|
+
children: [/* @__PURE__ */ jsx(AISparkle, { "data-icon": "inline-start" }), "AI"]
|
|
99
|
+
})
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function AILabelContent({ className, ...props }) {
|
|
103
|
+
const { edited } = React.useContext(AILabelContext);
|
|
104
|
+
if (edited) return null;
|
|
105
|
+
return /* @__PURE__ */ jsx(PopoverContent, {
|
|
106
|
+
"data-slot": "ai-label-content",
|
|
107
|
+
className: cn("w-80", className),
|
|
108
|
+
...props
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* WHEN TO USE
|
|
113
|
+
* - Any specific AI-generated content or suggestion where a user might reasonably ask "why am I seeing this" — a DataTable AI-tinted row, an AI Card summary, an inline AI suggestion in a form
|
|
114
|
+
* - As the accessible explainability entry point required alongside any other AI-presence styling (Button variant="ai", the AI Card outline, DataTable's row tint) — those mark presence, this explains it
|
|
115
|
+
*
|
|
116
|
+
* NOT WHEN
|
|
117
|
+
* - Marking AI presence with no explanation to give — use plain AISparkle or Button variant="ai" instead; don't add AILabel just to decorate something as AI-flavored
|
|
118
|
+
* - A full walkthrough or multi-step explanation — use a Dialog instead; AILabel's popover is for a short, first-layer summary, not a document
|
|
119
|
+
*
|
|
120
|
+
* KEY RULES
|
|
121
|
+
* - size: "default" | "sm" (default) — the marker's height step, matched to the surrounding size/density; both
|
|
122
|
+
* render the visible `[sparkle] AI` text at Button's matching text size. These are the only two to pick from by default.
|
|
123
|
+
* - iconOnly: drops the "AI" text and leaves just the sparkle, square, at that same size step. OPT-IN ONLY — use it
|
|
124
|
+
* solely when a designer explicitly asks for an icon-only marker, never as an automatic "tight space" fallback
|
|
125
|
+
* (DataTable keeps the visible text at every density). Icon-only is a shape, not a size — same two-axis split as
|
|
126
|
+
* Button's own `size` + `iconOnly`.
|
|
127
|
+
* - Without `iconOnly` no `aria-label` is needed — the visible "AI" text is the accessible name; pass one yourself only
|
|
128
|
+
* for a more specific name. With `iconOnly` there's no visible text, so `aria-label` is required — enforced at the
|
|
129
|
+
* type level, same as Button.
|
|
130
|
+
* - AISparkle inside the trigger stays `aria-hidden` (its own default) — the chip's visible "AI" text is what's
|
|
131
|
+
* announced, or with `iconOnly` the required `aria-label`.
|
|
132
|
+
* - An AI-filled value the user can override is the `edited`/`onRevert` pair on AILabel itself,
|
|
133
|
+
* not a hand-rolled swap: pass `edited={value !== aiValue}` and `onRevert={() => setValue(aiValue)}`,
|
|
134
|
+
* and while edited the trigger morphs into a ghost `Undo2Icon` button at the matching icon size
|
|
135
|
+
* (same footprint, no layout shift) that restores the AI value, and AILabelContent stops
|
|
136
|
+
* rendering — nothing left to explain, only something to restore. `revertLabel` names the undo
|
|
137
|
+
* button (default "Undo edit, restore AI value"); make it specific with several AI-filled fields
|
|
138
|
+
* on one page. Input and Textarea (in the label row, beside FieldLabel), Select (beside the trigger)
|
|
139
|
+
* and Checkbox/RadioGroup (beside the option or legend) all use exactly this — see the AI/* stories.
|
|
140
|
+
* - For a second, deeper layer of explanation, nest Collapsible/CollapsibleTrigger/CollapsibleContent inside AILabelContent (e.g. a "Show more" link) rather than opening a second popover — keep the deeper detail inside the same explanation surface, revealed only on request
|
|
141
|
+
* - PopoverTitle/PopoverDescription inside AILabelContent wire up aria-labelledby/aria-describedby automatically, same as any other Popover
|
|
142
|
+
*
|
|
143
|
+
* USAGE
|
|
144
|
+
* <AILabel>
|
|
145
|
+
* <AILabelTrigger />
|
|
146
|
+
* <AILabelContent>
|
|
147
|
+
* <PopoverHeader>
|
|
148
|
+
* <PopoverTitle>Why you're seeing this</PopoverTitle>
|
|
149
|
+
* </PopoverHeader>
|
|
150
|
+
* <PopoverDescription>
|
|
151
|
+
* Flagged because this patient's last visit was over 90 days ago.
|
|
152
|
+
* </PopoverDescription>
|
|
153
|
+
* </AILabelContent>
|
|
154
|
+
* </AILabel>
|
|
155
|
+
*
|
|
156
|
+
* // Icon-only — only when a designer explicitly asks for it; aria-label required
|
|
157
|
+
* <AILabelTrigger iconOnly aria-label="AI-suggested row" />
|
|
158
|
+
*
|
|
159
|
+
* // AI-filled value the user can override: marker while untouched, undo once edited
|
|
160
|
+
* <AILabel edited={value !== AI_VALUE} onRevert={() => setValue(AI_VALUE)}>
|
|
161
|
+
* <AILabelTrigger aria-label="AI-filled: why this value?" />
|
|
162
|
+
* <AILabelContent>...</AILabelContent>
|
|
163
|
+
* </AILabel>
|
|
164
|
+
*
|
|
165
|
+
* // With a second, deeper explanation layer
|
|
166
|
+
* <AILabelContent>
|
|
167
|
+
* <PopoverHeader>
|
|
168
|
+
* <PopoverTitle>Why you're seeing this</PopoverTitle>
|
|
169
|
+
* </PopoverHeader>
|
|
170
|
+
* <PopoverDescription>Flagged for overdue follow-up.</PopoverDescription>
|
|
171
|
+
* <Collapsible>
|
|
172
|
+
* <CollapsibleTrigger asChild>
|
|
173
|
+
* <Button variant="link" size="sm">Show more</Button>
|
|
174
|
+
* </CollapsibleTrigger>
|
|
175
|
+
* <CollapsibleContent className="text-sm text-muted-foreground">
|
|
176
|
+
* Based on the care-gap model, last-visit date, and current risk score.
|
|
177
|
+
* </CollapsibleContent>
|
|
178
|
+
* </Collapsible>
|
|
179
|
+
* </AILabelContent>
|
|
180
|
+
*/
|
|
181
|
+
//#endregion
|
|
182
|
+
export { AILabel, AILabelContent, AILabelTrigger };
|
|
183
|
+
|
|
184
|
+
//# sourceMappingURL=ai-label.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-label.js","names":[],"sources":["../../../src/components/ui/ai-label.tsx"],"sourcesContent":["/**\n * AILabel — the accessible entry point into an AI explanation.\n *\n * A thin composite over Popover: `AILabelTrigger` is a real, labelled\n * `Button` — a transparent `variant=\"ghost\"` button with a visible\n * `[sparkle] AI` label, not `AISparkle` alone (which is `aria-hidden` and\n * purely a brand mark — see its own file) and not an icon-only button\n * either. Deliberately `ghost`, not `variant=\"ai\"`'s filled AI Chip look\n * (Button's own AI Chip recipe) — the two need to read apart: the Chip is\n * an action, this is a marker/explainability trigger, and a filled pill\n * here would make them visually indistinguishable. The marker has visible\n * \"AI\" text, not just an accessible name, so the tag itself is the\n * explainability entry point a sighted user can find without hovering.\n * Opens `AILabelContent`, a Popover panel holding a short,\n * first-layer explanation.\n *\n * Marks genuine AI involvement only, never decoration — see manifest.json's\n * `ai_presence` convention for the full rule and every current AI-treatment\n * recipe. This is specifically the \"why am I seeing this\" affordance; other\n * recipes (Button variant=\"ai\", the AI Card outline, DataTable's row tint)\n * mark AI presence visually but don't need to explain anything on their own.\n */\n\nimport * as React from \"react\"\nimport { Undo2Icon } from \"lucide-react\"\n\nimport { AISparkle } from \"@/components/ui/ai-sparkle\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/components/ui/popover\"\nimport { cn } from \"@/lib/utils\"\n\n// `edited`/`onRevert` live on the root, not the trigger, so the trigger\n// AND the content both react from one place: while `edited`, the trigger\n// morphs into a ghost undo button and the content stops rendering — the\n// explanation is for AI's value, and once the user has overridden it there\n// is nothing left to explain, only something to restore.\nconst AILabelContext = React.createContext<{\n edited: boolean\n onRevert?: () => void\n revertLabel: string\n}>({ edited: false, revertLabel: \"Undo edit, restore AI value\" })\n\nfunction AILabel({\n edited = false,\n onRevert,\n revertLabel = \"Undo edit, restore AI value\",\n ...props\n}: React.ComponentProps<typeof Popover> & {\n /** True once the user has changed the AI-filled value away from what AI produced. Morphs AILabelTrigger into a ghost undo button (calling `onRevert`) and hides AILabelContent — one slot, two states. */\n edited?: boolean\n /** Restores the original AI-filled value. Required for the undo button to render; without it `edited` just hides the marker. */\n onRevert?: () => void\n /** Accessible name of the undo button; make it specific when there's more than one AI-filled field on the page. */\n revertLabel?: string\n}) {\n const context = React.useMemo(\n () => ({ edited, onRevert, revertLabel }),\n [edited, onRevert, revertLabel]\n )\n return (\n <AILabelContext.Provider value={context}>\n <Popover data-slot=\"ai-label\" {...props} />\n </AILabelContext.Provider>\n )\n}\n\ntype AILabelTriggerBaseProps = Omit<\n React.ComponentProps<typeof Button>,\n \"variant\" | \"size\" | \"children\" | \"asChild\" | \"aria-label\"\n>\n\n// Two axes, same as Button: `size` (\"default\" | \"sm\") is how tall the\n// marker is; `iconOnly` drops the visible \"AI\" text and leaves just the\n// sparkle, square, at that same size step. Icon-only is opt-in only — for\n// when a designer explicitly asks for it; the visible-text marker is the\n// default at every density. Like Button's own `iconOnly`, it needs an\n// `aria-label` since there's no text left for a screen reader to fall back\n// on; with the visible \"AI\" text, `aria-label` stays optional (only for a\n// more specific name than \"AI\").\n//\n// `sm` renders at Button's `xs` step (24px, h-6), not Button's own `sm`\n// (28px, h-7) — AILabelTrigger's \"sm\" is its own size name, not a literal\n// passthrough of Button's. 24px is a deliberate floor, not a shrink for\n// its own sake: it's the exact minimum WCAG 2.5.8 (AA) requires (24×24 CSS\n// px), which the marker clears on both axes since the visible \"AI\" text\n// keeps its rendered width well over 24px regardless of height — so this\n// is legal at every step down to here, just with no margin left below it.\n// Matching this to the fixed-height label row in Field/FieldLabel and\n// Field/FieldLegend (their own `aiLabel` slot) is exactly why this exists:\n// one component-level default instead of a `className=\"h-6\"` override at\n// every call site.\ntype AILabelTriggerSize = \"default\" | \"sm\"\nconst BUTTON_SIZE_BY_AILABEL_TRIGGER_SIZE = {\n default: \"default\",\n sm: \"xs\",\n} as const\ntype AILabelTriggerProps = AILabelTriggerBaseProps & {\n size?: AILabelTriggerSize\n} & (\n | { iconOnly: true; \"aria-label\": string }\n | { iconOnly?: false; \"aria-label\"?: string }\n )\n\n// The undo button is the same size step as the marker it replaces, just\n// square (`iconOnly`) — so the slot keeps its footprint when they swap.\n\nfunction AILabelTrigger({\n className,\n size = \"sm\",\n iconOnly = false,\n ...props\n}: AILabelTriggerProps) {\n const { edited, onRevert, revertLabel } = React.useContext(AILabelContext)\n const buttonSize = BUTTON_SIZE_BY_AILABEL_TRIGGER_SIZE[size]\n\n // Overridden by the user: same slot, same footprint (the matching icon-*\n // size, so the row doesn't shift when the two states swap), but a plain\n // ghost undo button instead of the marker — and NOT a PopoverTrigger, so\n // there's no popover to open. `onRevert` is what makes this an undo;\n // without it there's nothing to restore, so the slot just renders empty.\n if (edited) {\n if (!onRevert) return null\n // Drop the marker's own aria-label — the undo button has its own name.\n const rest = Object.fromEntries(\n Object.entries(props).filter(([key]) => key !== \"aria-label\")\n )\n return (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size={buttonSize}\n iconOnly\n aria-label={revertLabel}\n data-slot=\"ai-label-revert\"\n className={className}\n onClick={onRevert}\n {...(rest as Omit<\n React.ComponentProps<typeof Button>,\n \"size\" | \"aria-label\"\n >)}\n >\n <Undo2Icon />\n </Button>\n )\n }\n\n // variant=\"ghost\", not \"ai\" — a filled AI Chip (Button's own AI Chip\n // recipe: variant=\"ai\" rounded-full) is visually near-identical to this,\n // and the two need to read apart: the Chip is an action, this is a\n // marker/explainability trigger. Visible \"AI\" text is the accessible\n // name (WCAG 2.5.3 label-in-name) — pass aria-label yourself only to say\n // something more specific than \"AI\". With `iconOnly` there's no visible\n // text at all (just the sparkle), so `aria-label` is required the same\n // way Button's own `iconOnly` requires it. Two explicit branches (not a\n // single computed `iconOnly`) because Button's prop type is a\n // discriminated union keyed on the literal, which a boolean can't satisfy.\n if (iconOnly) {\n const { \"aria-label\": ariaLabel, ...rest } = props as {\n \"aria-label\": string\n }\n return (\n <PopoverTrigger asChild>\n <Button\n variant=\"ghost\"\n size={buttonSize}\n iconOnly\n aria-label={ariaLabel}\n className={className}\n {...rest}\n >\n <AISparkle />\n </Button>\n </PopoverTrigger>\n )\n }\n\n return (\n <PopoverTrigger asChild>\n <Button\n variant=\"ghost\"\n size={buttonSize}\n className={className}\n {...(props as Omit<\n React.ComponentProps<typeof Button>,\n \"size\" | \"iconOnly\" | \"asChild\"\n >)}\n >\n <AISparkle data-icon=\"inline-start\" />\n AI\n </Button>\n </PopoverTrigger>\n )\n}\n\nfunction AILabelContent({\n className,\n ...props\n}: React.ComponentProps<typeof PopoverContent>) {\n const { edited } = React.useContext(AILabelContext)\n // Nothing to explain once the user has overridden the value.\n if (edited) return null\n return (\n <PopoverContent\n data-slot=\"ai-label-content\"\n className={cn(\"w-80\", className)}\n {...props}\n />\n )\n}\n\nexport { AILabel, AILabelTrigger, AILabelContent }\n\n/**\n * WHEN TO USE\n * - Any specific AI-generated content or suggestion where a user might reasonably ask \"why am I seeing this\" — a DataTable AI-tinted row, an AI Card summary, an inline AI suggestion in a form\n * - As the accessible explainability entry point required alongside any other AI-presence styling (Button variant=\"ai\", the AI Card outline, DataTable's row tint) — those mark presence, this explains it\n *\n * NOT WHEN\n * - Marking AI presence with no explanation to give — use plain AISparkle or Button variant=\"ai\" instead; don't add AILabel just to decorate something as AI-flavored\n * - A full walkthrough or multi-step explanation — use a Dialog instead; AILabel's popover is for a short, first-layer summary, not a document\n *\n * KEY RULES\n * - size: \"default\" | \"sm\" (default) — the marker's height step, matched to the surrounding size/density; both\n * render the visible `[sparkle] AI` text at Button's matching text size. These are the only two to pick from by default.\n * - iconOnly: drops the \"AI\" text and leaves just the sparkle, square, at that same size step. OPT-IN ONLY — use it\n * solely when a designer explicitly asks for an icon-only marker, never as an automatic \"tight space\" fallback\n * (DataTable keeps the visible text at every density). Icon-only is a shape, not a size — same two-axis split as\n * Button's own `size` + `iconOnly`.\n * - Without `iconOnly` no `aria-label` is needed — the visible \"AI\" text is the accessible name; pass one yourself only\n * for a more specific name. With `iconOnly` there's no visible text, so `aria-label` is required — enforced at the\n * type level, same as Button.\n * - AISparkle inside the trigger stays `aria-hidden` (its own default) — the chip's visible \"AI\" text is what's\n * announced, or with `iconOnly` the required `aria-label`.\n * - An AI-filled value the user can override is the `edited`/`onRevert` pair on AILabel itself,\n * not a hand-rolled swap: pass `edited={value !== aiValue}` and `onRevert={() => setValue(aiValue)}`,\n * and while edited the trigger morphs into a ghost `Undo2Icon` button at the matching icon size\n * (same footprint, no layout shift) that restores the AI value, and AILabelContent stops\n * rendering — nothing left to explain, only something to restore. `revertLabel` names the undo\n * button (default \"Undo edit, restore AI value\"); make it specific with several AI-filled fields\n * on one page. Input and Textarea (in the label row, beside FieldLabel), Select (beside the trigger)\n * and Checkbox/RadioGroup (beside the option or legend) all use exactly this — see the AI/* stories.\n * - For a second, deeper layer of explanation, nest Collapsible/CollapsibleTrigger/CollapsibleContent inside AILabelContent (e.g. a \"Show more\" link) rather than opening a second popover — keep the deeper detail inside the same explanation surface, revealed only on request\n * - PopoverTitle/PopoverDescription inside AILabelContent wire up aria-labelledby/aria-describedby automatically, same as any other Popover\n *\n * USAGE\n * <AILabel>\n * <AILabelTrigger />\n * <AILabelContent>\n * <PopoverHeader>\n * <PopoverTitle>Why you're seeing this</PopoverTitle>\n * </PopoverHeader>\n * <PopoverDescription>\n * Flagged because this patient's last visit was over 90 days ago.\n * </PopoverDescription>\n * </AILabelContent>\n * </AILabel>\n *\n * // Icon-only — only when a designer explicitly asks for it; aria-label required\n * <AILabelTrigger iconOnly aria-label=\"AI-suggested row\" />\n *\n * // AI-filled value the user can override: marker while untouched, undo once edited\n * <AILabel edited={value !== AI_VALUE} onRevert={() => setValue(AI_VALUE)}>\n * <AILabelTrigger aria-label=\"AI-filled: why this value?\" />\n * <AILabelContent>...</AILabelContent>\n * </AILabel>\n *\n * // With a second, deeper explanation layer\n * <AILabelContent>\n * <PopoverHeader>\n * <PopoverTitle>Why you're seeing this</PopoverTitle>\n * </PopoverHeader>\n * <PopoverDescription>Flagged for overdue follow-up.</PopoverDescription>\n * <Collapsible>\n * <CollapsibleTrigger asChild>\n * <Button variant=\"link\" size=\"sm\">Show more</Button>\n * </CollapsibleTrigger>\n * <CollapsibleContent className=\"text-sm text-muted-foreground\">\n * Based on the care-gap model, last-visit date, and current risk score.\n * </CollapsibleContent>\n * </Collapsible>\n * </AILabelContent>\n */\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,IAAM,iBAAiB,MAAM,cAI1B;CAAE,QAAQ;CAAO,aAAa;AAA8B,CAAC;AAEhE,SAAS,QAAQ,EACf,SAAS,OACT,UACA,cAAc,+BACd,GAAG,SAQF;CACD,MAAM,UAAU,MAAM,eACb;EAAE;EAAQ;EAAU;CAAY,IACvC;EAAC;EAAQ;EAAU;CAAW,CAChC;CACA,OACE,oBAAC,eAAe,UAAhB;EAAyB,OAAO;EAC9B,UAAA,oBAAC,SAAD;GAAS,aAAU;GAAW,GAAI;EAAQ,CAAA;CACnB,CAAA;AAE7B;AA4BA,IAAM,sCAAsC;CAC1C,SAAS;CACT,IAAI;AACN;AAWA,SAAS,eAAe,EACtB,WACA,OAAO,MACP,WAAW,OACX,GAAG,SACmB;CACtB,MAAM,EAAE,QAAQ,UAAU,gBAAgB,MAAM,WAAW,cAAc;CACzE,MAAM,aAAa,oCAAoC;CAOvD,IAAI,QAAQ;EACV,IAAI,CAAC,UAAU,OAAO;EAEtB,MAAM,OAAO,OAAO,YAClB,OAAO,QAAQ,KAAK,CAAC,CAAC,QAAQ,CAAC,SAAS,QAAQ,YAAY,CAC9D;EACA,OACE,oBAAC,QAAD;GACE,MAAK;GACL,SAAQ;GACR,MAAM;GACN,UAAA;GACA,cAAY;GACZ,aAAU;GACC;GACX,SAAS;GACT,GAAK;GAKL,UAAA,oBAAC,WAAD,CAAY,CAAA;EACN,CAAA;CAEZ;CAYA,IAAI,UAAU;EACZ,MAAM,EAAE,cAAc,WAAW,GAAG,SAAS;EAG7C,OACE,oBAAC,gBAAD;GAAgB,SAAA;GACd,UAAA,oBAAC,QAAD;IACE,SAAQ;IACR,MAAM;IACN,UAAA;IACA,cAAY;IACD;IACX,GAAI;IAEJ,UAAA,oBAAC,WAAD,CAAY,CAAA;GACN,CAAA;EACM,CAAA;CAEpB;CAEA,OACE,oBAAC,gBAAD;EAAgB,SAAA;EACd,UAAA,qBAAC,QAAD;GACE,SAAQ;GACR,MAAM;GACK;GACX,GAAK;GAJP,UAAA,CASE,oBAAC,WAAD,EAAW,aAAU,eAAgB,CAAA,GAAC,IAEhC;;CACM,CAAA;AAEpB;AAEA,SAAS,eAAe,EACtB,WACA,GAAG,SAC2C;CAC9C,MAAM,EAAE,WAAW,MAAM,WAAW,cAAc;CAElD,IAAI,QAAQ,OAAO;CACnB,OACE,oBAAC,gBAAD;EACE,aAAU;EACV,WAAW,GAAG,QAAQ,SAAS;EAC/B,GAAI;CACL,CAAA;AAEL"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AISparkle — the single AI-identity mark ("Sara") for all AI-generated
|
|
3
|
+
* content and affordances: one gradient 4-pointed star (pink→orange, washed
|
|
4
|
+
* with yellow), per the AI Identity tokens in theme.css. Path/gradient/shine
|
|
5
|
+
* geometry is a hand-authored SVG — not lucide-react, since the two-layer
|
|
6
|
+
* gradient fill and animated states aren't expressible with a plain stroked
|
|
7
|
+
* icon.
|
|
8
|
+
*
|
|
9
|
+
* `size` is optional by design rather than a fixed pixel width/height —
|
|
10
|
+
* omit it to inherit whatever ambient icon sizing applies (e.g. a Button's
|
|
11
|
+
* `[&_svg]` rule), or set it for a token-derived size when used standalone.
|
|
12
|
+
*
|
|
13
|
+
* Purely a brand/identity mark — `aria-hidden` by default (see below) — not
|
|
14
|
+
* an accessible trigger for anything. It marks genuine AI involvement only,
|
|
15
|
+
* never decoration; see manifest.json's `ai_presence` convention for the
|
|
16
|
+
* full rule and every current AI-treatment recipe. When the surface needs
|
|
17
|
+
* an actual accessible entry point into an explanation (not just a visual
|
|
18
|
+
* marker), that's AILabel's job, not this icon's.
|
|
19
|
+
*/
|
|
20
|
+
import * as React from "react";
|
|
21
|
+
import { type VariantProps } from "class-variance-authority";
|
|
22
|
+
declare const aiSparkleSizeVariants: (props?: ({
|
|
23
|
+
size?: "sm" | "md" | "lg" | null | undefined;
|
|
24
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
25
|
+
declare const aiSparkleStateVariants: (props?: ({
|
|
26
|
+
state?: "default" | "listening" | "short-processing" | "long-processing" | null | undefined;
|
|
27
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
28
|
+
declare function AISparkle({ className, size, state, tone, ...props }: React.ComponentProps<"svg"> & VariantProps<typeof aiSparkleSizeVariants> & VariantProps<typeof aiSparkleStateVariants> & {
|
|
29
|
+
/**
|
|
30
|
+
* "gradient" (default) — the branded two-layer pink/orange/yellow fill.
|
|
31
|
+
* "mono" — a flat `fill="currentColor"` star, no gradient defs, no
|
|
32
|
+
* long-processing shine (that needs the gradient's own white-wash
|
|
33
|
+
* layer). For placing the mark on top of an already-colored surface
|
|
34
|
+
* (e.g. AIAvatar's gradient background) where the full brand gradient
|
|
35
|
+
* would read as muddy — set the icon's own text color instead.
|
|
36
|
+
*/
|
|
37
|
+
tone?: "gradient" | "mono";
|
|
38
|
+
}): React.JSX.Element;
|
|
39
|
+
export { AISparkle, aiSparkleSizeVariants, aiSparkleStateVariants };
|
|
40
|
+
/**
|
|
41
|
+
* WHEN TO USE
|
|
42
|
+
* - AI message avatars, empty-state headers, agent badges, copilot-assisted
|
|
43
|
+
* actions, AI-generated content markers.
|
|
44
|
+
* - Compose it as a Button's leading icon (`data-icon="inline-start"`) or a
|
|
45
|
+
* small corner accent on an icon-only trigger.
|
|
46
|
+
*
|
|
47
|
+
* NOT WHEN
|
|
48
|
+
* - Generic decorative sparkle unrelated to AI — use lucide's `Sparkles`.
|
|
49
|
+
* - A loading spinner — use Spinner; AISparkle's `state` communicates AI
|
|
50
|
+
* activity specifically, not generic async work.
|
|
51
|
+
* - Animated backgrounds or large decorative use — this is an identity mark,
|
|
52
|
+
* not a motion asset.
|
|
53
|
+
*
|
|
54
|
+
* KEY RULES
|
|
55
|
+
* - size: sm | md | lg — optional. Leave it unset when composing inside Button
|
|
56
|
+
* (its `[&_svg]` sizing takes over, same as any other icon) so it inherits
|
|
57
|
+
* the ambient icon size; set it explicitly for a token-derived size
|
|
58
|
+
* (size-4/5/6) when used standalone.
|
|
59
|
+
* - state: default | listening | short-processing | long-processing (default
|
|
60
|
+
* "default") — each maps to its own keyframe animation in
|
|
61
|
+
* src/styles/ai-sparkle.css (imported once via index.css, not per-usage):
|
|
62
|
+
* listening bobs vertically, short-processing does one full rotation then
|
|
63
|
+
* holds, long-processing pulses in scale AND sweeps a diagonal shine
|
|
64
|
+
* across the star, clipped to its silhouette. These are custom keyframes,
|
|
65
|
+
* not Tailwind's animate-bounce/spin/pulse — the motion curves and hold
|
|
66
|
+
* points are specific to this mark. The shine's -50px..50px translate is
|
|
67
|
+
* calibrated directly for this component's own 80x80 viewBox + 120x120
|
|
68
|
+
* rect — no extra scaling wrapper needed. All three loop `infinite` for as
|
|
69
|
+
* long as the state is active (not just long-processing — a slow
|
|
70
|
+
* short-processing step or a long listening session runs past 5s just as
|
|
71
|
+
* easily), so `ai-sparkle.css` falls every one of them back to the static
|
|
72
|
+
* resting mark under `prefers-reduced-motion` (SC 2.2.2, Level A).
|
|
73
|
+
* - tone: gradient (default) | mono — gradient is the fixed brand fill, driven
|
|
74
|
+
* by the `--color-ai-grad-*` / `--color-ai-wash*` tokens in theme.css, no
|
|
75
|
+
* color props of its own. mono drops to a flat `fill="currentColor"` star
|
|
76
|
+
* (no gradient defs, no long-processing shine) — set the icon's own text
|
|
77
|
+
* color instead when it needs to sit on top of an already-colored surface
|
|
78
|
+
* (e.g. AIAvatar's gradient background) where the full brand gradient
|
|
79
|
+
* would read as muddy.
|
|
80
|
+
* - `aria-hidden` by default, matching every other decorative icon in this
|
|
81
|
+
* system — pair it with real text, not as a standalone control.
|
|
82
|
+
*
|
|
83
|
+
* USAGE
|
|
84
|
+
* <Button variant="ai">
|
|
85
|
+
* <AISparkle data-icon="inline-start" />
|
|
86
|
+
* Summarize
|
|
87
|
+
* </Button>
|
|
88
|
+
*
|
|
89
|
+
* <AISparkle size="lg" state="short-processing" />
|
|
90
|
+
*/
|
|
91
|
+
//# sourceMappingURL=ai-sparkle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-sparkle.d.ts","sourceRoot":"","sources":["../../../src/components/ui/ai-sparkle.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAOjE,QAAA,MAAM,qBAAqB;;8EAQzB,CAAA;AAEF,QAAA,MAAM,sBAAsB;;8EAY1B,CAAA;AAEF,iBAAS,SAAS,CAAC,EACjB,SAAS,EACT,IAAI,EACJ,KAAiB,EACjB,IAAiB,EACjB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,GAC5B,YAAY,CAAC,OAAO,qBAAqB,CAAC,GAC1C,YAAY,CAAC,OAAO,sBAAsB,CAAC,GAAG;IAC5C;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;CAC3B,qBA0FF;AAED,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,CAAA;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { cn } from "../../lib/utils.js";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { cva } from "class-variance-authority";
|
|
5
|
+
//#region src/components/ui/ai-sparkle.tsx
|
|
6
|
+
/**
|
|
7
|
+
* AISparkle — the single AI-identity mark ("Sara") for all AI-generated
|
|
8
|
+
* content and affordances: one gradient 4-pointed star (pink→orange, washed
|
|
9
|
+
* with yellow), per the AI Identity tokens in theme.css. Path/gradient/shine
|
|
10
|
+
* geometry is a hand-authored SVG — not lucide-react, since the two-layer
|
|
11
|
+
* gradient fill and animated states aren't expressible with a plain stroked
|
|
12
|
+
* icon.
|
|
13
|
+
*
|
|
14
|
+
* `size` is optional by design rather than a fixed pixel width/height —
|
|
15
|
+
* omit it to inherit whatever ambient icon sizing applies (e.g. a Button's
|
|
16
|
+
* `[&_svg]` rule), or set it for a token-derived size when used standalone.
|
|
17
|
+
*
|
|
18
|
+
* Purely a brand/identity mark — `aria-hidden` by default (see below) — not
|
|
19
|
+
* an accessible trigger for anything. It marks genuine AI involvement only,
|
|
20
|
+
* never decoration; see manifest.json's `ai_presence` convention for the
|
|
21
|
+
* full rule and every current AI-treatment recipe. When the surface needs
|
|
22
|
+
* an actual accessible entry point into an explanation (not just a visual
|
|
23
|
+
* marker), that's AILabel's job, not this icon's.
|
|
24
|
+
*/
|
|
25
|
+
var STAR_PATH = "M40 5C40 24.33 55.67 40 75 40C55.67 40 40 55.67 40 75C40 55.67 24.33 40 5 40C24.33 40 40 24.33 40 5Z";
|
|
26
|
+
var aiSparkleSizeVariants = cva("", { variants: { size: {
|
|
27
|
+
sm: "size-4",
|
|
28
|
+
md: "size-5",
|
|
29
|
+
lg: "size-6"
|
|
30
|
+
} } });
|
|
31
|
+
var aiSparkleStateVariants = cva("", {
|
|
32
|
+
variants: { state: {
|
|
33
|
+
default: "",
|
|
34
|
+
listening: "mmds-ai-sparkle-listening",
|
|
35
|
+
"short-processing": "mmds-ai-sparkle-short-processing",
|
|
36
|
+
"long-processing": "mmds-ai-sparkle-long-processing"
|
|
37
|
+
} },
|
|
38
|
+
defaultVariants: { state: "default" }
|
|
39
|
+
});
|
|
40
|
+
function AISparkle({ className, size, state = "default", tone = "gradient", ...props }) {
|
|
41
|
+
const id = React.useId();
|
|
42
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
43
|
+
"data-slot": "ai-sparkle",
|
|
44
|
+
"data-state": state,
|
|
45
|
+
"data-tone": tone,
|
|
46
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
47
|
+
viewBox: "0 0 80 80",
|
|
48
|
+
fill: "none",
|
|
49
|
+
className: cn("shrink-0", size && aiSparkleSizeVariants({ size }), aiSparkleStateVariants({ state }), className),
|
|
50
|
+
"aria-hidden": "true",
|
|
51
|
+
...props,
|
|
52
|
+
children: tone === "mono" ? /* @__PURE__ */ jsx("path", {
|
|
53
|
+
d: STAR_PATH,
|
|
54
|
+
fill: "currentColor"
|
|
55
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
56
|
+
/* @__PURE__ */ jsxs("defs", { children: [
|
|
57
|
+
/* @__PURE__ */ jsxs("linearGradient", {
|
|
58
|
+
id: `${id}-grad`,
|
|
59
|
+
x1: "17.1875",
|
|
60
|
+
y1: "12.1875",
|
|
61
|
+
x2: "62.0313",
|
|
62
|
+
y2: "65",
|
|
63
|
+
gradientUnits: "userSpaceOnUse",
|
|
64
|
+
children: [/* @__PURE__ */ jsx("stop", {
|
|
65
|
+
stopColor: "var(--color-ai-grad-from)",
|
|
66
|
+
stopOpacity: "var(--color-ai-grad-from-alpha)"
|
|
67
|
+
}), /* @__PURE__ */ jsx("stop", {
|
|
68
|
+
offset: "1",
|
|
69
|
+
stopColor: "var(--color-ai-grad-to)"
|
|
70
|
+
})]
|
|
71
|
+
}),
|
|
72
|
+
/* @__PURE__ */ jsxs("linearGradient", {
|
|
73
|
+
id: `${id}-wash`,
|
|
74
|
+
x1: "17.1875",
|
|
75
|
+
y1: "12.1875",
|
|
76
|
+
x2: "62.0313",
|
|
77
|
+
y2: "65",
|
|
78
|
+
gradientUnits: "userSpaceOnUse",
|
|
79
|
+
children: [/* @__PURE__ */ jsx("stop", {
|
|
80
|
+
stopColor: "var(--color-ai-wash)",
|
|
81
|
+
stopOpacity: "0"
|
|
82
|
+
}), /* @__PURE__ */ jsx("stop", {
|
|
83
|
+
offset: "1",
|
|
84
|
+
stopColor: "var(--color-ai-wash)"
|
|
85
|
+
})]
|
|
86
|
+
}),
|
|
87
|
+
state === "long-processing" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("clipPath", {
|
|
88
|
+
id: `${id}-clip`,
|
|
89
|
+
children: /* @__PURE__ */ jsx("path", { d: STAR_PATH })
|
|
90
|
+
}), /* @__PURE__ */ jsxs("linearGradient", {
|
|
91
|
+
id: `${id}-shine`,
|
|
92
|
+
x1: "0",
|
|
93
|
+
y1: "0",
|
|
94
|
+
x2: "1",
|
|
95
|
+
y2: "1",
|
|
96
|
+
children: [
|
|
97
|
+
/* @__PURE__ */ jsx("stop", {
|
|
98
|
+
offset: "0",
|
|
99
|
+
stopColor: "#fff",
|
|
100
|
+
stopOpacity: "0"
|
|
101
|
+
}),
|
|
102
|
+
/* @__PURE__ */ jsx("stop", {
|
|
103
|
+
offset: "0.4",
|
|
104
|
+
stopColor: "#fff",
|
|
105
|
+
stopOpacity: "0"
|
|
106
|
+
}),
|
|
107
|
+
/* @__PURE__ */ jsx("stop", {
|
|
108
|
+
offset: "0.5",
|
|
109
|
+
stopColor: "#fff",
|
|
110
|
+
stopOpacity: "0.55"
|
|
111
|
+
}),
|
|
112
|
+
/* @__PURE__ */ jsx("stop", {
|
|
113
|
+
offset: "0.6",
|
|
114
|
+
stopColor: "#fff",
|
|
115
|
+
stopOpacity: "0"
|
|
116
|
+
}),
|
|
117
|
+
/* @__PURE__ */ jsx("stop", {
|
|
118
|
+
offset: "1",
|
|
119
|
+
stopColor: "#fff",
|
|
120
|
+
stopOpacity: "0"
|
|
121
|
+
})
|
|
122
|
+
]
|
|
123
|
+
})] })
|
|
124
|
+
] }),
|
|
125
|
+
/* @__PURE__ */ jsx("path", {
|
|
126
|
+
d: STAR_PATH,
|
|
127
|
+
fill: `url(#${id}-grad)`
|
|
128
|
+
}),
|
|
129
|
+
/* @__PURE__ */ jsx("path", {
|
|
130
|
+
d: STAR_PATH,
|
|
131
|
+
fill: `url(#${id}-wash)`,
|
|
132
|
+
opacity: "var(--color-ai-wash-alpha)"
|
|
133
|
+
}),
|
|
134
|
+
state === "long-processing" && /* @__PURE__ */ jsx("rect", {
|
|
135
|
+
x: "-20",
|
|
136
|
+
y: "-20",
|
|
137
|
+
width: "120",
|
|
138
|
+
height: "120",
|
|
139
|
+
fill: `url(#${id}-shine)`,
|
|
140
|
+
clipPath: `url(#${id}-clip)`,
|
|
141
|
+
className: "mmds-ai-sparkle-shine"
|
|
142
|
+
})
|
|
143
|
+
] })
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* WHEN TO USE
|
|
148
|
+
* - AI message avatars, empty-state headers, agent badges, copilot-assisted
|
|
149
|
+
* actions, AI-generated content markers.
|
|
150
|
+
* - Compose it as a Button's leading icon (`data-icon="inline-start"`) or a
|
|
151
|
+
* small corner accent on an icon-only trigger.
|
|
152
|
+
*
|
|
153
|
+
* NOT WHEN
|
|
154
|
+
* - Generic decorative sparkle unrelated to AI — use lucide's `Sparkles`.
|
|
155
|
+
* - A loading spinner — use Spinner; AISparkle's `state` communicates AI
|
|
156
|
+
* activity specifically, not generic async work.
|
|
157
|
+
* - Animated backgrounds or large decorative use — this is an identity mark,
|
|
158
|
+
* not a motion asset.
|
|
159
|
+
*
|
|
160
|
+
* KEY RULES
|
|
161
|
+
* - size: sm | md | lg — optional. Leave it unset when composing inside Button
|
|
162
|
+
* (its `[&_svg]` sizing takes over, same as any other icon) so it inherits
|
|
163
|
+
* the ambient icon size; set it explicitly for a token-derived size
|
|
164
|
+
* (size-4/5/6) when used standalone.
|
|
165
|
+
* - state: default | listening | short-processing | long-processing (default
|
|
166
|
+
* "default") — each maps to its own keyframe animation in
|
|
167
|
+
* src/styles/ai-sparkle.css (imported once via index.css, not per-usage):
|
|
168
|
+
* listening bobs vertically, short-processing does one full rotation then
|
|
169
|
+
* holds, long-processing pulses in scale AND sweeps a diagonal shine
|
|
170
|
+
* across the star, clipped to its silhouette. These are custom keyframes,
|
|
171
|
+
* not Tailwind's animate-bounce/spin/pulse — the motion curves and hold
|
|
172
|
+
* points are specific to this mark. The shine's -50px..50px translate is
|
|
173
|
+
* calibrated directly for this component's own 80x80 viewBox + 120x120
|
|
174
|
+
* rect — no extra scaling wrapper needed. All three loop `infinite` for as
|
|
175
|
+
* long as the state is active (not just long-processing — a slow
|
|
176
|
+
* short-processing step or a long listening session runs past 5s just as
|
|
177
|
+
* easily), so `ai-sparkle.css` falls every one of them back to the static
|
|
178
|
+
* resting mark under `prefers-reduced-motion` (SC 2.2.2, Level A).
|
|
179
|
+
* - tone: gradient (default) | mono — gradient is the fixed brand fill, driven
|
|
180
|
+
* by the `--color-ai-grad-*` / `--color-ai-wash*` tokens in theme.css, no
|
|
181
|
+
* color props of its own. mono drops to a flat `fill="currentColor"` star
|
|
182
|
+
* (no gradient defs, no long-processing shine) — set the icon's own text
|
|
183
|
+
* color instead when it needs to sit on top of an already-colored surface
|
|
184
|
+
* (e.g. AIAvatar's gradient background) where the full brand gradient
|
|
185
|
+
* would read as muddy.
|
|
186
|
+
* - `aria-hidden` by default, matching every other decorative icon in this
|
|
187
|
+
* system — pair it with real text, not as a standalone control.
|
|
188
|
+
*
|
|
189
|
+
* USAGE
|
|
190
|
+
* <Button variant="ai">
|
|
191
|
+
* <AISparkle data-icon="inline-start" />
|
|
192
|
+
* Summarize
|
|
193
|
+
* </Button>
|
|
194
|
+
*
|
|
195
|
+
* <AISparkle size="lg" state="short-processing" />
|
|
196
|
+
*/
|
|
197
|
+
//#endregion
|
|
198
|
+
export { AISparkle, aiSparkleSizeVariants, aiSparkleStateVariants };
|
|
199
|
+
|
|
200
|
+
//# sourceMappingURL=ai-sparkle.js.map
|