sonance-brand-mcp 1.3.109 → 1.3.111
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/dist/assets/api/sonance-ai-edit/route.ts +30 -7
- package/dist/assets/api/sonance-vision-apply/route.ts +33 -8
- package/dist/assets/api/sonance-vision-edit/route.ts +33 -8
- package/dist/assets/components/alert.tsx +35 -9
- package/dist/assets/components/badge.tsx +49 -20
- package/dist/assets/components/button.tsx +29 -20
- package/dist/assets/components/card.tsx +87 -33
- package/dist/assets/components/checkbox.tsx +36 -12
- package/dist/assets/components/dialog.tsx +73 -30
- package/dist/assets/components/dropdown-menu.tsx +57 -20
- package/dist/assets/components/input.tsx +35 -14
- package/dist/assets/components/pagination.tsx +86 -35
- package/dist/assets/components/popover.tsx +80 -36
- package/dist/assets/components/radio-group.tsx +40 -12
- package/dist/assets/components/select.tsx +62 -26
- package/dist/assets/components/switch.tsx +41 -13
- package/dist/assets/components/tabs.tsx +32 -12
- package/dist/assets/components/tooltip.tsx +34 -5
- package/dist/assets/dev-tools/SonanceDevTools.tsx +441 -365
- package/dist/assets/dev-tools/components/ChatHistory.tsx +141 -0
- package/dist/assets/dev-tools/components/ChatInterface.tsx +402 -294
- package/dist/assets/dev-tools/components/ChatTabBar.tsx +82 -0
- package/dist/assets/dev-tools/components/InlineDiffPreview.tsx +204 -0
- package/dist/assets/dev-tools/components/InspectorOverlay.tsx +12 -9
- package/dist/assets/dev-tools/components/PropertiesPanel.tsx +695 -0
- package/dist/assets/dev-tools/components/VisionModeBorder.tsx +16 -7
- package/dist/assets/dev-tools/constants.ts +38 -6
- package/dist/assets/dev-tools/hooks/useComputedStyles.ts +365 -0
- package/dist/assets/dev-tools/index.ts +3 -0
- package/dist/assets/dev-tools/panels/AnalysisPanel.tsx +32 -32
- package/dist/assets/dev-tools/panels/ComponentsPanel.tsx +277 -127
- package/dist/assets/dev-tools/types.ts +51 -2
- package/dist/index.js +22 -3
- package/package.json +2 -1
|
@@ -1,9 +1,33 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
1
2
|
import { cn } from "@/lib/utils";
|
|
2
3
|
import { forwardRef } from "react";
|
|
3
4
|
|
|
4
5
|
export type InputState = "default" | "hover" | "focus" | "error" | "disabled";
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
const inputVariants = cva(
|
|
8
|
+
"w-full border border-input-border bg-input text-foreground placeholder:text-input-placeholder transition-all duration-200 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-secondary",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
size: {
|
|
12
|
+
xs: "h-7 px-2.5 text-xs rounded-md",
|
|
13
|
+
sm: "h-8 px-3 text-sm rounded-lg",
|
|
14
|
+
md: "h-9 px-3.5 text-sm rounded-lg",
|
|
15
|
+
lg: "h-10 px-4 text-sm rounded-xl",
|
|
16
|
+
},
|
|
17
|
+
inputVariant: {
|
|
18
|
+
default: "hover:border-border-hover focus:border-input-focus focus:ring-2 focus:ring-primary/10",
|
|
19
|
+
glass: "bg-input/80 backdrop-blur-sm hover:border-border-hover focus:border-input-focus focus:ring-2 focus:ring-primary/20",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
size: "sm",
|
|
24
|
+
inputVariant: "default",
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size">,
|
|
30
|
+
VariantProps<typeof inputVariants> {
|
|
7
31
|
label?: string;
|
|
8
32
|
error?: string;
|
|
9
33
|
/** Visual state for Storybook/Figma documentation */
|
|
@@ -17,9 +41,9 @@ const getStateStyles = (state?: InputState) => {
|
|
|
17
41
|
if (!state || state === "default") return "";
|
|
18
42
|
|
|
19
43
|
const stateMap: Record<string, string> = {
|
|
20
|
-
hover: "border-border-hover
|
|
21
|
-
focus: "border-input-focus",
|
|
22
|
-
error: "border-error",
|
|
44
|
+
hover: "border-border-hover",
|
|
45
|
+
focus: "border-input-focus ring-2 ring-primary/10",
|
|
46
|
+
error: "border-error ring-2 ring-error/10",
|
|
23
47
|
disabled: "opacity-50 cursor-not-allowed bg-secondary",
|
|
24
48
|
};
|
|
25
49
|
|
|
@@ -27,14 +51,14 @@ const getStateStyles = (state?: InputState) => {
|
|
|
27
51
|
};
|
|
28
52
|
|
|
29
53
|
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
30
|
-
({ className, label, error, state, disabled, style, wrapperStyle, ...props }, ref) => {
|
|
54
|
+
({ className, label, error, state, disabled, style, wrapperStyle, size, inputVariant, ...props }, ref) => {
|
|
31
55
|
const isDisabled = disabled || state === "disabled";
|
|
32
56
|
const hasError = error || state === "error";
|
|
33
57
|
|
|
34
58
|
return (
|
|
35
59
|
<div data-sonance-name="input" className="w-full" style={wrapperStyle}>
|
|
36
60
|
{label && (
|
|
37
|
-
<label className="mb-
|
|
61
|
+
<label className="mb-1.5 block text-[11px] font-medium uppercase tracking-wide text-foreground-muted">
|
|
38
62
|
{label}
|
|
39
63
|
</label>
|
|
40
64
|
)}
|
|
@@ -43,19 +67,14 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
43
67
|
disabled={isDisabled}
|
|
44
68
|
style={style}
|
|
45
69
|
className={cn(
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
"transition-colors duration-200",
|
|
49
|
-
"hover:border-border-hover",
|
|
50
|
-
"focus:border-input-focus focus:outline-none",
|
|
51
|
-
"disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-secondary",
|
|
52
|
-
hasError && "border-error",
|
|
70
|
+
inputVariants({ size, inputVariant }),
|
|
71
|
+
hasError && "border-error ring-2 ring-error/10",
|
|
53
72
|
getStateStyles(state),
|
|
54
73
|
className
|
|
55
74
|
)} data-sonance-name="input"
|
|
56
75
|
{...props}
|
|
57
76
|
/>
|
|
58
|
-
{error && <p id="input-p-error" className="mt-1 text-
|
|
77
|
+
{error && <p id="input-p-error" className="mt-1 text-xs text-error">{error}</p>}
|
|
59
78
|
</div>
|
|
60
79
|
);
|
|
61
80
|
}
|
|
@@ -63,3 +82,5 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
63
82
|
|
|
64
83
|
Input.displayName = "Input";
|
|
65
84
|
|
|
85
|
+
export { inputVariants };
|
|
86
|
+
|
|
@@ -1,18 +1,36 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { forwardRef } from "react";
|
|
3
|
+
import { forwardRef, createContext, useContext, useMemo } from "react";
|
|
4
4
|
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
6
|
import { cn } from "@/lib/utils";
|
|
6
7
|
|
|
7
8
|
export type PaginationButtonState = "default" | "hover" | "focus" | "active" | "disabled";
|
|
8
9
|
|
|
10
|
+
const paginationButtonVariants = cva(
|
|
11
|
+
"flex items-center justify-center font-medium border transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:ring-offset-2",
|
|
12
|
+
{
|
|
13
|
+
variants: {
|
|
14
|
+
size: {
|
|
15
|
+
xs: "h-6 min-w-6 px-1.5 text-[10px] rounded-md",
|
|
16
|
+
sm: "h-7 min-w-7 px-2 text-xs rounded-lg",
|
|
17
|
+
md: "h-8 min-w-8 px-2.5 text-sm rounded-lg",
|
|
18
|
+
lg: "h-9 min-w-9 px-3 text-sm rounded-xl",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
size: "sm",
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
9
27
|
// State styles for Storybook/Figma visualization
|
|
10
28
|
const getButtonStateStyles = (state?: PaginationButtonState) => {
|
|
11
29
|
if (!state || state === "default") return "";
|
|
12
30
|
|
|
13
31
|
const stateMap: Record<string, string> = {
|
|
14
32
|
hover: "bg-secondary-hover",
|
|
15
|
-
focus: "ring-2 ring-
|
|
33
|
+
focus: "ring-2 ring-primary/20 ring-offset-2",
|
|
16
34
|
active: "bg-primary text-primary-foreground border-primary",
|
|
17
35
|
disabled: "opacity-50 cursor-not-allowed",
|
|
18
36
|
};
|
|
@@ -20,6 +38,22 @@ const getButtonStateStyles = (state?: PaginationButtonState) => {
|
|
|
20
38
|
return stateMap[state] || "";
|
|
21
39
|
};
|
|
22
40
|
|
|
41
|
+
const PaginationSizeContext = createContext<"xs" | "sm" | "md" | "lg">("sm");
|
|
42
|
+
|
|
43
|
+
const ellipsisSizes = {
|
|
44
|
+
xs: "h-6 w-6 text-[10px]",
|
|
45
|
+
sm: "h-7 w-7 text-xs",
|
|
46
|
+
md: "h-8 w-8 text-sm",
|
|
47
|
+
lg: "h-9 w-9 text-sm",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const iconSizes = {
|
|
51
|
+
xs: "h-3 w-3",
|
|
52
|
+
sm: "h-3.5 w-3.5",
|
|
53
|
+
md: "h-4 w-4",
|
|
54
|
+
lg: "h-4 w-4",
|
|
55
|
+
};
|
|
56
|
+
|
|
23
57
|
interface PaginationProps {
|
|
24
58
|
currentPage: number;
|
|
25
59
|
totalPages: number;
|
|
@@ -27,6 +61,8 @@ interface PaginationProps {
|
|
|
27
61
|
siblingCount?: number;
|
|
28
62
|
showFirstLast?: boolean;
|
|
29
63
|
className?: string;
|
|
64
|
+
/** Size variant for pagination buttons */
|
|
65
|
+
size?: "xs" | "sm" | "md" | "lg";
|
|
30
66
|
}
|
|
31
67
|
|
|
32
68
|
function generatePaginationRange(
|
|
@@ -76,6 +112,7 @@ export function Pagination({
|
|
|
76
112
|
siblingCount = 1,
|
|
77
113
|
showFirstLast = true,
|
|
78
114
|
className,
|
|
115
|
+
size = "sm",
|
|
79
116
|
}: PaginationProps) {
|
|
80
117
|
const pages = generatePaginationRange(currentPage, totalPages, siblingCount);
|
|
81
118
|
|
|
@@ -83,18 +120,19 @@ export function Pagination({
|
|
|
83
120
|
const canGoNext = currentPage < totalPages;
|
|
84
121
|
|
|
85
122
|
return (
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
123
|
+
<PaginationSizeContext.Provider value={size}>
|
|
124
|
+
<nav data-sonance-name="pagination"
|
|
125
|
+
role="navigation"
|
|
126
|
+
aria-label="Pagination"
|
|
127
|
+
className={cn("flex items-center gap-1", className)}
|
|
128
|
+
>
|
|
91
129
|
{/* Previous button */}
|
|
92
130
|
<PaginationButton
|
|
93
131
|
onClick={() => canGoPrevious && onPageChange(currentPage - 1)}
|
|
94
132
|
disabled={!canGoPrevious}
|
|
95
133
|
aria-label="Go to previous page"
|
|
96
134
|
>
|
|
97
|
-
<ChevronLeft className=
|
|
135
|
+
<ChevronLeft className={iconSizes[size]} />
|
|
98
136
|
</PaginationButton>
|
|
99
137
|
|
|
100
138
|
{/* Page numbers */}
|
|
@@ -103,9 +141,9 @@ export function Pagination({
|
|
|
103
141
|
return (
|
|
104
142
|
<span id="pagination-span" data-sonance-name="pagination"
|
|
105
143
|
key={`ellipsis-${index}`}
|
|
106
|
-
className="flex
|
|
144
|
+
className={cn("flex items-center justify-center text-foreground-muted", ellipsisSizes[size])}
|
|
107
145
|
>
|
|
108
|
-
<MoreHorizontal className=
|
|
146
|
+
<MoreHorizontal className={iconSizes[size]} />
|
|
109
147
|
</span>
|
|
110
148
|
);
|
|
111
149
|
}
|
|
@@ -129,9 +167,10 @@ export function Pagination({
|
|
|
129
167
|
disabled={!canGoNext}
|
|
130
168
|
aria-label="Go to next page"
|
|
131
169
|
>
|
|
132
|
-
<ChevronRight className=
|
|
170
|
+
<ChevronRight className={iconSizes[size]} />
|
|
133
171
|
</PaginationButton>
|
|
134
|
-
|
|
172
|
+
</nav>
|
|
173
|
+
</PaginationSizeContext.Provider>
|
|
135
174
|
);
|
|
136
175
|
}
|
|
137
176
|
|
|
@@ -143,6 +182,7 @@ interface PaginationButtonProps extends React.ButtonHTMLAttributes<HTMLButtonEle
|
|
|
143
182
|
|
|
144
183
|
const PaginationButton = forwardRef<HTMLButtonElement, PaginationButtonProps>(
|
|
145
184
|
({ className, active, disabled, state, children, ...props }, ref) => {
|
|
185
|
+
const size = useContext(PaginationSizeContext);
|
|
146
186
|
const isActive = active || state === "active";
|
|
147
187
|
const isDisabled = disabled || state === "disabled";
|
|
148
188
|
|
|
@@ -152,9 +192,7 @@ const PaginationButton = forwardRef<HTMLButtonElement, PaginationButtonProps>(
|
|
|
152
192
|
type="button"
|
|
153
193
|
disabled={isDisabled}
|
|
154
194
|
className={cn(
|
|
155
|
-
|
|
156
|
-
"border transition-colors",
|
|
157
|
-
"focus:outline-none focus:ring-2 focus:ring-border-focus focus:ring-offset-2",
|
|
195
|
+
paginationButtonVariants({ size }),
|
|
158
196
|
isActive
|
|
159
197
|
? "bg-primary text-primary-foreground border-primary"
|
|
160
198
|
: isDisabled
|
|
@@ -179,37 +217,50 @@ interface CompactPaginationProps {
|
|
|
179
217
|
totalPages: number;
|
|
180
218
|
onPageChange: (page: number) => void;
|
|
181
219
|
className?: string;
|
|
220
|
+
/** Size variant for compact pagination */
|
|
221
|
+
size?: "xs" | "sm" | "md" | "lg";
|
|
182
222
|
}
|
|
183
223
|
|
|
224
|
+
const compactTextSizes = {
|
|
225
|
+
xs: "text-[10px]",
|
|
226
|
+
sm: "text-xs",
|
|
227
|
+
md: "text-sm",
|
|
228
|
+
lg: "text-sm",
|
|
229
|
+
};
|
|
230
|
+
|
|
184
231
|
export function CompactPagination({
|
|
185
232
|
currentPage,
|
|
186
233
|
totalPages,
|
|
187
234
|
onPageChange,
|
|
188
235
|
className,
|
|
236
|
+
size = "sm",
|
|
189
237
|
}: CompactPaginationProps) {
|
|
190
238
|
return (
|
|
191
|
-
<
|
|
192
|
-
<
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
239
|
+
<PaginationSizeContext.Provider value={size}>
|
|
240
|
+
<div className={cn("flex items-center gap-3", className)}>
|
|
241
|
+
<PaginationButton
|
|
242
|
+
onClick={() => currentPage > 1 && onPageChange(currentPage - 1)}
|
|
243
|
+
disabled={currentPage <= 1}
|
|
244
|
+
aria-label="Go to previous page"
|
|
245
|
+
>
|
|
246
|
+
<ChevronLeft className={iconSizes[size]} />
|
|
247
|
+
</PaginationButton>
|
|
199
248
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
249
|
+
<span id="compact-pagination-span" className={cn("text-foreground-secondary", compactTextSizes[size])}>
|
|
250
|
+
Page <span id="compact-pagination-span-currentpage" className="font-medium text-foreground">{currentPage}</span> of{" "}
|
|
251
|
+
<span id="compact-pagination-span-totalpages" className="font-medium text-foreground">{totalPages}</span>
|
|
252
|
+
</span>
|
|
204
253
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
254
|
+
<PaginationButton
|
|
255
|
+
onClick={() => currentPage < totalPages && onPageChange(currentPage + 1)}
|
|
256
|
+
disabled={currentPage >= totalPages}
|
|
257
|
+
aria-label="Go to next page"
|
|
258
|
+
>
|
|
259
|
+
<ChevronRight className={iconSizes[size]} />
|
|
260
|
+
</PaginationButton>
|
|
261
|
+
</div>
|
|
262
|
+
</PaginationSizeContext.Provider>
|
|
213
263
|
);
|
|
214
264
|
}
|
|
215
265
|
|
|
266
|
+
export { paginationButtonVariants };
|
|
@@ -1,12 +1,44 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useState, useRef, useEffect, useCallback } from "react";
|
|
3
|
+
import { useState, useRef, useEffect, useCallback, createContext, useContext } from "react";
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
6
|
import { cn } from "@/lib/utils";
|
|
6
7
|
|
|
7
8
|
type PopoverPosition = "top" | "bottom" | "left" | "right";
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
type PopoverSize = "compact" | "default" | "spacious";
|
|
10
|
+
type PopoverVariant = "default" | "glass";
|
|
11
|
+
|
|
12
|
+
const PopoverContext = createContext<{ size: PopoverSize }>({ size: "default" });
|
|
13
|
+
|
|
14
|
+
const popoverVariants = cva(
|
|
15
|
+
"fixed z-50 min-w-[200px] border shadow-xl animate-in fade-in zoom-in-95 duration-150",
|
|
16
|
+
{
|
|
17
|
+
variants: {
|
|
18
|
+
size: {
|
|
19
|
+
compact: "rounded-lg",
|
|
20
|
+
default: "rounded-xl",
|
|
21
|
+
spacious: "rounded-2xl",
|
|
22
|
+
},
|
|
23
|
+
popoverVariant: {
|
|
24
|
+
default: "bg-card border-border",
|
|
25
|
+
glass: "bg-card/95 border-border/50 backdrop-blur-xl",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
defaultVariants: {
|
|
29
|
+
size: "default",
|
|
30
|
+
popoverVariant: "glass",
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const popoverContentPadding = {
|
|
36
|
+
compact: "p-3",
|
|
37
|
+
default: "p-4",
|
|
38
|
+
spacious: "p-5",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
interface PopoverProps extends VariantProps<typeof popoverVariants> {
|
|
10
42
|
trigger: React.ReactNode;
|
|
11
43
|
children: React.ReactNode;
|
|
12
44
|
position?: PopoverPosition;
|
|
@@ -25,6 +57,8 @@ export function Popover({
|
|
|
25
57
|
position = "bottom",
|
|
26
58
|
triggerOn = "click",
|
|
27
59
|
className,
|
|
60
|
+
size = "default",
|
|
61
|
+
popoverVariant = "glass",
|
|
28
62
|
}: PopoverProps) {
|
|
29
63
|
const [isOpen, setIsOpen] = useState(false);
|
|
30
64
|
const [portalPosition, setPortalPosition] = useState<PortalPosition>({ top: 0, left: 0 });
|
|
@@ -144,41 +178,42 @@ export function Popover({
|
|
|
144
178
|
};
|
|
145
179
|
|
|
146
180
|
return (
|
|
147
|
-
<
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
<div
|
|
154
|
-
ref={triggerRef}
|
|
155
|
-
onClick={triggerOn === "click" ? () => setIsOpen(!isOpen) : undefined}
|
|
156
|
-
className="cursor-pointer"
|
|
181
|
+
<PopoverContext.Provider value={{ size }}>
|
|
182
|
+
<div data-sonance-name="popover"
|
|
183
|
+
ref={containerRef}
|
|
184
|
+
className="relative inline-block"
|
|
185
|
+
onMouseEnter={handleMouseEnter}
|
|
186
|
+
onMouseLeave={handleMouseLeave}
|
|
157
187
|
>
|
|
158
|
-
{trigger}
|
|
159
|
-
</div>
|
|
160
|
-
{mounted && isOpen && createPortal(
|
|
161
188
|
<div
|
|
162
|
-
ref={
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
"animate-in fade-in zoom-in-95 duration-150",
|
|
166
|
-
position === "left" || position === "right" ? "-translate-y-1/2" : "",
|
|
167
|
-
className
|
|
168
|
-
)}
|
|
169
|
-
style={{
|
|
170
|
-
top: portalPosition.top,
|
|
171
|
-
left: portalPosition.left,
|
|
172
|
-
transformOrigin: transformOrigin[position],
|
|
173
|
-
}}
|
|
174
|
-
onMouseEnter={handleMouseEnter}
|
|
175
|
-
onMouseLeave={handleMouseLeave}
|
|
189
|
+
ref={triggerRef}
|
|
190
|
+
onClick={triggerOn === "click" ? () => setIsOpen(!isOpen) : undefined}
|
|
191
|
+
className="cursor-pointer"
|
|
176
192
|
>
|
|
177
|
-
{
|
|
178
|
-
</div
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
193
|
+
{trigger}
|
|
194
|
+
</div>
|
|
195
|
+
{mounted && isOpen && createPortal(
|
|
196
|
+
<div
|
|
197
|
+
ref={popoverRef}
|
|
198
|
+
className={cn(
|
|
199
|
+
popoverVariants({ size, popoverVariant }),
|
|
200
|
+
position === "left" || position === "right" ? "-translate-y-1/2" : "",
|
|
201
|
+
className
|
|
202
|
+
)}
|
|
203
|
+
style={{
|
|
204
|
+
top: portalPosition.top,
|
|
205
|
+
left: portalPosition.left,
|
|
206
|
+
transformOrigin: transformOrigin[position],
|
|
207
|
+
}}
|
|
208
|
+
onMouseEnter={handleMouseEnter}
|
|
209
|
+
onMouseLeave={handleMouseLeave}
|
|
210
|
+
>
|
|
211
|
+
{children}
|
|
212
|
+
</div>,
|
|
213
|
+
document.body
|
|
214
|
+
)}
|
|
215
|
+
</div>
|
|
216
|
+
</PopoverContext.Provider>
|
|
182
217
|
);
|
|
183
218
|
}
|
|
184
219
|
|
|
@@ -189,6 +224,15 @@ export function PopoverContent({
|
|
|
189
224
|
className?: string;
|
|
190
225
|
children: React.ReactNode;
|
|
191
226
|
}) {
|
|
192
|
-
|
|
227
|
+
const { size } = useContext(PopoverContext);
|
|
228
|
+
return (
|
|
229
|
+
<div
|
|
230
|
+
data-sonance-name="popover"
|
|
231
|
+
className={cn(popoverContentPadding[size], className)}
|
|
232
|
+
>
|
|
233
|
+
{children}
|
|
234
|
+
</div>
|
|
235
|
+
);
|
|
193
236
|
}
|
|
194
237
|
|
|
238
|
+
export { popoverVariants };
|
|
@@ -1,17 +1,42 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { forwardRef, createContext, useContext, useState, useId } from "react";
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
5
|
import { cn } from "@/lib/utils";
|
|
5
6
|
|
|
6
7
|
export type RadioGroupItemState = "default" | "hover" | "focus" | "checked" | "disabled";
|
|
7
8
|
|
|
9
|
+
const radioItemVariants = cva(
|
|
10
|
+
"peer shrink-0 appearance-none rounded-full border border-border bg-input transition-all duration-150 hover:border-border-hover checked:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 focus:ring-offset-2 focus:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50",
|
|
11
|
+
{
|
|
12
|
+
variants: {
|
|
13
|
+
size: {
|
|
14
|
+
xs: "h-3.5 w-3.5",
|
|
15
|
+
sm: "h-4 w-4",
|
|
16
|
+
md: "h-5 w-5",
|
|
17
|
+
lg: "h-6 w-6",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultVariants: {
|
|
21
|
+
size: "sm",
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const radioIndicatorSizes = {
|
|
27
|
+
xs: "h-1.5 w-1.5",
|
|
28
|
+
sm: "h-2 w-2",
|
|
29
|
+
md: "h-2.5 w-2.5",
|
|
30
|
+
lg: "h-3 w-3",
|
|
31
|
+
};
|
|
32
|
+
|
|
8
33
|
// State styles for Storybook/Figma visualization
|
|
9
34
|
const getStateStyles = (state?: RadioGroupItemState) => {
|
|
10
35
|
if (!state || state === "default") return "";
|
|
11
36
|
|
|
12
37
|
const stateMap: Record<string, string> = {
|
|
13
38
|
hover: "border-border-hover",
|
|
14
|
-
focus: "ring-2 ring-
|
|
39
|
+
focus: "ring-2 ring-primary/20 ring-offset-2 ring-offset-background",
|
|
15
40
|
checked: "border-primary",
|
|
16
41
|
disabled: "opacity-50 cursor-not-allowed",
|
|
17
42
|
};
|
|
@@ -23,6 +48,7 @@ interface RadioGroupContextValue {
|
|
|
23
48
|
value: string;
|
|
24
49
|
onChange: (value: string) => void;
|
|
25
50
|
name: string;
|
|
51
|
+
size: "xs" | "sm" | "md" | "lg";
|
|
26
52
|
}
|
|
27
53
|
|
|
28
54
|
const RadioGroupContext = createContext<RadioGroupContextValue | null>(null);
|
|
@@ -35,6 +61,8 @@ interface RadioGroupProps {
|
|
|
35
61
|
className?: string;
|
|
36
62
|
children: React.ReactNode;
|
|
37
63
|
orientation?: "horizontal" | "vertical";
|
|
64
|
+
/** Size variant for radio items */
|
|
65
|
+
size?: "xs" | "sm" | "md" | "lg";
|
|
38
66
|
}
|
|
39
67
|
|
|
40
68
|
export function RadioGroup({
|
|
@@ -45,6 +73,7 @@ export function RadioGroup({
|
|
|
45
73
|
className,
|
|
46
74
|
children,
|
|
47
75
|
orientation = "vertical",
|
|
76
|
+
size = "sm",
|
|
48
77
|
}: RadioGroupProps) {
|
|
49
78
|
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
50
79
|
const value = controlledValue ?? internalValue;
|
|
@@ -57,12 +86,12 @@ export function RadioGroup({
|
|
|
57
86
|
};
|
|
58
87
|
|
|
59
88
|
return (
|
|
60
|
-
<RadioGroupContext.Provider value={{ value, onChange, name: groupName }}>
|
|
89
|
+
<RadioGroupContext.Provider value={{ value, onChange, name: groupName, size }}>
|
|
61
90
|
<div
|
|
62
91
|
role="radiogroup"
|
|
63
92
|
className={cn(
|
|
64
93
|
"flex",
|
|
65
|
-
orientation === "vertical" ? "flex-col gap-
|
|
94
|
+
orientation === "vertical" ? "flex-col gap-2.5" : "flex-row gap-5",
|
|
66
95
|
className
|
|
67
96
|
)}
|
|
68
97
|
>
|
|
@@ -87,13 +116,13 @@ export const RadioGroupItem = forwardRef<HTMLInputElement, RadioGroupItemProps>(
|
|
|
87
116
|
throw new Error("RadioGroupItem must be used within a RadioGroup");
|
|
88
117
|
}
|
|
89
118
|
|
|
90
|
-
const { value: groupValue, onChange, name } = context;
|
|
119
|
+
const { value: groupValue, onChange, name, size } = context;
|
|
91
120
|
const inputId = id || `radio-${value}`;
|
|
92
121
|
const isChecked = groupValue === value || state === "checked";
|
|
93
122
|
const isDisabled = disabled || state === "disabled";
|
|
94
123
|
|
|
95
124
|
return (
|
|
96
|
-
<div data-sonance-name="radio-group" className="flex items-start gap-
|
|
125
|
+
<div data-sonance-name="radio-group" className="flex items-start gap-2.5">
|
|
97
126
|
<div className="relative flex items-center justify-center">
|
|
98
127
|
<input
|
|
99
128
|
type="radio"
|
|
@@ -105,11 +134,7 @@ export const RadioGroupItem = forwardRef<HTMLInputElement, RadioGroupItemProps>(
|
|
|
105
134
|
onChange={() => onChange(value)}
|
|
106
135
|
disabled={isDisabled}
|
|
107
136
|
className={cn(
|
|
108
|
-
|
|
109
|
-
"checked:border-primary",
|
|
110
|
-
"focus:outline-none focus:ring-2 focus:ring-border-focus focus:ring-offset-2 focus:ring-offset-background",
|
|
111
|
-
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
112
|
-
"transition-colors duration-150",
|
|
137
|
+
radioItemVariants({ size }),
|
|
113
138
|
getStateStyles(state),
|
|
114
139
|
className
|
|
115
140
|
)}
|
|
@@ -117,8 +142,9 @@ export const RadioGroupItem = forwardRef<HTMLInputElement, RadioGroupItemProps>(
|
|
|
117
142
|
/>
|
|
118
143
|
<div data-sonance-name="radio-group"
|
|
119
144
|
className={cn(
|
|
120
|
-
"pointer-events-none absolute
|
|
121
|
-
"scale-0 transition-
|
|
145
|
+
"pointer-events-none absolute rounded-full bg-primary",
|
|
146
|
+
"scale-0 transition-all duration-150",
|
|
147
|
+
radioIndicatorSizes[size],
|
|
122
148
|
isChecked && "scale-100"
|
|
123
149
|
)}
|
|
124
150
|
/>
|
|
@@ -145,3 +171,5 @@ export const RadioGroupItem = forwardRef<HTMLInputElement, RadioGroupItemProps>(
|
|
|
145
171
|
|
|
146
172
|
RadioGroupItem.displayName = "RadioGroupItem";
|
|
147
173
|
|
|
174
|
+
export { radioItemVariants };
|
|
175
|
+
|