sonance-brand-mcp 1.3.1 → 1.3.3

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.
Files changed (136) hide show
  1. package/dist/assets/api/sonance-analyze/route.ts +1116 -0
  2. package/dist/assets/api/sonance-assets/route.ts +113 -0
  3. package/dist/assets/api/sonance-components/route.ts +41 -0
  4. package/dist/assets/api/sonance-inject-id/route.ts +363 -0
  5. package/dist/assets/api/sonance-save-logo/route.ts +426 -0
  6. package/dist/assets/api/sonance-theme/route.ts +106 -0
  7. package/dist/assets/brand-system.ts +1265 -0
  8. package/dist/assets/components/accordion.stories.tsx +26 -26
  9. package/dist/assets/components/accordion.tsx +3 -3
  10. package/dist/assets/components/alert-dialog.stories.tsx +7 -7
  11. package/dist/assets/components/alert-dialog.tsx +2 -1
  12. package/dist/assets/components/alert.stories.tsx +3 -3
  13. package/dist/assets/components/alert.tsx +4 -3
  14. package/dist/assets/components/aspect-ratio.stories.tsx +4 -1
  15. package/dist/assets/components/autocomplete.stories.tsx +9 -9
  16. package/dist/assets/components/autocomplete.tsx +3 -3
  17. package/dist/assets/components/avatar.stories.tsx +5 -5
  18. package/dist/assets/components/avatar.tsx +4 -4
  19. package/dist/assets/components/badge.stories.tsx +10 -10
  20. package/dist/assets/components/badge.tsx +3 -3
  21. package/dist/assets/components/breadcrumbs.stories.tsx +7 -7
  22. package/dist/assets/components/breadcrumbs.tsx +13 -8
  23. package/dist/assets/components/button.stories.tsx +74 -74
  24. package/dist/assets/components/button.tsx +2 -0
  25. package/dist/assets/components/calendar.stories.tsx +11 -11
  26. package/dist/assets/components/calendar.tsx +4 -4
  27. package/dist/assets/components/card.stories.tsx +22 -22
  28. package/dist/assets/components/card.tsx +7 -3
  29. package/dist/assets/components/carousel.stories.tsx +6 -6
  30. package/dist/assets/components/carousel.tsx +10 -8
  31. package/dist/assets/components/chart.tsx +5 -5
  32. package/dist/assets/components/checkbox-group.stories.tsx +6 -6
  33. package/dist/assets/components/checkbox-group.tsx +3 -3
  34. package/dist/assets/components/checkbox.stories.tsx +23 -20
  35. package/dist/assets/components/checkbox.tsx +13 -16
  36. package/dist/assets/components/code.stories.tsx +24 -24
  37. package/dist/assets/components/code.tsx +7 -14
  38. package/dist/assets/components/collapsible.stories.tsx +3 -3
  39. package/dist/assets/components/command.stories.tsx +14 -14
  40. package/dist/assets/components/command.tsx +4 -3
  41. package/dist/assets/components/context-menu.stories.tsx +1 -1
  42. package/dist/assets/components/context-menu.tsx +3 -7
  43. package/dist/assets/components/date-input.stories.tsx +9 -9
  44. package/dist/assets/components/date-input.tsx +2 -2
  45. package/dist/assets/components/date-picker.stories.tsx +9 -9
  46. package/dist/assets/components/date-picker.tsx +3 -3
  47. package/dist/assets/components/date-range-picker.stories.tsx +12 -12
  48. package/dist/assets/components/date-range-picker.tsx +3 -3
  49. package/dist/assets/components/dialog.stories.tsx +40 -40
  50. package/dist/assets/components/dialog.tsx +8 -12
  51. package/dist/assets/components/divider.stories.tsx +30 -30
  52. package/dist/assets/components/divider.tsx +4 -8
  53. package/dist/assets/components/drawer.stories.tsx +32 -31
  54. package/dist/assets/components/drawer.tsx +7 -6
  55. package/dist/assets/components/dropdown-menu.tsx +3 -7
  56. package/dist/assets/components/dropdown.stories.tsx +12 -12
  57. package/dist/assets/components/dropdown.tsx +5 -5
  58. package/dist/assets/components/form.stories.tsx +30 -29
  59. package/dist/assets/components/form.tsx +5 -5
  60. package/dist/assets/components/hover-card.stories.tsx +12 -10
  61. package/dist/assets/components/hover-card.tsx +1 -1
  62. package/dist/assets/components/image.stories.tsx +48 -25
  63. package/dist/assets/components/image.tsx +8 -5
  64. package/dist/assets/components/input-otp.stories.tsx +15 -15
  65. package/dist/assets/components/input-otp.tsx +5 -5
  66. package/dist/assets/components/input.stories.tsx +30 -25
  67. package/dist/assets/components/input.tsx +7 -4
  68. package/dist/assets/components/kbd.stories.tsx +34 -34
  69. package/dist/assets/components/kbd.tsx +5 -5
  70. package/dist/assets/components/link.stories.tsx +36 -36
  71. package/dist/assets/components/link.tsx +4 -0
  72. package/dist/assets/components/listbox.stories.tsx +5 -5
  73. package/dist/assets/components/listbox.tsx +4 -4
  74. package/dist/assets/components/menubar.tsx +3 -7
  75. package/dist/assets/components/navbar.stories.tsx +24 -24
  76. package/dist/assets/components/navbar.tsx +8 -14
  77. package/dist/assets/components/navigation-menu.stories.tsx +11 -9
  78. package/dist/assets/components/navigation-menu.tsx +1 -1
  79. package/dist/assets/components/number-input.stories.tsx +11 -11
  80. package/dist/assets/components/number-input.tsx +3 -3
  81. package/dist/assets/components/pagination.stories.tsx +13 -13
  82. package/dist/assets/components/pagination.tsx +6 -6
  83. package/dist/assets/components/popover.stories.tsx +35 -35
  84. package/dist/assets/components/popover.tsx +98 -15
  85. package/dist/assets/components/progress.stories.tsx +5 -5
  86. package/dist/assets/components/progress.tsx +5 -5
  87. package/dist/assets/components/radio-group.stories.tsx +7 -7
  88. package/dist/assets/components/radio-group.tsx +3 -3
  89. package/dist/assets/components/range-calendar.stories.tsx +18 -18
  90. package/dist/assets/components/range-calendar.tsx +3 -3
  91. package/dist/assets/components/resizable.stories.tsx +23 -23
  92. package/dist/assets/components/resizable.tsx +1 -1
  93. package/dist/assets/components/scroll-area.stories.tsx +15 -15
  94. package/dist/assets/components/scroll-area.tsx +1 -1
  95. package/dist/assets/components/scroll-shadow.stories.tsx +17 -17
  96. package/dist/assets/components/scroll-shadow.tsx +2 -2
  97. package/dist/assets/components/select.stories.tsx +20 -19
  98. package/dist/assets/components/select.tsx +10 -6
  99. package/dist/assets/components/separator.tsx +1 -1
  100. package/dist/assets/components/sheet.tsx +3 -7
  101. package/dist/assets/components/sidebar.stories.tsx +30 -30
  102. package/dist/assets/components/sidebar.tsx +24 -27
  103. package/dist/assets/components/skeleton.stories.tsx +3 -3
  104. package/dist/assets/components/skeleton.tsx +2 -2
  105. package/dist/assets/components/slider.stories.tsx +6 -6
  106. package/dist/assets/components/slider.tsx +3 -3
  107. package/dist/assets/components/spacer.stories.tsx +11 -11
  108. package/dist/assets/components/spacer.tsx +2 -2
  109. package/dist/assets/components/spinner.stories.tsx +8 -8
  110. package/dist/assets/components/spinner.tsx +5 -5
  111. package/dist/assets/components/switch.stories.tsx +24 -20
  112. package/dist/assets/components/switch.tsx +14 -6
  113. package/dist/assets/components/table.stories.tsx +7 -7
  114. package/dist/assets/components/table.tsx +8 -8
  115. package/dist/assets/components/tabs.stories.tsx +37 -37
  116. package/dist/assets/components/tabs.tsx +3 -3
  117. package/dist/assets/components/textarea.stories.tsx +13 -12
  118. package/dist/assets/components/textarea.tsx +3 -3
  119. package/dist/assets/components/theme-toggle.stories.tsx +31 -30
  120. package/dist/assets/components/theme-toggle.tsx +2 -2
  121. package/dist/assets/components/time-input.stories.tsx +16 -16
  122. package/dist/assets/components/time-input.tsx +2 -2
  123. package/dist/assets/components/toast.stories.tsx +8 -5
  124. package/dist/assets/components/toast.tsx +6 -6
  125. package/dist/assets/components/toggle-group.tsx +1 -1
  126. package/dist/assets/components/toggle.tsx +1 -1
  127. package/dist/assets/components/tooltip.stories.tsx +49 -27
  128. package/dist/assets/components/tooltip.tsx +1 -1
  129. package/dist/assets/components/user.stories.tsx +23 -23
  130. package/dist/assets/components/user.tsx +7 -4
  131. package/dist/assets/dev-tools/SonanceDevTools.tsx +4201 -0
  132. package/dist/assets/dev-tools/index.ts +10 -0
  133. package/dist/assets/globals.css +9 -0
  134. package/dist/assets/styles/brand-overrides.css +37 -0
  135. package/dist/index.js +1882 -7
  136. package/package.json +1 -1
