sonance-brand-mcp 1.2.4 → 1.3.1
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/components/alert-dialog.stories.tsx +142 -0
- package/dist/assets/components/alert-dialog.tsx +142 -0
- package/dist/assets/components/aspect-ratio.stories.tsx +67 -0
- package/dist/assets/components/aspect-ratio.tsx +8 -0
- package/dist/assets/components/avatar.tsx +64 -20
- package/dist/assets/components/carousel.stories.tsx +158 -0
- package/dist/assets/components/carousel.tsx +262 -0
- package/dist/assets/components/chart.stories.tsx +376 -0
- package/dist/assets/components/chart.tsx +384 -0
- package/dist/assets/components/checkbox.tsx +12 -2
- package/dist/assets/components/code.tsx +22 -20
- package/dist/assets/components/collapsible.stories.tsx +128 -0
- package/dist/assets/components/collapsible.tsx +10 -0
- package/dist/assets/components/command.stories.tsx +183 -0
- package/dist/assets/components/command.tsx +170 -0
- package/dist/assets/components/context-menu.stories.tsx +159 -0
- package/dist/assets/components/context-menu.tsx +218 -0
- package/dist/assets/components/divider.tsx +38 -35
- package/dist/assets/components/dropdown-menu.tsx +217 -0
- package/dist/assets/components/hover-card.stories.tsx +113 -0
- package/dist/assets/components/hover-card.tsx +35 -0
- package/dist/assets/components/kbd.tsx +6 -6
- package/dist/assets/components/menubar.stories.tsx +208 -0
- package/dist/assets/components/menubar.tsx +251 -0
- package/dist/assets/components/navigation-menu.stories.tsx +237 -0
- package/dist/assets/components/navigation-menu.tsx +135 -0
- package/dist/assets/components/resizable.stories.tsx +197 -0
- package/dist/assets/components/resizable.tsx +47 -0
- package/dist/assets/components/scroll-area.stories.tsx +123 -0
- package/dist/assets/components/scroll-area.tsx +48 -0
- package/dist/assets/components/scroll-shadow.tsx +29 -7
- package/dist/assets/components/separator.tsx +32 -0
- package/dist/assets/components/sheet.tsx +141 -0
- package/dist/assets/components/sidebar.stories.tsx +351 -0
- package/dist/assets/components/sidebar.tsx +760 -0
- package/dist/assets/components/toggle-group.stories.tsx +153 -0
- package/dist/assets/components/toggle-group.tsx +61 -0
- package/dist/assets/components/toggle.stories.tsx +77 -0
- package/dist/assets/components/toggle.tsx +46 -0
- package/dist/assets/components/tooltip.tsx +23 -90
- package/dist/assets/globals.css +30 -0
- package/dist/assets/logos/40th-anniversary/Sonance_40_Logo_CMYK_BEAM_BLUE_40_AND_BEAM_DARK.png +0 -0
- package/dist/assets/logos/Sonance logo dark mode.png +0 -0
- package/dist/assets/logos/Sonance logo light mode.png +0 -0
- package/dist/assets/logos/blaze/BlazeBySonance_Logo_Lockup_2C_Light_RGB_05162025.png +0 -0
- package/dist/assets/logos/blaze/BlazeBySonance_Logo_Lockup_3C_Dark_RGB_05162025.png +0 -0
- package/dist/assets/logos/blaze/BlazeBySonance_Logo_Lockup_White_RGB_05162025.png +0 -0
- package/dist/assets/logos/iport/IPORT_Sonance_LockUp_2C_Dark_RGB.png +0 -0
- package/dist/assets/logos/iport/IPORT_Sonance_LockUp_2C_Light_RGB.png +0 -0
- package/dist/assets/logos/james/James_Logo_Black_CMYK.png +0 -0
- package/dist/assets/logos/james/James_Logo_Black_RGB.png +0 -0
- package/dist/assets/logos/james/James_Logo_LtGray_CMYK.png +0 -0
- package/dist/assets/logos/james/James_Logo_LtGray_RGB.png +0 -0
- package/dist/assets/logos/james/James_Logo_Polished_RGB.png +0 -0
- package/dist/assets/logos/james/James_Logo_Reverse_CMYK.png +0 -0
- package/dist/assets/logos/james/James_Logo_Reverse_RGB.png +0 -0
- package/dist/assets/logos/james/James_Logo_White_CMYK.png +0 -0
- package/dist/assets/logos/life-is-better/Sonance_LifeisBetter_Dark_RGB.png +0 -0
- package/dist/assets/logos/life-is-better/Sonance_LifeisBetter_Light_RGB.png +0 -0
- package/dist/assets/logos/my-sonance/My.Sonance_Logo_2C_Dark_RGB.png +0 -0
- package/dist/assets/logos/my-sonance/My.Sonance_Logo_2C_Light_RGB.png +0 -0
- package/dist/assets/logos/my-sonance/My.Sonance_Logo_2C_Reverse_RGB.png +0 -0
- package/dist/assets/logos/my-sonance/My.Sonance_Logo_Black_RGB.png +0 -0
- package/dist/assets/logos/my-sonance/My.Sonance_Logo_Reverse_RGB.png +0 -0
- package/dist/assets/logos/sonance/Sonance_Logo_2C_Dark_RGB.png +0 -0
- package/dist/assets/logos/sonance/Sonance_Logo_2C_Light_RGB.png +0 -0
- package/dist/assets/logos/sonance/Sonance_Logo_2C_Reverse_RGB.png +0 -0
- package/dist/assets/logos/sonance/Sonance_Logo_Black_RGB.png +0 -0
- package/dist/assets/logos/sonance/Sonance_Logo_Grayscale_RGB.png +0 -0
- package/dist/assets/logos/sonance/Sonance_Logo_Reverse_RGB.png +0 -0
- package/dist/assets/logos/sonance-academy/SonanceAcademy_Logo_Dark_CMYK.png +0 -0
- package/dist/assets/logos/sonance-academy/SonanceAcademy_Logo_Light_CMYK.png +0 -0
- package/dist/assets/logos/sonance-iport/Sonance_IPORT_LockUp_3C_Dark_RGB.png +0 -0
- package/dist/assets/logos/sonance-iport/Sonance_IPORT_LockUp_3C_Light_RGB.png +0 -0
- package/dist/assets/logos/sonance-iport/Sonance_IPORT_LockUp_3C_Reverse_RGB.png +0 -0
- package/dist/assets/logos/sonance-iport/Sonance_IPORT_LockUp_Black_RGB.png +0 -0
- package/dist/assets/logos/sonance-iport/Sonance_IPORT_LockUp_Grayscale_RGB.png +0 -0
- package/dist/assets/logos/sonance-iport/Sonance_IPORT_LockUp_Reverse_RGB.png +0 -0
- package/dist/assets/logos/sonance-james/Sonance_James_Lockup_Dark.png +0 -0
- package/dist/assets/logos/sonance-james/Sonance_James_Lockup_Light.png +0 -0
- package/dist/assets/logos/sonance-james-iport/Sonance_James_IPORT_LockupStacked_Dark.png +0 -0
- package/dist/assets/logos/sonance-james-iport/Sonance_James_IPORT_LockupStacked_Light.png +0 -0
- package/dist/assets/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png +0 -0
- package/dist/assets/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png +0 -0
- package/dist/assets/logos/trufig/TrufigLogo_Black.png +0 -0
- package/dist/assets/logos/trufig/TrufigLogo_Light.png +0 -0
- package/dist/assets/logos/trufig/TrufigWatermark_Black.png +0 -0
- package/dist/assets/logos/trufig/TrufigWatermark_Light.png +0 -0
- package/dist/index.js +416 -17
- package/package.json +1 -1
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as RechartsPrimitive from "recharts";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
// Format: { THEME_NAME: CSS_SELECTOR }
|
|
8
|
+
const THEMES = { light: "", dark: ".dark" } as const;
|
|
9
|
+
|
|
10
|
+
export type ChartConfig = {
|
|
11
|
+
[k in string]: {
|
|
12
|
+
label?: React.ReactNode;
|
|
13
|
+
icon?: React.ComponentType;
|
|
14
|
+
} & (
|
|
15
|
+
| { color?: string; theme?: never }
|
|
16
|
+
| { color?: never; theme: Record<keyof typeof THEMES, string> }
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type ChartContextProps = {
|
|
21
|
+
config: ChartConfig;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const ChartContext = React.createContext<ChartContextProps | null>(null);
|
|
25
|
+
|
|
26
|
+
function useChart() {
|
|
27
|
+
const context = React.useContext(ChartContext);
|
|
28
|
+
|
|
29
|
+
if (!context) {
|
|
30
|
+
throw new Error("useChart must be used within a <ChartContainer />");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return context;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const ChartContainer = React.forwardRef<
|
|
37
|
+
HTMLDivElement,
|
|
38
|
+
React.ComponentProps<"div"> & {
|
|
39
|
+
config: ChartConfig;
|
|
40
|
+
children: React.ComponentProps<
|
|
41
|
+
typeof RechartsPrimitive.ResponsiveContainer
|
|
42
|
+
>["children"];
|
|
43
|
+
}
|
|
44
|
+
>(({ id, className, children, config, ...props }, ref) => {
|
|
45
|
+
const uniqueId = React.useId();
|
|
46
|
+
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<ChartContext.Provider value={{ config }}>
|
|
50
|
+
<div
|
|
51
|
+
data-chart={chartId}
|
|
52
|
+
ref={ref}
|
|
53
|
+
className={cn(
|
|
54
|
+
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-foreground-muted [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-secondary-hover [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-secondary-hover [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
|
|
55
|
+
className
|
|
56
|
+
)}
|
|
57
|
+
{...props}
|
|
58
|
+
>
|
|
59
|
+
<ChartStyle id={chartId} config={config} />
|
|
60
|
+
<RechartsPrimitive.ResponsiveContainer>
|
|
61
|
+
{children}
|
|
62
|
+
</RechartsPrimitive.ResponsiveContainer>
|
|
63
|
+
</div>
|
|
64
|
+
</ChartContext.Provider>
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
ChartContainer.displayName = "Chart";
|
|
68
|
+
|
|
69
|
+
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
|
70
|
+
const colorConfig = Object.entries(config).filter(
|
|
71
|
+
([, config]) => config.theme || config.color
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (!colorConfig.length) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<style
|
|
80
|
+
dangerouslySetInnerHTML={{
|
|
81
|
+
__html: Object.entries(THEMES)
|
|
82
|
+
.map(
|
|
83
|
+
([theme, prefix]) => `
|
|
84
|
+
${prefix} [data-chart=${id}] {
|
|
85
|
+
${colorConfig
|
|
86
|
+
.map(([key, itemConfig]) => {
|
|
87
|
+
const color =
|
|
88
|
+
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
|
89
|
+
itemConfig.color;
|
|
90
|
+
return color ? ` --color-${key}: ${color};` : null;
|
|
91
|
+
})
|
|
92
|
+
.join("\n")}
|
|
93
|
+
}
|
|
94
|
+
`
|
|
95
|
+
)
|
|
96
|
+
.join("\n"),
|
|
97
|
+
}}
|
|
98
|
+
/>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const ChartTooltip = RechartsPrimitive.Tooltip;
|
|
103
|
+
|
|
104
|
+
interface ChartTooltipContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
105
|
+
active?: boolean;
|
|
106
|
+
payload?: Array<{
|
|
107
|
+
name?: string;
|
|
108
|
+
value?: number | string;
|
|
109
|
+
dataKey?: string | number;
|
|
110
|
+
color?: string;
|
|
111
|
+
payload?: Record<string, unknown>;
|
|
112
|
+
fill?: string;
|
|
113
|
+
}>;
|
|
114
|
+
label?: string;
|
|
115
|
+
hideLabel?: boolean;
|
|
116
|
+
hideIndicator?: boolean;
|
|
117
|
+
indicator?: "line" | "dot" | "dashed";
|
|
118
|
+
nameKey?: string;
|
|
119
|
+
labelKey?: string;
|
|
120
|
+
labelFormatter?: (value: unknown, payload: unknown[]) => React.ReactNode;
|
|
121
|
+
labelClassName?: string;
|
|
122
|
+
formatter?: (
|
|
123
|
+
value: unknown,
|
|
124
|
+
name: unknown,
|
|
125
|
+
item: unknown,
|
|
126
|
+
index: number,
|
|
127
|
+
payload: unknown
|
|
128
|
+
) => React.ReactNode;
|
|
129
|
+
color?: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const ChartTooltipContent = React.forwardRef<HTMLDivElement, ChartTooltipContentProps>(
|
|
133
|
+
(
|
|
134
|
+
{
|
|
135
|
+
active,
|
|
136
|
+
payload,
|
|
137
|
+
className,
|
|
138
|
+
indicator = "dot",
|
|
139
|
+
hideLabel = false,
|
|
140
|
+
hideIndicator = false,
|
|
141
|
+
label,
|
|
142
|
+
labelFormatter,
|
|
143
|
+
labelClassName,
|
|
144
|
+
formatter,
|
|
145
|
+
color,
|
|
146
|
+
nameKey,
|
|
147
|
+
labelKey,
|
|
148
|
+
},
|
|
149
|
+
ref
|
|
150
|
+
) => {
|
|
151
|
+
const { config } = useChart();
|
|
152
|
+
|
|
153
|
+
const tooltipLabel = React.useMemo(() => {
|
|
154
|
+
if (hideLabel || !payload?.length) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const [item] = payload;
|
|
159
|
+
const key = `${labelKey || item.dataKey || item.name || "value"}`;
|
|
160
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
|
161
|
+
const value =
|
|
162
|
+
!labelKey && typeof label === "string"
|
|
163
|
+
? config[label as keyof typeof config]?.label || label
|
|
164
|
+
: itemConfig?.label;
|
|
165
|
+
|
|
166
|
+
if (labelFormatter) {
|
|
167
|
+
return (
|
|
168
|
+
<div className={cn("font-medium", labelClassName)}>
|
|
169
|
+
{labelFormatter(value, payload)}
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!value) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return <div className={cn("font-medium", labelClassName)}>{value}</div>;
|
|
179
|
+
}, [
|
|
180
|
+
label,
|
|
181
|
+
labelFormatter,
|
|
182
|
+
payload,
|
|
183
|
+
hideLabel,
|
|
184
|
+
labelClassName,
|
|
185
|
+
config,
|
|
186
|
+
labelKey,
|
|
187
|
+
]);
|
|
188
|
+
|
|
189
|
+
if (!active || !payload?.length) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const nestLabel = payload.length === 1 && indicator !== "dot";
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<div
|
|
197
|
+
ref={ref}
|
|
198
|
+
className={cn(
|
|
199
|
+
"grid min-w-[8rem] items-start gap-1.5 rounded-sm border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
|
|
200
|
+
className
|
|
201
|
+
)}
|
|
202
|
+
>
|
|
203
|
+
{!nestLabel ? tooltipLabel : null}
|
|
204
|
+
<div className="grid gap-1.5">
|
|
205
|
+
{payload.map((item, index) => {
|
|
206
|
+
const key = `${nameKey || item.name || item.dataKey || "value"}`;
|
|
207
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
|
208
|
+
const indicatorColor = color || (item.payload as Record<string, unknown>)?.fill as string || item.color;
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<div
|
|
212
|
+
key={String(item.dataKey)}
|
|
213
|
+
className={cn(
|
|
214
|
+
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-foreground-muted",
|
|
215
|
+
indicator === "dot" && "items-center"
|
|
216
|
+
)}
|
|
217
|
+
>
|
|
218
|
+
{formatter && item?.value !== undefined && item.name ? (
|
|
219
|
+
formatter(item.value, item.name, item, index, item.payload)
|
|
220
|
+
) : (
|
|
221
|
+
<>
|
|
222
|
+
{itemConfig?.icon ? (
|
|
223
|
+
<itemConfig.icon />
|
|
224
|
+
) : (
|
|
225
|
+
!hideIndicator && (
|
|
226
|
+
<div
|
|
227
|
+
className={cn(
|
|
228
|
+
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
|
|
229
|
+
{
|
|
230
|
+
"h-2.5 w-2.5": indicator === "dot",
|
|
231
|
+
"w-1": indicator === "line",
|
|
232
|
+
"w-0 border-[1.5px] border-dashed bg-transparent":
|
|
233
|
+
indicator === "dashed",
|
|
234
|
+
"my-0.5": nestLabel && indicator === "dashed",
|
|
235
|
+
}
|
|
236
|
+
)}
|
|
237
|
+
style={
|
|
238
|
+
{
|
|
239
|
+
"--color-bg": indicatorColor,
|
|
240
|
+
"--color-border": indicatorColor,
|
|
241
|
+
} as React.CSSProperties
|
|
242
|
+
}
|
|
243
|
+
/>
|
|
244
|
+
)
|
|
245
|
+
)}
|
|
246
|
+
<div
|
|
247
|
+
className={cn(
|
|
248
|
+
"flex flex-1 justify-between leading-none",
|
|
249
|
+
nestLabel ? "items-end" : "items-center"
|
|
250
|
+
)}
|
|
251
|
+
>
|
|
252
|
+
<div className="grid gap-1.5">
|
|
253
|
+
{nestLabel ? tooltipLabel : null}
|
|
254
|
+
<span className="text-foreground-muted">
|
|
255
|
+
{itemConfig?.label || item.name}
|
|
256
|
+
</span>
|
|
257
|
+
</div>
|
|
258
|
+
{item.value && (
|
|
259
|
+
<span className="font-mono font-medium tabular-nums text-foreground">
|
|
260
|
+
{typeof item.value === 'number' ? item.value.toLocaleString() : item.value}
|
|
261
|
+
</span>
|
|
262
|
+
)}
|
|
263
|
+
</div>
|
|
264
|
+
</>
|
|
265
|
+
)}
|
|
266
|
+
</div>
|
|
267
|
+
);
|
|
268
|
+
})}
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
);
|
|
274
|
+
ChartTooltipContent.displayName = "ChartTooltip";
|
|
275
|
+
|
|
276
|
+
const ChartLegend = RechartsPrimitive.Legend;
|
|
277
|
+
|
|
278
|
+
interface ChartLegendContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
279
|
+
payload?: Array<{
|
|
280
|
+
value?: string;
|
|
281
|
+
dataKey?: string | number;
|
|
282
|
+
color?: string;
|
|
283
|
+
}>;
|
|
284
|
+
verticalAlign?: "top" | "bottom";
|
|
285
|
+
hideIcon?: boolean;
|
|
286
|
+
nameKey?: string;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const ChartLegendContent = React.forwardRef<HTMLDivElement, ChartLegendContentProps>(
|
|
290
|
+
(
|
|
291
|
+
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
|
|
292
|
+
ref
|
|
293
|
+
) => {
|
|
294
|
+
const { config } = useChart();
|
|
295
|
+
|
|
296
|
+
if (!payload?.length) {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return (
|
|
301
|
+
<div
|
|
302
|
+
ref={ref}
|
|
303
|
+
className={cn(
|
|
304
|
+
"flex items-center justify-center gap-4",
|
|
305
|
+
verticalAlign === "top" ? "pb-3" : "pt-3",
|
|
306
|
+
className
|
|
307
|
+
)}
|
|
308
|
+
>
|
|
309
|
+
{payload.map((item) => {
|
|
310
|
+
const key = `${nameKey || item.dataKey || "value"}`;
|
|
311
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
|
312
|
+
|
|
313
|
+
return (
|
|
314
|
+
<div
|
|
315
|
+
key={item.value}
|
|
316
|
+
className={cn(
|
|
317
|
+
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-foreground-muted"
|
|
318
|
+
)}
|
|
319
|
+
>
|
|
320
|
+
{itemConfig?.icon && !hideIcon ? (
|
|
321
|
+
<itemConfig.icon />
|
|
322
|
+
) : (
|
|
323
|
+
<div
|
|
324
|
+
className="h-2 w-2 shrink-0 rounded-[2px]"
|
|
325
|
+
style={{
|
|
326
|
+
backgroundColor: item.color,
|
|
327
|
+
}}
|
|
328
|
+
/>
|
|
329
|
+
)}
|
|
330
|
+
{itemConfig?.label}
|
|
331
|
+
</div>
|
|
332
|
+
);
|
|
333
|
+
})}
|
|
334
|
+
</div>
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
);
|
|
338
|
+
ChartLegendContent.displayName = "ChartLegend";
|
|
339
|
+
|
|
340
|
+
// Helper to extract item config from a payload.
|
|
341
|
+
function getPayloadConfigFromPayload(
|
|
342
|
+
config: ChartConfig,
|
|
343
|
+
payload: unknown,
|
|
344
|
+
key: string
|
|
345
|
+
) {
|
|
346
|
+
if (typeof payload !== "object" || payload === null) {
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const payloadPayload =
|
|
351
|
+
"payload" in payload &&
|
|
352
|
+
typeof payload.payload === "object" &&
|
|
353
|
+
payload.payload !== null
|
|
354
|
+
? payload.payload
|
|
355
|
+
: undefined;
|
|
356
|
+
|
|
357
|
+
let configLabelKey: string = key;
|
|
358
|
+
|
|
359
|
+
if (
|
|
360
|
+
key in payload &&
|
|
361
|
+
typeof (payload as Record<string, unknown>)[key] === "string"
|
|
362
|
+
) {
|
|
363
|
+
configLabelKey = (payload as Record<string, unknown>)[key] as string;
|
|
364
|
+
} else if (
|
|
365
|
+
payloadPayload &&
|
|
366
|
+
key in payloadPayload &&
|
|
367
|
+
typeof (payloadPayload as Record<string, unknown>)[key] === "string"
|
|
368
|
+
) {
|
|
369
|
+
configLabelKey = (payloadPayload as Record<string, unknown>)[key] as string;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return configLabelKey in config
|
|
373
|
+
? config[configLabelKey]
|
|
374
|
+
: config[key as keyof typeof config];
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export {
|
|
378
|
+
ChartContainer,
|
|
379
|
+
ChartTooltip,
|
|
380
|
+
ChartTooltipContent,
|
|
381
|
+
ChartLegend,
|
|
382
|
+
ChartLegendContent,
|
|
383
|
+
ChartStyle,
|
|
384
|
+
};
|
|
@@ -32,17 +32,27 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
|
|
32
32
|
const uniqueId = useId();
|
|
33
33
|
const inputId = id || `checkbox-${uniqueId}`;
|
|
34
34
|
const isDisabled = disabled || state === "disabled";
|
|
35
|
-
|
|
35
|
+
|
|
36
|
+
// Resolve checked state:
|
|
37
|
+
// 1. If visual state is "checked", force true.
|
|
38
|
+
// 2. Else use provided checked prop (undefined = uncontrolled).
|
|
39
|
+
const isVisualChecked = state === "checked";
|
|
40
|
+
const resolvedChecked = isVisualChecked ? true : checked;
|
|
41
|
+
|
|
42
|
+
// If visual state forces checked but no handler, mark readOnly to avoid React warnings
|
|
43
|
+
const isReadOnly = isVisualChecked && !props.onChange && !props.readOnly;
|
|
36
44
|
|
|
37
45
|
return (
|
|
38
46
|
<div className="flex items-start gap-3">
|
|
39
47
|
<div className="relative flex items-center justify-center">
|
|
40
48
|
<input
|
|
49
|
+
key={isVisualChecked ? "visual-checked" : "normal"}
|
|
41
50
|
type="checkbox"
|
|
42
51
|
id={inputId}
|
|
43
52
|
ref={ref}
|
|
44
53
|
disabled={isDisabled}
|
|
45
|
-
checked={
|
|
54
|
+
checked={resolvedChecked}
|
|
55
|
+
readOnly={isReadOnly || props.readOnly}
|
|
46
56
|
className={cn(
|
|
47
57
|
"peer h-5 w-5 shrink-0 appearance-none border border-border bg-input",
|
|
48
58
|
"hover:border-border-hover",
|
|
@@ -20,7 +20,8 @@ export const Code = forwardRef<HTMLElement, CodeProps>(
|
|
|
20
20
|
<code
|
|
21
21
|
ref={ref}
|
|
22
22
|
className={cn(
|
|
23
|
-
"px-1
|
|
23
|
+
"px-1 py-0.5 font-mono text-xs sm:text-sm rounded-sm",
|
|
24
|
+
"break-words",
|
|
24
25
|
variantClasses[variant],
|
|
25
26
|
className
|
|
26
27
|
)}
|
|
@@ -61,12 +62,12 @@ export function CodeBlock({
|
|
|
61
62
|
const lines = code.split("\n");
|
|
62
63
|
|
|
63
64
|
return (
|
|
64
|
-
<div className={cn("relative group", className)}>
|
|
65
|
+
<div className={cn("relative group w-full max-w-full overflow-hidden", className)}>
|
|
65
66
|
{filename && (
|
|
66
|
-
<div className="flex items-center justify-between px-4 py-2 border-b border-border bg-background-tertiary">
|
|
67
|
-
<span className="text-xs font-medium text-foreground-muted">{filename}</span>
|
|
67
|
+
<div className="flex items-center justify-between px-3 sm:px-4 py-2 border-b border-border bg-background-tertiary">
|
|
68
|
+
<span className="text-xs font-medium text-foreground-muted truncate">{filename}</span>
|
|
68
69
|
{language && (
|
|
69
|
-
<span className="text-xs uppercase tracking-wider text-foreground-subtle">
|
|
70
|
+
<span className="text-xs uppercase tracking-wider text-foreground-subtle flex-shrink-0 ml-2">
|
|
70
71
|
{language}
|
|
71
72
|
</span>
|
|
72
73
|
)}
|
|
@@ -75,22 +76,22 @@ export function CodeBlock({
|
|
|
75
76
|
<div className="relative">
|
|
76
77
|
<pre
|
|
77
78
|
className={cn(
|
|
78
|
-
"overflow-x-auto bg-background-secondary p-4",
|
|
79
|
-
"font-mono text-sm text-foreground-secondary",
|
|
79
|
+
"overflow-x-auto bg-background-secondary p-3 sm:p-4 w-full max-w-full",
|
|
80
|
+
"font-mono text-xs sm:text-sm text-foreground-secondary",
|
|
80
81
|
filename && "rounded-t-none"
|
|
81
82
|
)}
|
|
82
83
|
>
|
|
83
|
-
<code>
|
|
84
|
+
<code className="block">
|
|
84
85
|
{showLineNumbers ? (
|
|
85
86
|
lines.map((line, index) => (
|
|
86
87
|
<div
|
|
87
88
|
key={index}
|
|
88
89
|
className={cn(
|
|
89
90
|
"flex",
|
|
90
|
-
highlightLines.includes(index + 1) && "bg-primary/10 -mx-4 px-4"
|
|
91
|
+
highlightLines.includes(index + 1) && "bg-primary/10 -mx-3 sm:-mx-4 px-3 sm:px-4"
|
|
91
92
|
)}
|
|
92
93
|
>
|
|
93
|
-
<span className="mr-4 inline-block w-8 select-none text-right text-foreground-subtle">
|
|
94
|
+
<span className="mr-3 sm:mr-4 inline-block w-6 sm:w-8 select-none text-right text-foreground-subtle">
|
|
94
95
|
{index + 1}
|
|
95
96
|
</span>
|
|
96
97
|
<span>{line}</span>
|
|
@@ -104,7 +105,7 @@ export function CodeBlock({
|
|
|
104
105
|
<button
|
|
105
106
|
onClick={copyToClipboard}
|
|
106
107
|
className={cn(
|
|
107
|
-
"absolute right-2 top-2 p-2",
|
|
108
|
+
"absolute right-1.5 sm:right-2 top-1.5 sm:top-2 p-1.5 sm:p-2",
|
|
108
109
|
"bg-background border border-border rounded-sm",
|
|
109
110
|
"text-foreground-muted hover:text-foreground",
|
|
110
111
|
"opacity-0 group-hover:opacity-100 transition-opacity",
|
|
@@ -113,9 +114,9 @@ export function CodeBlock({
|
|
|
113
114
|
aria-label={copied ? "Copied!" : "Copy code"}
|
|
114
115
|
>
|
|
115
116
|
{copied ? (
|
|
116
|
-
<Check className="h-4 w-4 text-success" />
|
|
117
|
+
<Check className="h-3.5 w-3.5 sm:h-4 sm:w-4 text-success" />
|
|
117
118
|
) : (
|
|
118
|
-
<Copy className="h-4 w-4" />
|
|
119
|
+
<Copy className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
|
|
119
120
|
)}
|
|
120
121
|
</button>
|
|
121
122
|
</div>
|
|
@@ -148,24 +149,25 @@ export function Snippet({
|
|
|
148
149
|
return (
|
|
149
150
|
<div
|
|
150
151
|
className={cn(
|
|
151
|
-
"inline-flex items-center gap-2 px-3 py-2",
|
|
152
|
+
"inline-flex items-center gap-1.5 sm:gap-2 px-2 sm:px-3 py-1.5 sm:py-2",
|
|
152
153
|
"bg-background-secondary border border-border rounded-sm",
|
|
153
|
-
"font-mono text-sm",
|
|
154
|
+
"font-mono text-xs sm:text-sm",
|
|
155
|
+
"max-w-full overflow-hidden",
|
|
154
156
|
className
|
|
155
157
|
)}
|
|
156
158
|
>
|
|
157
|
-
{symbol && <span className="text-foreground-muted">{symbol}</span>}
|
|
158
|
-
<code className="text-foreground">{text}</code>
|
|
159
|
+
{symbol && <span className="text-foreground-muted flex-shrink-0">{symbol}</span>}
|
|
160
|
+
<code className="text-foreground truncate">{text}</code>
|
|
159
161
|
{!hideCopyButton && (
|
|
160
162
|
<button
|
|
161
163
|
onClick={copyToClipboard}
|
|
162
|
-
className="ml-2 p-1 text-foreground-muted hover:text-foreground transition-colors"
|
|
164
|
+
className="ml-1 sm:ml-2 p-1 text-foreground-muted hover:text-foreground transition-colors flex-shrink-0"
|
|
163
165
|
aria-label={copied ? "Copied!" : "Copy"}
|
|
164
166
|
>
|
|
165
167
|
{copied ? (
|
|
166
|
-
<Check className="h-3.5 w-3.5 text-success" />
|
|
168
|
+
<Check className="h-3 w-3 sm:h-3.5 sm:w-3.5 text-success" />
|
|
167
169
|
) : (
|
|
168
|
-
<Copy className="h-3.5 w-3.5" />
|
|
170
|
+
<Copy className="h-3 w-3 sm:h-3.5 sm:w-3.5" />
|
|
169
171
|
)}
|
|
170
172
|
</button>
|
|
171
173
|
)}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { ChevronDown } from "lucide-react";
|
|
6
|
+
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "./collapsible";
|
|
7
|
+
import { cn } from "@/lib/utils";
|
|
8
|
+
|
|
9
|
+
const meta: Meta<typeof Collapsible> = {
|
|
10
|
+
title: "Components/Data Display/Collapsible",
|
|
11
|
+
component: Collapsible,
|
|
12
|
+
parameters: {
|
|
13
|
+
layout: "centered",
|
|
14
|
+
},
|
|
15
|
+
tags: ["autodocs"],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default meta;
|
|
19
|
+
type Story = StoryObj<typeof Collapsible>;
|
|
20
|
+
|
|
21
|
+
function CollapsibleDemo() {
|
|
22
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Collapsible
|
|
26
|
+
open={isOpen}
|
|
27
|
+
onOpenChange={setIsOpen}
|
|
28
|
+
className="w-[350px] space-y-2"
|
|
29
|
+
>
|
|
30
|
+
<div className="flex items-center justify-between space-x-4 px-4">
|
|
31
|
+
<h4 className="text-sm font-medium text-foreground">
|
|
32
|
+
@peduarte starred 3 repositories
|
|
33
|
+
</h4>
|
|
34
|
+
<CollapsibleTrigger asChild>
|
|
35
|
+
<button className="inline-flex h-8 w-8 items-center justify-center rounded-sm border border-border bg-transparent hover:bg-secondary-hover transition-colors">
|
|
36
|
+
<ChevronDown
|
|
37
|
+
className={cn(
|
|
38
|
+
"h-4 w-4 transition-transform duration-200",
|
|
39
|
+
isOpen && "rotate-180"
|
|
40
|
+
)}
|
|
41
|
+
/>
|
|
42
|
+
<span className="sr-only">Toggle</span>
|
|
43
|
+
</button>
|
|
44
|
+
</CollapsibleTrigger>
|
|
45
|
+
</div>
|
|
46
|
+
<div className="rounded-sm border border-border px-4 py-3 text-sm text-foreground">
|
|
47
|
+
@radix-ui/primitives
|
|
48
|
+
</div>
|
|
49
|
+
<CollapsibleContent className="space-y-2">
|
|
50
|
+
<div className="rounded-sm border border-border px-4 py-3 text-sm text-foreground">
|
|
51
|
+
@radix-ui/colors
|
|
52
|
+
</div>
|
|
53
|
+
<div className="rounded-sm border border-border px-4 py-3 text-sm text-foreground">
|
|
54
|
+
@stitches/react
|
|
55
|
+
</div>
|
|
56
|
+
</CollapsibleContent>
|
|
57
|
+
</Collapsible>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const Default: Story = {
|
|
62
|
+
render: () => <CollapsibleDemo />,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function CollapsibleFAQDemo() {
|
|
66
|
+
const [openItems, setOpenItems] = useState<string[]>([]);
|
|
67
|
+
|
|
68
|
+
const toggleItem = (item: string) => {
|
|
69
|
+
setOpenItems((prev) =>
|
|
70
|
+
prev.includes(item) ? prev.filter((i) => i !== item) : [...prev, item]
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const faqs = [
|
|
75
|
+
{
|
|
76
|
+
id: "1",
|
|
77
|
+
question: "What is Sonance?",
|
|
78
|
+
answer:
|
|
79
|
+
"Sonance is a premium audio brand specializing in architectural speakers and outdoor audio solutions.",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: "2",
|
|
83
|
+
question: "How do I install outdoor speakers?",
|
|
84
|
+
answer:
|
|
85
|
+
"Outdoor speakers should be installed by a certified integrator to ensure proper weatherproofing and optimal audio performance.",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: "3",
|
|
89
|
+
question: "Do you offer warranties?",
|
|
90
|
+
answer:
|
|
91
|
+
"Yes, all Sonance products come with comprehensive warranty coverage. Please refer to your product documentation for specific terms.",
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div className="w-[400px] space-y-2">
|
|
97
|
+
{faqs.map((faq) => (
|
|
98
|
+
<Collapsible
|
|
99
|
+
key={faq.id}
|
|
100
|
+
open={openItems.includes(faq.id)}
|
|
101
|
+
onOpenChange={() => toggleItem(faq.id)}
|
|
102
|
+
>
|
|
103
|
+
<CollapsibleTrigger asChild>
|
|
104
|
+
<button className="flex w-full items-center justify-between rounded-sm border border-border bg-card px-4 py-3 text-left text-sm font-medium text-foreground hover:bg-card-hover transition-colors">
|
|
105
|
+
{faq.question}
|
|
106
|
+
<ChevronDown
|
|
107
|
+
className={cn(
|
|
108
|
+
"h-4 w-4 shrink-0 transition-transform duration-200",
|
|
109
|
+
openItems.includes(faq.id) && "rotate-180"
|
|
110
|
+
)}
|
|
111
|
+
/>
|
|
112
|
+
</button>
|
|
113
|
+
</CollapsibleTrigger>
|
|
114
|
+
<CollapsibleContent>
|
|
115
|
+
<div className="border border-t-0 border-border bg-background px-4 py-3 text-sm text-foreground-secondary">
|
|
116
|
+
{faq.answer}
|
|
117
|
+
</div>
|
|
118
|
+
</CollapsibleContent>
|
|
119
|
+
</Collapsible>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const FAQ: Story = {
|
|
126
|
+
render: () => <CollapsibleFAQDemo />,
|
|
127
|
+
};
|
|
128
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
|
|
4
|
+
|
|
5
|
+
const Collapsible = CollapsiblePrimitive.Root;
|
|
6
|
+
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
|
|
7
|
+
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
|
|
8
|
+
|
|
9
|
+
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
|
|
10
|
+
|