@@ -31,9 +31,9 @@ type Story = StoryObj<typeof meta>;
31
31
 
32
32
  export const Default: Story = {
33
33
  render: () => (
34
- <Popover trigger={<Button variant="secondary">Click me</Button>}>
34
+ <Popover trigger={<Button id="default-button-secondary" variant="secondary">Click me</Button>}>
35
35
  <PopoverContent>
36
- <p className="text-foreground-secondary">
36
+ <p id="default-p-this-is-the-popover-" className="text-foreground-secondary">
37
37
  This is the popover content. Click outside to close.
38
38
  </p>
39
39
  </PopoverContent>
@@ -43,9 +43,9 @@ export const Default: Story = {
43
43
 
44
44
  export const OnHover: Story = {
45
45
  render: () => (
46
- <Popover trigger={<Button variant="secondary">Hover me</Button>} triggerOn="hover">
46
+ <Popover trigger={<Button id="on-hover-button-secondary" variant="secondary">Hover me</Button>} triggerOn="hover">
47
47
  <PopoverContent>
48
- <p className="text-foreground-secondary">
48
+ <p id="on-hover-p-this-popover-appears" className="text-foreground-secondary">
49
49
  This popover appears on hover.
50
50
  </p>
51
51
  </PopoverContent>
@@ -55,9 +55,9 @@ export const OnHover: Story = {
55
55
 
56
56
  export const TopPosition: Story = {
57
57
  render: () => (
58
- <Popover trigger={<Button variant="secondary">Open Top</Button>} position="top">
58
+ <Popover trigger={<Button id="top-position-button-secondary" variant="secondary">Open Top</Button>} position="top">
59
59
  <PopoverContent>
60
- <p className="text-foreground-secondary">Popover on top</p>
60
+ <p id="top-position-p-popover-on-top" className="text-foreground-secondary">Popover on top</p>
61
61
  </PopoverContent>
62
62
  </Popover>
63
63
  ),
@@ -65,9 +65,9 @@ export const TopPosition: Story = {
65
65
 
66
66
  export const LeftPosition: Story = {
67
67
  render: () => (
68
- <Popover trigger={<Button variant="secondary">Open Left</Button>} position="left">
68
+ <Popover trigger={<Button id="left-position-button-secondary" variant="secondary">Open Left</Button>} position="left">
69
69
  <PopoverContent>
70
- <p className="text-foreground-secondary">Popover on left</p>
70
+ <p id="left-position-p-popover-on-left" className="text-foreground-secondary">Popover on left</p>
71
71
  </PopoverContent>
72
72
  </Popover>
73
73
  ),
@@ -75,9 +75,9 @@ export const LeftPosition: Story = {
75
75
 
76
76
  export const RightPosition: Story = {
77
77
  render: () => (
78
- <Popover trigger={<Button variant="secondary">Open Right</Button>} position="right">
78
+ <Popover trigger={<Button id="right-position-button-secondary" variant="secondary">Open Right</Button>} position="right">
79
79
  <PopoverContent>
80
- <p className="text-foreground-secondary">Popover on right</p>
80
+ <p id="right-position-p-popover-on-right" className="text-foreground-secondary">Popover on right</p>
81
81
  </PopoverContent>
82
82
  </Popover>
83
83
  ),
@@ -86,24 +86,24 @@ export const RightPosition: Story = {
86
86
  export const AllPositions: Story = {
87
87
  render: () => (
88
88
  <div className="flex items-center gap-8 p-16">
89
- <Popover trigger={<Button variant="secondary" size="sm">Top</Button>} position="top">
89
+ <Popover trigger={<Button id="all-positions-button-secondary" variant="secondary" size="sm">Top</Button>} position="top">
90
90
  <PopoverContent>
91
- <p className="text-sm">Top position</p>
91
+ <p id="all-positions-p-top-position" className="text-sm">Top position</p>
92
92
  </PopoverContent>
93
93
  </Popover>
94
- <Popover trigger={<Button variant="secondary" size="sm">Bottom</Button>} position="bottom">
94
+ <Popover trigger={<Button id="all-positions-button-secondary" variant="secondary" size="sm">Bottom</Button>} position="bottom">
95
95
  <PopoverContent>
96
- <p className="text-sm">Bottom position</p>
96
+ <p id="all-positions-p-bottom-position" className="text-sm">Bottom position</p>
97
97
  </PopoverContent>
98
98
  </Popover>
99
- <Popover trigger={<Button variant="secondary" size="sm">Left</Button>} position="left">
99
+ <Popover trigger={<Button id="all-positions-button-secondary" variant="secondary" size="sm">Left</Button>} position="left">
100
100
  <PopoverContent>
101
- <p className="text-sm">Left position</p>
101
+ <p id="all-positions-p-left-position" className="text-sm">Left position</p>
102
102
  </PopoverContent>
103
103
  </Popover>
104
- <Popover trigger={<Button variant="secondary" size="sm">Right</Button>} position="right">
104
+ <Popover trigger={<Button id="all-positions-button-secondary" variant="secondary" size="sm">Right</Button>} position="right">
105
105
  <PopoverContent>
106
- <p className="text-sm">Right position</p>
106
+ <p id="all-positions-p-right-position" className="text-sm">Right position</p>
107
107
  </PopoverContent>
108
108
  </Popover>
109
109
  </div>
@@ -112,7 +112,7 @@ export const AllPositions: Story = {
112
112
 
113
113
  export const MenuExample: Story = {
114
114
  render: () => (
115
- <Popover trigger={<Button variant="secondary">Actions</Button>}>
115
+ <Popover trigger={<Button id="menu-example-button-secondary" variant="secondary">Actions</Button>}>
116
116
  <div className="py-2">
117
117
  {['Edit', 'Duplicate', 'Archive', 'Delete'].map((action) => (
118
118
  <button
@@ -133,14 +133,14 @@ export const UserMenuExample: Story = {
133
133
  trigger={
134
134
  <button className="flex items-center gap-2 p-2 hover:bg-secondary-hover rounded transition-colors">
135
135
  <div className="w-8 h-8 rounded-full bg-primary" />
136
- <span className="text-sm text-foreground">John Doe</span>
136
+ <span id="user-menu-example-span-john-doe" className="text-sm text-foreground">John Doe</span>
137
137
  </button>
138
138
  }
139
139
  >
140
140
  <div className="w-48">
141
141
  <div className="px-4 py-3 border-b border-border">
142
- <p className="text-sm font-medium text-foreground">John Doe</p>
143
- <p className="text-xs text-foreground-muted">john@example.com</p>
142
+ <p id="user-menu-example-p-john-doe" className="text-sm font-medium text-foreground">John Doe</p>
143
+ <p id="user-menu-example-p-johnexamplecom" className="text-xs text-foreground-muted">john@example.com</p>
144
144
  </div>
145
145
  <div className="py-2">
146
146
  {['Profile', 'Settings', 'Billing'].map((item) => (
@@ -168,36 +168,36 @@ export const ResponsiveMatrix: Story = {
168
168
  <div className="space-y-8">
169
169
  {/* Mobile */}
170
170
  <div>
171
- <h4 className="text-xs uppercase text-foreground-muted mb-2">Mobile (375px)</h4>
171
+ <h4 id="responsive-matrix-h4-mobile-375px" className="text-xs uppercase text-foreground-muted mb-2">Mobile (375px)</h4>
172
172
  <div className="w-[375px] border border-dashed border-border p-4 flex justify-center">
173
- <Popover trigger={<Button variant="secondary" size="sm">Actions</Button>}>
173
+ <Popover trigger={<Button id="responsive-matrix-button-secondary" variant="secondary" size="sm">Actions</Button>}>
174
174
  <PopoverContent>
175
- <p className="text-sm">Popover content</p>
175
+ <p id="responsive-matrix-p-popover-content" className="text-sm">Popover content</p>
176
176
  </PopoverContent>
177
177
  </Popover>
178
178
  </div>
179
179
  </div>
180
180
  {/* Tablet */}
181
181
  <div>
182
- <h4 className="text-xs uppercase text-foreground-muted mb-2">Tablet (768px)</h4>
182
+ <h4 id="responsive-matrix-h4-tablet-768px" className="text-xs uppercase text-foreground-muted mb-2">Tablet (768px)</h4>
183
183
  <div className="w-[768px] border border-dashed border-border p-4 flex justify-center gap-4">
184
- <Popover trigger={<Button variant="secondary">Top</Button>} position="top">
185
- <PopoverContent><p className="text-sm">Top popover</p></PopoverContent>
184
+ <Popover trigger={<Button id="responsive-matrix-button-secondary" variant="secondary">Top</Button>} position="top">
185
+ <PopoverContent><p id="responsive-matrix-p-top-popover" className="text-sm">Top popover</p></PopoverContent>
186
186
  </Popover>
187
- <Popover trigger={<Button variant="secondary">Bottom</Button>} position="bottom">
188
- <PopoverContent><p className="text-sm">Bottom popover</p></PopoverContent>
187
+ <Popover trigger={<Button id="responsive-matrix-button-secondary" variant="secondary">Bottom</Button>} position="bottom">
188
+ <PopoverContent><p id="responsive-matrix-p-bottom-popover" className="text-sm">Bottom popover</p></PopoverContent>
189
189
  </Popover>
190
190
  </div>
191
191
  </div>
192
192
  {/* Desktop */}
193
193
  <div>
194
- <h4 className="text-xs uppercase text-foreground-muted mb-2">Desktop (1280px)</h4>
194
+ <h4 id="responsive-matrix-h4-desktop-1280px" className="text-xs uppercase text-foreground-muted mb-2">Desktop (1280px)</h4>
195
195
  <div className="w-[1280px] border border-dashed border-border p-4 flex justify-between items-center">
196
- <Popover trigger={<Button variant="secondary">User Menu</Button>}>
196
+ <Popover trigger={<Button id="responsive-matrix-button-secondary" variant="secondary">User Menu</Button>}>
197
197
  <div className="w-48">
198
198
  <div className="px-4 py-3 border-b border-border">
199
- <p className="text-sm font-medium">John Doe</p>
200
- <p className="text-xs text-foreground-muted">john@example.com</p>
199
+ <p id="responsive-matrix-p-john-doe" className="text-sm font-medium">John Doe</p>
200
+ <p id="responsive-matrix-p-johnexamplecom" className="text-xs text-foreground-muted">john@example.com</p>
201
201
  </div>
202
202
  <div className="py-2">
203
203
  <button className="w-full px-4 py-2 text-left text-sm hover:bg-secondary-hover">Profile</button>
@@ -205,7 +205,7 @@ export const ResponsiveMatrix: Story = {
205
205
  </div>
206
206
  </div>
207
207
  </Popover>
208
- <Popover trigger={<Button variant="secondary">Actions</Button>}>
208
+ <Popover trigger={<Button id="responsive-matrix-button-secondary" variant="secondary">Actions</Button>}>
209
209
  <div className="py-2">
210
210
  {['Edit', 'Duplicate', 'Archive', 'Delete'].map((action) => (
211
211
  <button key={action} className="w-full px-4 py-2 text-left text-sm hover:bg-secondary-hover">{action}</button>
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
- import { useState, useRef, useEffect } from "react";
3
+ import { useState, useRef, useEffect, useCallback } from "react";
4
+ import { createPortal } from "react-dom";
4
5
  import { cn } from "@/lib/utils";
5
6
 
6
7
  type PopoverPosition = "top" | "bottom" | "left" | "right";
@@ -13,6 +14,11 @@ interface PopoverProps {
13
14
  className?: string;
14
15
  }
15
16
 
17
+ interface PortalPosition {
18
+ top: number;
19
+ left: number;
20
+ }
21
+
16
22
  export function Popover({
17
23
  trigger,
18
24
  children,
@@ -21,22 +27,76 @@ export function Popover({
21
27
  className,
22
28
  }: PopoverProps) {
23
29
  const [isOpen, setIsOpen] = useState(false);
30
+ const [portalPosition, setPortalPosition] = useState<PortalPosition>({ top: 0, left: 0 });
31
+ const [mounted, setMounted] = useState(false);
24
32
  const containerRef = useRef<HTMLDivElement>(null);
33
+ const triggerRef = useRef<HTMLDivElement>(null);
34
+ const popoverRef = useRef<HTMLDivElement>(null);
25
35
  const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(null);
26
36
 
37
+ // Track if component is mounted (for portal)
38
+ useEffect(() => {
39
+ setMounted(true);
40
+ return () => setMounted(false);
41
+ }, []);
42
+
43
+ // Calculate position based on trigger element
44
+ const calculatePosition = useCallback(() => {
45
+ if (!triggerRef.current) return;
46
+
47
+ const triggerRect = triggerRef.current.getBoundingClientRect();
48
+ const popoverWidth = 224; // w-56 = 14rem = 224px
49
+ const gap = 8;
50
+
51
+ let top = 0;
52
+ let left = 0;
53
+
54
+ switch (position) {
55
+ case "top":
56
+ top = triggerRect.top - gap;
57
+ left = triggerRect.left + triggerRect.width / 2 - popoverWidth / 2;
58
+ break;
59
+ case "bottom":
60
+ top = triggerRect.bottom + gap;
61
+ left = triggerRect.left + triggerRect.width / 2 - popoverWidth / 2;
62
+ break;
63
+ case "left":
64
+ top = triggerRect.top + triggerRect.height / 2;
65
+ left = triggerRect.left - popoverWidth - gap;
66
+ break;
67
+ case "right":
68
+ top = triggerRect.top + triggerRect.height / 2;
69
+ left = triggerRect.right + gap;
70
+ break;
71
+ }
72
+
73
+ setPortalPosition({ top, left });
74
+ }, [position]);
75
+
76
+ // Update position when opened
77
+ useEffect(() => {
78
+ if (isOpen) {
79
+ calculatePosition();
80
+ }
81
+ }, [isOpen, calculatePosition]);
82
+
27
83
  // Close on click outside
28
84
  useEffect(() => {
29
- if (triggerOn !== "click") return;
85
+ if (!isOpen || triggerOn !== "click") return;
30
86
 
31
87
  const handleClickOutside = (event: MouseEvent) => {
32
- if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
88
+ const target = event.target as Node;
89
+ const clickedInTrigger = containerRef.current?.contains(target);
90
+ const clickedInPopover = popoverRef.current?.contains(target);
91
+
92
+ if (!clickedInTrigger && !clickedInPopover) {
33
93
  setIsOpen(false);
34
94
  }
35
95
  };
36
96
 
37
97
  document.addEventListener("mousedown", handleClickOutside);
38
98
  return () => document.removeEventListener("mousedown", handleClickOutside);
39
- }, [triggerOn]);
99
+ }, [isOpen, triggerOn]);
40
100
 
41
101
  // Close on escape
42
102
  useEffect(() => {
@@ -50,6 +110,18 @@ export function Popover({
50
110
  return () => document.removeEventListener("keydown", handleEscape);
51
111
  }, []);
52
112
 
113
+ // Close on scroll
114
+ useEffect(() => {
115
+ if (!isOpen) return;
116
+
117
+ const handleScroll = () => {
118
+ setIsOpen(false);
119
+ };
120
+
121
+ window.addEventListener("scroll", handleScroll, true);
122
+ return () => window.removeEventListener("scroll", handleScroll, true);
123
+ }, [isOpen]);
124
+
53
125
  const handleMouseEnter = () => {
54
126
  if (triggerOn !== "hover") return;
55
127
  if (hoverTimeoutRef.current) clearTimeout(hoverTimeoutRef.current);
@@ -63,37 +135,48 @@ export function Popover({
63
135
  }, 150);
64
136
  };
65
137
 
66
- const positionClasses: Record<PopoverPosition, string> = {
67
- top: "bottom-full left-1/2 -translate-x-1/2 mb-2",
68
- bottom: "top-full left-1/2 -translate-x-1/2 mt-2",
69
- left: "right-full top-1/2 -translate-y-1/2 mr-2",
70
- right: "left-full top-1/2 -translate-y-1/2 ml-2",
138
+ // Transform origin based on position
139
+ const transformOrigin: Record<PopoverPosition, string> = {
140
+ top: "bottom center",
141
+ bottom: "top center",
142
+ left: "right center",
143
+ right: "left center",
71
144
  };
72
145
 
73
146
  return (
74
- <div
147
+ <div data-sonance-name="popover"
75
148
  ref={containerRef}
76
149
  className="relative inline-block"
77
150
  onMouseEnter={handleMouseEnter}
78
151
  onMouseLeave={handleMouseLeave}
79
152
  >
80
153
  <div
154
+ ref={triggerRef}
81
155
  onClick={triggerOn === "click" ? () => setIsOpen(!isOpen) : undefined}
82
156
  className="cursor-pointer"
83
157
  >
84
158
  {trigger}
85
159
  </div>
86
- {isOpen && (
160
+ {mounted && isOpen && createPortal(
87
161
  <div
162
+ ref={popoverRef}
88
163
  className={cn(
89
- "absolute z-50 min-w-[200px] bg-card border border-border shadow-lg",
164
+ "fixed z-50 min-w-[200px] bg-card border border-border shadow-lg",
90
165
  "animate-in fade-in zoom-in-95 duration-150",
91
- positionClasses[position],
166
+ position === "left" || position === "right" ? "-translate-y-1/2" : "",
92
167
  className
93
168
  )}
169
+ style={{
170
+ top: portalPosition.top,
171
+ left: portalPosition.left,
172
+ transformOrigin: transformOrigin[position],
173
+ }}
174
+ onMouseEnter={handleMouseEnter}
175
+ onMouseLeave={handleMouseLeave}
94
176
  >
95
177
  {children}
96
- </div>
178
+ </div>,
179
+ document.body
97
180
  )}
98
181
  </div>
99
182
  );
@@ -106,6 +189,6 @@ export function PopoverContent({
106
189
  className?: string;
107
190
  children: React.ReactNode;
108
191
  }) {
109
- return <div className={cn("p-4", className)}>{children}</div>;
192
+ return <div data-sonance-name="popover" className={cn("p-4", className)}>{children}</div>;
110
193
  }
111
194
 
@@ -96,11 +96,11 @@ export const LoadingExample: Story = {
96
96
  render: () => (
97
97
  <div className="space-y-6 w-96">
98
98
  <div>
99
- <h4 className="text-sm font-medium text-foreground mb-2">File Upload</h4>
99
+ <h4 id="loading-example-h4-file-upload" className="text-sm font-medium text-foreground mb-2">File Upload</h4>
100
100
  <Progress value={67} label="document.pdf" showValue />
101
101
  </div>
102
102
  <div>
103
- <h4 className="text-sm font-medium text-foreground mb-2">Installation Progress</h4>
103
+ <h4 id="loading-example-h4-installation-progres" className="text-sm font-medium text-foreground mb-2">Installation Progress</h4>
104
104
  <Progress value={45} label="Installing dependencies..." showValue />
105
105
  </div>
106
106
  </div>
@@ -113,7 +113,7 @@ export const ResponsiveMatrix: Story = {
113
113
  <div className="space-y-8">
114
114
  {/* Mobile */}
115
115
  <div>
116
- <h4 className="text-xs uppercase text-foreground-muted mb-2">Mobile (375px)</h4>
116
+ <h4 id="responsive-matrix-h4-mobile-375px" className="text-xs uppercase text-foreground-muted mb-2">Mobile (375px)</h4>
117
117
  <div className="w-[375px] border border-dashed border-border p-4 space-y-6">
118
118
  <Progress value={65} label="Upload Progress" showValue />
119
119
  <div className="flex justify-center">
@@ -123,7 +123,7 @@ export const ResponsiveMatrix: Story = {
123
123
  </div>
124
124
  {/* Tablet */}
125
125
  <div>
126
- <h4 className="text-xs uppercase text-foreground-muted mb-2">Tablet (768px)</h4>
126
+ <h4 id="responsive-matrix-h4-tablet-768px" className="text-xs uppercase text-foreground-muted mb-2">Tablet (768px)</h4>
127
127
  <div className="w-[768px] border border-dashed border-border p-4">
128
128
  <div className="grid grid-cols-2 gap-8 items-center">
129
129
  <Progress value={45} label="Installation" showValue />
@@ -136,7 +136,7 @@ export const ResponsiveMatrix: Story = {
136
136
  </div>
137
137
  {/* Desktop */}
138
138
  <div>
139
- <h4 className="text-xs uppercase text-foreground-muted mb-2">Desktop (1280px)</h4>
139
+ <h4 id="responsive-matrix-h4-desktop-1280px" className="text-xs uppercase text-foreground-muted mb-2">Desktop (1280px)</h4>
140
140
  <div className="w-[1280px] border border-dashed border-border p-4">
141
141
  <div className="grid grid-cols-4 gap-8 items-center">
142
142
  <Progress value={25} size="sm" showValue />
@@ -23,16 +23,16 @@ export const Progress = forwardRef<HTMLDivElement, ProgressProps>(
23
23
  };
24
24
 
25
25
  return (
26
- <div ref={ref} className={cn("w-full", className)} {...props}>
26
+ <div data-sonance-name="progress" ref={ref} className={cn("w-full", className)} {...props}>
27
27
  {(label || showValue) && (
28
28
  <div className="mb-2 flex items-center justify-between">
29
29
  {label && (
30
- <span className="text-xs font-medium uppercase tracking-widest text-foreground-muted">
30
+ <span id="progress-span-label" className="text-xs font-medium uppercase tracking-widest text-foreground-muted">
31
31
  {label}
32
32
  </span>
33
33
  )}
34
34
  {showValue && (
35
- <span className="text-sm font-medium text-foreground">{Math.round(percentage)}%</span>
35
+ <span id="progress-span-mathroundpercentage" className="text-sm font-medium text-foreground">{Math.round(percentage)}%</span>
36
36
  )}
37
37
  </div>
38
38
  )}
@@ -89,7 +89,7 @@ export function CircularProgress({
89
89
  const strokeDashoffset = circumference - (percentage / 100) * circumference;
90
90
 
91
91
  return (
92
- <div className={cn("relative inline-flex items-center justify-center", className)}>
92
+ <div data-sonance-name="progress" className={cn("relative inline-flex items-center justify-center", className)}>
93
93
  <svg width={resolvedSize} height={resolvedSize} className="-rotate-90">
94
94
  {/* Background circle */}
95
95
  <circle
@@ -116,7 +116,7 @@ export function CircularProgress({
116
116
  />
117
117
  </svg>
118
118
  {showValue && (
119
- <span className="absolute text-xs font-medium text-foreground">
119
+ <span id="circular-progress-span-mathroundpercentage" className="absolute text-xs font-medium text-foreground">
120
120
  {Math.round(percentage)}%
121
121
  </span>
122
122
  )}
@@ -91,7 +91,7 @@ export const WithDisabledOption: Story = {
91
91
  export const PlanSelection: Story = {
92
92
  render: () => (
93
93
  <div className="w-80">
94
- <h3 className="text-sm font-medium text-foreground mb-4">Select a Plan</h3>
94
+ <h3 id="plan-selection-h3-select-a-plan" className="text-sm font-medium text-foreground mb-4">Select a Plan</h3>
95
95
  <RadioGroup defaultValue="pro">
96
96
  <RadioGroupItem
97
97
  value="free"
@@ -118,12 +118,12 @@ export const StateMatrix: Story = {
118
118
  render: () => {
119
119
  const states: RadioGroupItemState[] = ['default', 'hover', 'focus', 'checked', 'disabled'];
120
120
  return (
121
- <div className="space-y-6">
122
- <h3 className="text-sm font-medium text-foreground-muted">RadioGroupItem States</h3>
121
+ <div data-sonance-name="radio-group.stories" className="space-y-6">
122
+ <h3 id="state-matrix-h3-radiogroupitem-state" className="text-sm font-medium text-foreground-muted">RadioGroupItem States</h3>
123
123
  <div className="space-y-4">
124
124
  {states.map((state) => (
125
125
  <div key={state} className="flex items-center gap-4">
126
- <span className="text-xs font-medium text-foreground-muted uppercase w-20">{state}</span>
126
+ <span id="state-matrix-span-state" className="text-xs font-medium text-foreground-muted uppercase w-20">{state}</span>
127
127
  <RadioGroup>
128
128
  <RadioGroupItem
129
129
  value="demo"
@@ -145,7 +145,7 @@ export const ResponsiveMatrix: Story = {
145
145
  <div className="space-y-8">
146
146
  {/* Mobile */}
147
147
  <div>
148
- <h4 className="text-xs uppercase text-foreground-muted mb-2">Mobile (375px)</h4>
148
+ <h4 id="responsive-matrix-h4-mobile-375px" className="text-xs uppercase text-foreground-muted mb-2">Mobile (375px)</h4>
149
149
  <div className="w-[375px] border border-dashed border-border p-4">
150
150
  <RadioGroup defaultValue="standard">
151
151
  <RadioGroupItem value="standard" label="Standard" description="5-7 business days" />
@@ -156,7 +156,7 @@ export const ResponsiveMatrix: Story = {
156
156
  </div>
157
157
  {/* Tablet */}
158
158
  <div>
159
- <h4 className="text-xs uppercase text-foreground-muted mb-2">Tablet (768px)</h4>
159
+ <h4 id="responsive-matrix-h4-tablet-768px" className="text-xs uppercase text-foreground-muted mb-2">Tablet (768px)</h4>
160
160
  <div className="w-[768px] border border-dashed border-border p-4">
161
161
  <div className="grid grid-cols-2 gap-8">
162
162
  <RadioGroup defaultValue="small">
@@ -173,7 +173,7 @@ export const ResponsiveMatrix: Story = {
173
173
  </div>
174
174
  {/* Desktop */}
175
175
  <div>
176
- <h4 className="text-xs uppercase text-foreground-muted mb-2">Desktop (1280px)</h4>
176
+ <h4 id="responsive-matrix-h4-desktop-1280px" className="text-xs uppercase text-foreground-muted mb-2">Desktop (1280px)</h4>
177
177
  <div className="w-[1280px] border border-dashed border-border p-4">
178
178
  <RadioGroup defaultValue="pro" orientation="horizontal" className="flex gap-8">
179
179
  <RadioGroupItem value="free" label="Free" description="Basic features" />
@@ -93,7 +93,7 @@ export const RadioGroupItem = forwardRef<HTMLInputElement, RadioGroupItemProps>(
93
93
  const isDisabled = disabled || state === "disabled";
94
94
 
95
95
  return (
96
- <div className="flex items-start gap-3">
96
+ <div data-sonance-name="radio-group" className="flex items-start gap-3">
97
97
  <div className="relative flex items-center justify-center">
98
98
  <input
99
99
  type="radio"
@@ -115,7 +115,7 @@ export const RadioGroupItem = forwardRef<HTMLInputElement, RadioGroupItemProps>(
115
115
  )}
116
116
  {...props}
117
117
  />
118
- <div
118
+ <div data-sonance-name="radio-group"
119
119
  className={cn(
120
120
  "pointer-events-none absolute h-2.5 w-2.5 rounded-full bg-primary",
121
121
  "scale-0 transition-transform duration-150",
@@ -134,7 +134,7 @@ export const RadioGroupItem = forwardRef<HTMLInputElement, RadioGroupItemProps>(
134
134
  </label>
135
135
  )}
136
136
  {description && (
137
- <p className="text-xs text-foreground-muted">{description}</p>
137
+ <p id="p-description" className="text-xs text-foreground-muted">{description}</p>
138
138
  )}
139
139
  </div>
140
140
  )}
@@ -38,15 +38,15 @@ export const Default: Story = {
38
38
  render: () => {
39
39
  const [range, setRange] = useState<DateRange>({ start: undefined, end: undefined });
40
40
  return (
41
- <div className="space-y-4">
41
+ <div data-sonance-name="range-calendar.stories" className="space-y-4">
42
42
  <RangeCalendar
43
43
  value={range}
44
44
  onValueChange={setRange}
45
45
  />
46
46
  <div className="text-sm text-foreground-muted">
47
- {range.start && <span>Start: {range.start.toLocaleDateString()}</span>}
48
- {range.start && range.end && <span> | </span>}
49
- {range.end && <span>End: {range.end.toLocaleDateString()}</span>}
47
+ {range.start && <span id="default-span-start-rangestarttolo">Start: {range.start.toLocaleDateString()}</span>}
48
+ {range.start && range.end && <span id="default-span"> | </span>}
49
+ {range.end && <span id="default-span-end-rangeendtolocale">End: {range.end.toLocaleDateString()}</span>}
50
50
  </div>
51
51
  </div>
52
52
  );
@@ -105,7 +105,7 @@ export const WithMinMaxDates: Story = {
105
105
  const today = new Date();
106
106
  return (
107
107
  <div className="space-y-4">
108
- <p className="text-sm text-foreground-muted">
108
+ <p id="with-min-max-dates-p-selection-limited-to" className="text-sm text-foreground-muted">
109
109
  Selection limited to next 60 days
110
110
  </p>
111
111
  <RangeCalendar
@@ -129,7 +129,7 @@ export const DisabledWeekends: Story = {
129
129
  };
130
130
  return (
131
131
  <div className="space-y-4">
132
- <p className="text-sm text-foreground-muted">
132
+ <p id="disabled-weekends-p-weekends-are-disable" className="text-sm text-foreground-muted">
133
133
  Weekends are disabled (business days only)
134
134
  </p>
135
135
  <RangeCalendar
@@ -170,15 +170,15 @@ export const HotelBookingExample: Story = {
170
170
  <div className="flex items-center gap-4 text-sm">
171
171
  <div className="flex items-center gap-2">
172
172
  <div className="w-4 h-4 bg-primary rounded-sm" />
173
- <span>Selected</span>
173
+ <span id="hotel-booking-example-span-selected">Selected</span>
174
174
  </div>
175
175
  <div className="flex items-center gap-2">
176
176
  <div className="w-4 h-4 bg-primary/20 rounded-sm" />
177
- <span>In Range</span>
177
+ <span id="hotel-booking-example-span-in-range">In Range</span>
178
178
  </div>
179
179
  <div className="flex items-center gap-2">
180
180
  <div className="w-4 h-4 bg-secondary text-foreground-subtle text-xs flex items-center justify-center">x</div>
181
- <span>Unavailable</span>
181
+ <span id="hotel-booking-example-span-unavailable">Unavailable</span>
182
182
  </div>
183
183
  </div>
184
184
  <RangeCalendar
@@ -191,8 +191,8 @@ export const HotelBookingExample: Story = {
191
191
  {nights > 0 && (
192
192
  <div className="p-4 border border-border rounded-sm">
193
193
  <div className="flex justify-between">
194
- <span className="text-foreground-muted">Duration</span>
195
- <span className="font-medium">{nights} night{nights !== 1 ? 's' : ''}</span>
194
+ <span id="hotel-booking-example-span-duration" className="text-foreground-muted">Duration</span>
195
+ <span id="hotel-booking-example-span-nights-nightnights-1" className="font-medium">{nights} night{nights !== 1 ? 's' : ''}</span>
196
196
  </div>
197
197
  </div>
198
198
  )}
@@ -239,7 +239,7 @@ export const ReportDateRangeExample: Story = {
239
239
  maxDate={today}
240
240
  />
241
241
  {range.start && range.end && (
242
- <p className="text-sm">
242
+ <p id="report-date-range-example-p-selected-rangestartt" className="text-sm">
243
243
  Selected: {range.start.toLocaleDateString()} - {range.end.toLocaleDateString()}
244
244
  </p>
245
245
  )}
@@ -258,7 +258,7 @@ export const AllConfigurations: Story = {
258
258
  return (
259
259
  <div className="space-y-8">
260
260
  <div>
261
- <h4 className="text-sm font-medium mb-2">Single Month</h4>
261
+ <h4 id="all-configurations-h4-single-month" className="text-sm font-medium mb-2">Single Month</h4>
262
262
  <RangeCalendar
263
263
  value={range1}
264
264
  onValueChange={setRange1}
@@ -266,7 +266,7 @@ export const AllConfigurations: Story = {
266
266
  />
267
267
  </div>
268
268
  <div>
269
- <h4 className="text-sm font-medium mb-2">Two Months (Default)</h4>
269
+ <h4 id="all-configurations-h4-two-months-default" className="text-sm font-medium mb-2">Two Months (Default)</h4>
270
270
  <RangeCalendar
271
271
  value={range2}
272
272
  onValueChange={setRange2}
@@ -274,7 +274,7 @@ export const AllConfigurations: Story = {
274
274
  />
275
275
  </div>
276
276
  <div>
277
- <h4 className="text-sm font-medium mb-2">Three Months</h4>
277
+ <h4 id="all-configurations-h4-three-months" className="text-sm font-medium mb-2">Three Months</h4>
278
278
  <RangeCalendar
279
279
  value={range3}
280
280
  onValueChange={setRange3}
@@ -297,7 +297,7 @@ export const ResponsiveMatrix: Story = {
297
297
  <div className="space-y-8">
298
298
  {/* Mobile */}
299
299
  <div>
300
- <h4 className="text-xs uppercase text-foreground-muted mb-2">Mobile (375px)</h4>
300
+ <h4 id="responsive-matrix-h4-mobile-375px" className="text-xs uppercase text-foreground-muted mb-2">Mobile (375px)</h4>
301
301
  <div className="w-[375px] border border-dashed border-border p-4 overflow-x-auto">
302
302
  <RangeCalendar
303
303
  value={mobile}
@@ -308,7 +308,7 @@ export const ResponsiveMatrix: Story = {
308
308
  </div>
309
309
  {/* Tablet */}
310
310
  <div>
311
- <h4 className="text-xs uppercase text-foreground-muted mb-2">Tablet (768px)</h4>
311
+ <h4 id="responsive-matrix-h4-tablet-768px" className="text-xs uppercase text-foreground-muted mb-2">Tablet (768px)</h4>
312
312
  <div className="w-[768px] border border-dashed border-border p-4">
313
313
  <RangeCalendar
314
314
  value={tablet}
@@ -319,7 +319,7 @@ export const ResponsiveMatrix: Story = {
319
319
  </div>
320
320
  {/* Desktop */}
321
321
  <div>
322
- <h4 className="text-xs uppercase text-foreground-muted mb-2">Desktop (1280px)</h4>
322
+ <h4 id="responsive-matrix-h4-desktop-1280px" className="text-xs uppercase text-foreground-muted mb-2">Desktop (1280px)</h4>
323
323
  <div className="w-[1280px] border border-dashed border-border p-4">
324
324
  <RangeCalendar
325
325
  value={desktop}