@traky-app/ui 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/README.md +30 -2
  2. package/package.json +26 -4
  3. package/registry/registry.json +6 -0
  4. package/registry/traky/hooks/use-mobile.ts +19 -0
  5. package/registry/traky/json/accordion.json +18 -0
  6. package/registry/traky/json/alert-dialog.json +20 -0
  7. package/registry/traky/json/alert.json +17 -0
  8. package/registry/traky/json/animated-beam.json +14 -0
  9. package/registry/traky/json/animated-list.json +14 -0
  10. package/registry/traky/json/avatar.json +17 -0
  11. package/registry/traky/json/badge.json +18 -0
  12. package/registry/traky/json/bento-grid.json +20 -0
  13. package/registry/traky/json/breadcrumb.json +18 -0
  14. package/registry/traky/json/bubble.json +18 -0
  15. package/registry/traky/json/button-group.json +21 -0
  16. package/registry/traky/json/button.json +18 -0
  17. package/registry/traky/json/calendar.json +21 -0
  18. package/registry/traky/json/card.json +14 -0
  19. package/registry/traky/json/carousel.json +21 -0
  20. package/registry/traky/json/chart.json +17 -0
  21. package/registry/traky/json/checkbox.json +18 -0
  22. package/registry/traky/json/checkpoint.json +22 -0
  23. package/registry/traky/json/code-block.json +21 -0
  24. package/registry/traky/json/collapsible.json +17 -0
  25. package/registry/traky/json/command.json +22 -0
  26. package/registry/traky/json/context-menu.json +18 -0
  27. package/registry/traky/json/dialog.json +18 -0
  28. package/registry/traky/json/drawer.json +17 -0
  29. package/registry/traky/json/dropdown-menu.json +18 -0
  30. package/registry/traky/json/empty.json +17 -0
  31. package/registry/traky/json/field.json +21 -0
  32. package/registry/traky/json/globe.json +17 -0
  33. package/registry/traky/json/hook-use-mobile.json +14 -0
  34. package/registry/traky/json/hover-card.json +17 -0
  35. package/registry/traky/json/input-group.json +22 -0
  36. package/registry/traky/json/input-otp.json +18 -0
  37. package/registry/traky/json/input.json +17 -0
  38. package/registry/traky/json/item.json +21 -0
  39. package/registry/traky/json/kbd.json +14 -0
  40. package/registry/traky/json/label.json +17 -0
  41. package/registry/traky/json/lib-disable-input-autofill.json +14 -0
  42. package/registry/traky/json/lib-sidebar-persistence.json +14 -0
  43. package/registry/traky/json/lib-utils.json +18 -0
  44. package/registry/traky/json/map.json +19 -0
  45. package/registry/traky/json/marker.json +18 -0
  46. package/registry/traky/json/marquee.json +14 -0
  47. package/registry/traky/json/message.json +14 -0
  48. package/registry/traky/json/navigation-menu.json +19 -0
  49. package/registry/traky/json/number-ticker.json +14 -0
  50. package/registry/traky/json/pagination.json +20 -0
  51. package/registry/traky/json/popover.json +17 -0
  52. package/registry/traky/json/progress.json +17 -0
  53. package/registry/traky/json/select.json +18 -0
  54. package/registry/traky/json/separator.json +17 -0
  55. package/registry/traky/json/sheet.json +18 -0
  56. package/registry/traky/json/sidebar.json +29 -0
  57. package/registry/traky/json/skeleton.json +14 -0
  58. package/registry/traky/json/sonner.json +18 -0
  59. package/registry/traky/json/spinner.json +17 -0
  60. package/registry/traky/json/switch.json +17 -0
  61. package/registry/traky/json/table.json +14 -0
  62. package/registry/traky/json/tabs.json +17 -0
  63. package/registry/traky/json/textarea.json +17 -0
  64. package/registry/traky/json/toggle-group.json +21 -0
  65. package/registry/traky/json/toggle.json +18 -0
  66. package/registry/traky/json/tooltip.json +17 -0
  67. package/registry/traky/json/trace.json +20 -0
  68. package/registry/traky/lib/disable-input-autofill.ts +23 -0
  69. package/registry/traky/lib/sidebar-persistence.ts +76 -0
  70. package/registry/traky/lib/utils.ts +6 -0
  71. package/registry/traky/registry.json +637 -0
  72. package/registry/traky/ui/accordion.tsx +65 -0
  73. package/registry/traky/ui/alert-dialog.tsx +163 -0
  74. package/registry/traky/ui/alert.tsx +66 -0
  75. package/registry/traky/ui/animated-beam.tsx +185 -0
  76. package/registry/traky/ui/animated-list.tsx +71 -0
  77. package/registry/traky/ui/avatar.tsx +53 -0
  78. package/registry/traky/ui/badge.tsx +46 -0
  79. package/registry/traky/ui/bento-grid.tsx +109 -0
  80. package/registry/traky/ui/breadcrumb.tsx +108 -0
  81. package/registry/traky/ui/bubble.tsx +125 -0
  82. package/registry/traky/ui/button-group.tsx +83 -0
  83. package/registry/traky/ui/button.tsx +64 -0
  84. package/registry/traky/ui/calendar.tsx +213 -0
  85. package/registry/traky/ui/card.tsx +92 -0
  86. package/registry/traky/ui/carousel.tsx +240 -0
  87. package/registry/traky/ui/chart.tsx +380 -0
  88. package/registry/traky/ui/checkbox.tsx +31 -0
  89. package/registry/traky/ui/checkpoint.tsx +94 -0
  90. package/registry/traky/ui/code-block.tsx +232 -0
  91. package/registry/traky/ui/collapsible.tsx +33 -0
  92. package/registry/traky/ui/command.tsx +190 -0
  93. package/registry/traky/ui/context-menu.tsx +251 -0
  94. package/registry/traky/ui/dialog.tsx +142 -0
  95. package/registry/traky/ui/drawer.tsx +135 -0
  96. package/registry/traky/ui/dropdown-menu.tsx +256 -0
  97. package/registry/traky/ui/empty.tsx +104 -0
  98. package/registry/traky/ui/field.tsx +248 -0
  99. package/registry/traky/ui/globe.tsx +234 -0
  100. package/registry/traky/ui/hover-card.tsx +44 -0
  101. package/registry/traky/ui/input-group.tsx +170 -0
  102. package/registry/traky/ui/input-otp.tsx +77 -0
  103. package/registry/traky/ui/input.tsx +24 -0
  104. package/registry/traky/ui/item.tsx +193 -0
  105. package/registry/traky/ui/kbd.tsx +28 -0
  106. package/registry/traky/ui/label.tsx +24 -0
  107. package/registry/traky/ui/map.tsx +2226 -0
  108. package/registry/traky/ui/marker.tsx +69 -0
  109. package/registry/traky/ui/marquee.tsx +74 -0
  110. package/registry/traky/ui/message.tsx +92 -0
  111. package/registry/traky/ui/navigation-menu.tsx +169 -0
  112. package/registry/traky/ui/number-ticker.tsx +90 -0
  113. package/registry/traky/ui/pagination.tsx +122 -0
  114. package/registry/traky/ui/popover.tsx +48 -0
  115. package/registry/traky/ui/progress.tsx +31 -0
  116. package/registry/traky/ui/select.tsx +193 -0
  117. package/registry/traky/ui/separator.tsx +28 -0
  118. package/registry/traky/ui/sheet.tsx +143 -0
  119. package/registry/traky/ui/sidebar.tsx +764 -0
  120. package/registry/traky/ui/skeleton.tsx +13 -0
  121. package/registry/traky/ui/sonner.tsx +71 -0
  122. package/registry/traky/ui/spinner.tsx +15 -0
  123. package/registry/traky/ui/switch.tsx +41 -0
  124. package/registry/traky/ui/table.tsx +116 -0
  125. package/registry/traky/ui/tabs.tsx +66 -0
  126. package/registry/traky/ui/textarea.tsx +20 -0
  127. package/registry/traky/ui/toggle-group.tsx +83 -0
  128. package/registry/traky/ui/toggle.tsx +47 -0
  129. package/registry/traky/ui/tooltip.tsx +61 -0
  130. package/registry/traky/ui/trace.tsx +165 -0
  131. package/schema/components.json +24 -0
@@ -0,0 +1,240 @@
1
+ "use client"
2
+
3
+ import { IconArrowLeft, IconArrowRight } from "@tabler/icons-react";
4
+ import * as React from "react"
5
+ import useEmblaCarousel, {
6
+ type UseEmblaCarouselType,
7
+ } from "embla-carousel-react"
8
+ import { cn } from "@/lib/utils"
9
+ import { Button } from "@/components/ui/button"
10
+
11
+ type CarouselApi = UseEmblaCarouselType[1]
12
+ type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
13
+ type CarouselOptions = UseCarouselParameters[0]
14
+ type CarouselPlugin = UseCarouselParameters[1]
15
+
16
+ type CarouselProps = {
17
+ opts?: CarouselOptions
18
+ plugins?: CarouselPlugin
19
+ orientation?: "horizontal" | "vertical"
20
+ setApi?: (api: CarouselApi) => void
21
+ }
22
+
23
+ type CarouselContextProps = {
24
+ carouselRef: ReturnType<typeof useEmblaCarousel>[0]
25
+ api: ReturnType<typeof useEmblaCarousel>[1]
26
+ scrollPrev: () => void
27
+ scrollNext: () => void
28
+ canScrollPrev: boolean
29
+ canScrollNext: boolean
30
+ } & CarouselProps
31
+
32
+ const CarouselContext = React.createContext<CarouselContextProps | null>(null)
33
+
34
+ function useCarousel() {
35
+ const context = React.useContext(CarouselContext)
36
+
37
+ if (!context) {
38
+ throw new Error("useCarousel must be used within a <Carousel />")
39
+ }
40
+
41
+ return context
42
+ }
43
+
44
+ function Carousel({
45
+ orientation = "horizontal",
46
+ opts,
47
+ setApi,
48
+ plugins,
49
+ className,
50
+ children,
51
+ ...props
52
+ }: React.ComponentProps<"div"> & CarouselProps) {
53
+ const [carouselRef, api] = useEmblaCarousel(
54
+ {
55
+ ...opts,
56
+ axis: orientation === "horizontal" ? "x" : "y",
57
+ },
58
+ plugins
59
+ )
60
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
61
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
62
+
63
+ const onSelect = React.useCallback((api: CarouselApi) => {
64
+ if (!api) return
65
+ setCanScrollPrev(api.canScrollPrev())
66
+ setCanScrollNext(api.canScrollNext())
67
+ }, [])
68
+
69
+ const scrollPrev = React.useCallback(() => {
70
+ api?.scrollPrev()
71
+ }, [api])
72
+
73
+ const scrollNext = React.useCallback(() => {
74
+ api?.scrollNext()
75
+ }, [api])
76
+
77
+ const handleKeyDown = React.useCallback(
78
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
79
+ if (event.key === "IconArrowLeft") {
80
+ event.preventDefault()
81
+ scrollPrev()
82
+ } else if (event.key === "IconArrowRight") {
83
+ event.preventDefault()
84
+ scrollNext()
85
+ }
86
+ },
87
+ [scrollPrev, scrollNext]
88
+ )
89
+
90
+ React.useEffect(() => {
91
+ if (!api || !setApi) return
92
+ setApi(api)
93
+ }, [api, setApi])
94
+
95
+ React.useEffect(() => {
96
+ if (!api) return
97
+ onSelect(api)
98
+ api.on("reInit", onSelect)
99
+ api.on("select", onSelect)
100
+
101
+ return () => {
102
+ api?.off("select", onSelect)
103
+ }
104
+ }, [api, onSelect])
105
+
106
+ return (
107
+ <CarouselContext.Provider
108
+ value={{
109
+ carouselRef,
110
+ api: api,
111
+ opts,
112
+ orientation:
113
+ orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
114
+ scrollPrev,
115
+ scrollNext,
116
+ canScrollPrev,
117
+ canScrollNext,
118
+ }}
119
+ >
120
+ <div
121
+ onKeyDownCapture={handleKeyDown}
122
+ className={cn("relative", className)}
123
+ role="region"
124
+ aria-roledescription="carousel"
125
+ data-slot="carousel"
126
+ {...props}
127
+ >
128
+ {children}
129
+ </div>
130
+ </CarouselContext.Provider>
131
+ )
132
+ }
133
+
134
+ function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
135
+ const { carouselRef, orientation } = useCarousel()
136
+
137
+ return (
138
+ <div
139
+ ref={carouselRef}
140
+ className="overflow-hidden"
141
+ data-slot="carousel-content"
142
+ >
143
+ <div
144
+ className={cn(
145
+ "flex",
146
+ orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
147
+ className
148
+ )}
149
+ {...props}
150
+ />
151
+ </div>
152
+ )
153
+ }
154
+
155
+ function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
156
+ const { orientation } = useCarousel()
157
+
158
+ return (
159
+ <div
160
+ role="group"
161
+ aria-roledescription="slide"
162
+ data-slot="carousel-item"
163
+ className={cn(
164
+ "min-w-0 shrink-0 grow-0 basis-full",
165
+ orientation === "horizontal" ? "pl-4" : "pt-4",
166
+ className
167
+ )}
168
+ {...props}
169
+ />
170
+ )
171
+ }
172
+
173
+ function CarouselPrevious({
174
+ className,
175
+ variant = "outline",
176
+ size = "icon",
177
+ ...props
178
+ }: React.ComponentProps<typeof Button>) {
179
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
180
+
181
+ return (
182
+ <Button
183
+ data-slot="carousel-previous"
184
+ variant={variant}
185
+ size={size}
186
+ className={cn(
187
+ "absolute size-8 rounded-full",
188
+ orientation === "horizontal"
189
+ ? "top-1/2 -left-12 -translate-y-1/2"
190
+ : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
191
+ className
192
+ )}
193
+ disabled={!canScrollPrev}
194
+ onClick={scrollPrev}
195
+ {...props}
196
+ >
197
+ <IconArrowLeft />
198
+ <span className="sr-only">Previous slide</span>
199
+ </Button>
200
+ )
201
+ }
202
+
203
+ function CarouselNext({
204
+ className,
205
+ variant = "outline",
206
+ size = "icon",
207
+ ...props
208
+ }: React.ComponentProps<typeof Button>) {
209
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
210
+
211
+ return (
212
+ <Button
213
+ data-slot="carousel-next"
214
+ variant={variant}
215
+ size={size}
216
+ className={cn(
217
+ "absolute size-8 rounded-full",
218
+ orientation === "horizontal"
219
+ ? "top-1/2 -right-12 -translate-y-1/2"
220
+ : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
221
+ className
222
+ )}
223
+ disabled={!canScrollNext}
224
+ onClick={scrollNext}
225
+ {...props}
226
+ >
227
+ <IconArrowRight />
228
+ <span className="sr-only">Next slide</span>
229
+ </Button>
230
+ )
231
+ }
232
+
233
+ export {
234
+ type CarouselApi,
235
+ Carousel,
236
+ CarouselContent,
237
+ CarouselItem,
238
+ CarouselPrevious,
239
+ CarouselNext,
240
+ }
@@ -0,0 +1,380 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as RechartsPrimitive from "recharts"
5
+ import type {
6
+ TooltipContentProps,
7
+ TooltipPayloadEntry,
8
+ LegendPayload,
9
+ } from "recharts"
10
+
11
+ import { cn } from "@/lib/utils"
12
+
13
+ // Format: { THEME_NAME: CSS_SELECTOR }
14
+ const THEMES = { light: "", dark: ".dark" } as const
15
+
16
+ export type ChartConfig = {
17
+ [k in string]: {
18
+ label?: React.ReactNode
19
+ icon?: React.ComponentType
20
+ } & (
21
+ | { color?: string; theme?: never }
22
+ | { color?: never; theme: Record<keyof typeof THEMES, string> }
23
+ )
24
+ }
25
+
26
+ type ChartContextProps = {
27
+ config: ChartConfig
28
+ }
29
+
30
+ const ChartContext = React.createContext<ChartContextProps | null>(null)
31
+
32
+ function useChart() {
33
+ const context = React.useContext(ChartContext)
34
+
35
+ if (!context) {
36
+ throw new Error("useChart must be used within a <ChartContainer />")
37
+ }
38
+
39
+ return context
40
+ }
41
+
42
+ /** Color de la serie definido en el config. La leyenda y el tooltip usan el mismo token que el trazo. */
43
+ function seriesColor(config: ChartConfig, key: string, fallback?: string) {
44
+ if (key in config && config[key]?.color) {
45
+ return `var(--color-${key})`
46
+ }
47
+ return fallback
48
+ }
49
+
50
+ function ChartContainer({
51
+ id,
52
+ className,
53
+ children,
54
+ config,
55
+ ...props
56
+ }: React.ComponentProps<"div"> & {
57
+ config: ChartConfig
58
+ children: React.ComponentProps<
59
+ typeof RechartsPrimitive.ResponsiveContainer
60
+ >["children"]
61
+ }) {
62
+ const uniqueId = React.useId()
63
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
64
+
65
+ return (
66
+ <ChartContext.Provider value={{ config }}>
67
+ <div
68
+ data-slot="chart"
69
+ data-chart={chartId}
70
+ className={cn(
71
+ "[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
72
+ className
73
+ )}
74
+ {...props}
75
+ >
76
+ <ChartStyle id={chartId} config={config} />
77
+ <RechartsPrimitive.ResponsiveContainer
78
+ width="100%"
79
+ height="100%"
80
+ minWidth={0}
81
+ debounce={50}
82
+ >
83
+ {children}
84
+ </RechartsPrimitive.ResponsiveContainer>
85
+ </div>
86
+ </ChartContext.Provider>
87
+ )
88
+ }
89
+
90
+ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
91
+ const colorConfig = Object.entries(config).filter(
92
+ ([, config]) => config.theme || config.color
93
+ )
94
+
95
+ if (!colorConfig.length) {
96
+ return null
97
+ }
98
+
99
+ return (
100
+ <style
101
+ dangerouslySetInnerHTML={{
102
+ __html: Object.entries(THEMES)
103
+ .map(
104
+ ([theme, prefix]) => `
105
+ ${prefix} [data-chart=${id}] {
106
+ ${colorConfig
107
+ .map(([key, itemConfig]) => {
108
+ const color =
109
+ itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
110
+ itemConfig.color
111
+ return color ? ` --color-${key}: ${color};` : null
112
+ })
113
+ .join("\n")}
114
+ }
115
+ `
116
+ )
117
+ .join("\n"),
118
+ }}
119
+ />
120
+ )
121
+ }
122
+
123
+ const ChartTooltip = RechartsPrimitive.Tooltip
124
+
125
+ function ChartTooltipContent({
126
+ active,
127
+ payload,
128
+ className,
129
+ indicator = "dot",
130
+ hideLabel = false,
131
+ hideIndicator = false,
132
+ label,
133
+ labelFormatter,
134
+ labelClassName,
135
+ formatter,
136
+ color,
137
+ nameKey,
138
+ labelKey,
139
+ }: Partial<TooltipContentProps> &
140
+ React.ComponentProps<"div"> & {
141
+ hideLabel?: boolean
142
+ hideIndicator?: boolean
143
+ indicator?: "line" | "dot" | "dashed"
144
+ nameKey?: string
145
+ labelKey?: string
146
+ }) {
147
+ const { config } = useChart()
148
+
149
+ const tooltipLabel = React.useMemo(() => {
150
+ if (hideLabel || !payload?.length) {
151
+ return null
152
+ }
153
+
154
+ const [item] = payload
155
+ const key = `${labelKey || item?.dataKey || item?.name || "value"}`
156
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
157
+ const value =
158
+ !labelKey && typeof label === "string"
159
+ ? config[label as keyof typeof config]?.label || label
160
+ : itemConfig?.label
161
+
162
+ if (labelFormatter) {
163
+ return (
164
+ <div className={cn("font-medium", labelClassName)}>
165
+ {labelFormatter(value, payload)}
166
+ </div>
167
+ )
168
+ }
169
+
170
+ if (!value) {
171
+ return null
172
+ }
173
+
174
+ return <div className={cn("font-medium", labelClassName)}>{value}</div>
175
+ }, [
176
+ label,
177
+ labelFormatter,
178
+ payload,
179
+ hideLabel,
180
+ labelClassName,
181
+ config,
182
+ labelKey,
183
+ ])
184
+
185
+ if (!active || !payload?.length) {
186
+ return null
187
+ }
188
+
189
+ const nestLabel = payload.length === 1 && indicator !== "dot"
190
+
191
+ return (
192
+ <div
193
+ className={cn(
194
+ "border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl",
195
+ className
196
+ )}
197
+ >
198
+ {!nestLabel ? tooltipLabel : null}
199
+ <div className="grid gap-1.5">
200
+ {payload
201
+ .filter((item: TooltipPayloadEntry) => item.type !== "none")
202
+ .map((item: TooltipPayloadEntry, index: number) => {
203
+ const key = `${item.dataKey || nameKey || item.name || "value"}`
204
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
205
+ const indicatorColor =
206
+ color || seriesColor(config, key, item.color || item.payload?.fill)
207
+
208
+ return (
209
+ <div
210
+ key={
211
+ item.graphicalItemId ??
212
+ `row-${index}-${String(item.name ?? item.dataKey ?? "")}`
213
+ }
214
+ className={cn(
215
+ "[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
216
+ indicator === "dot" && "items-center"
217
+ )}
218
+ >
219
+ {formatter && item?.value !== undefined && item.name ? (
220
+ formatter(item.value, item.name, item, index, item.payload)
221
+ ) : (
222
+ <>
223
+ {itemConfig?.icon ? (
224
+ <itemConfig.icon />
225
+ ) : (
226
+ !hideIndicator && (
227
+ <div
228
+ className={cn(
229
+ "shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
230
+ {
231
+ "h-2.5 w-2.5": indicator === "dot",
232
+ "w-1": indicator === "line",
233
+ "w-0 border-[1.5px] border-dashed bg-transparent":
234
+ indicator === "dashed",
235
+ "my-0.5": nestLabel && indicator === "dashed",
236
+ }
237
+ )}
238
+ style={
239
+ {
240
+ "--color-bg": indicatorColor,
241
+ "--color-border": indicatorColor,
242
+ } as React.CSSProperties
243
+ }
244
+ />
245
+ )
246
+ )}
247
+ <div
248
+ className={cn(
249
+ "flex flex-1 justify-between leading-none",
250
+ nestLabel ? "items-end" : "items-center"
251
+ )}
252
+ >
253
+ <div className="grid gap-1.5">
254
+ {nestLabel ? tooltipLabel : null}
255
+ <span className="text-muted-foreground">
256
+ {itemConfig?.label || item.name}
257
+ </span>
258
+ </div>
259
+ {item.value && (
260
+ <span className="text-foreground font-mono font-medium tabular-nums">
261
+ {item.value.toLocaleString()}
262
+ </span>
263
+ )}
264
+ </div>
265
+ </>
266
+ )}
267
+ </div>
268
+ )
269
+ })}
270
+ </div>
271
+ </div>
272
+ )
273
+ }
274
+
275
+ const ChartLegend = RechartsPrimitive.Legend
276
+
277
+ function ChartLegendContent({
278
+ className,
279
+ hideIcon = false,
280
+ payload,
281
+ verticalAlign = "bottom",
282
+ nameKey,
283
+ }: React.ComponentProps<"div"> & {
284
+ payload?: ReadonlyArray<LegendPayload>
285
+ verticalAlign?: "top" | "bottom";
286
+ hideIcon?: boolean
287
+ nameKey?: string
288
+ }) {
289
+ const { config } = useChart()
290
+
291
+ if (!payload?.length) {
292
+ return null
293
+ }
294
+
295
+ return (
296
+ <div
297
+ className={cn(
298
+ "flex items-center justify-center gap-4",
299
+ verticalAlign === "top" ? "pb-3" : "pt-3",
300
+ className
301
+ )}
302
+ >
303
+ {payload
304
+ .filter((item: LegendPayload) => item.type !== "none")
305
+ .map((item: LegendPayload) => {
306
+ const key = `${nameKey || item.dataKey || "value"}`
307
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
308
+
309
+ return (
310
+ <div
311
+ key={item.value}
312
+ className={cn(
313
+ "[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3"
314
+ )}
315
+ >
316
+ {itemConfig?.icon && !hideIcon ? (
317
+ <itemConfig.icon />
318
+ ) : (
319
+ <div
320
+ className="h-2 w-2 shrink-0 rounded-[2px]"
321
+ style={{
322
+ backgroundColor: seriesColor(config, key, item.color),
323
+ }}
324
+ />
325
+ )}
326
+ {itemConfig?.label}
327
+ </div>
328
+ )
329
+ })}
330
+ </div>
331
+ )
332
+ }
333
+
334
+ // Helper to extract item config from a payload.
335
+ function getPayloadConfigFromPayload(
336
+ config: ChartConfig,
337
+ payload: unknown,
338
+ key: string
339
+ ) {
340
+ if (typeof payload !== "object" || payload === null) {
341
+ return undefined
342
+ }
343
+
344
+ const payloadPayload =
345
+ "payload" in payload &&
346
+ typeof payload.payload === "object" &&
347
+ payload.payload !== null
348
+ ? payload.payload
349
+ : undefined
350
+
351
+ let configLabelKey: string = key
352
+
353
+ if (
354
+ key in payload &&
355
+ typeof payload[key as keyof typeof payload] === "string"
356
+ ) {
357
+ configLabelKey = payload[key as keyof typeof payload] as string
358
+ } else if (
359
+ payloadPayload &&
360
+ key in payloadPayload &&
361
+ typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
362
+ ) {
363
+ configLabelKey = payloadPayload[
364
+ key as keyof typeof payloadPayload
365
+ ] as string
366
+ }
367
+
368
+ return configLabelKey in config
369
+ ? config[configLabelKey]
370
+ : config[key as keyof typeof config]
371
+ }
372
+
373
+ export {
374
+ ChartContainer,
375
+ ChartTooltip,
376
+ ChartTooltipContent,
377
+ ChartLegend,
378
+ ChartLegendContent,
379
+ ChartStyle,
380
+ }
@@ -0,0 +1,31 @@
1
+ "use client"
2
+
3
+ import { IconCheck } from "@tabler/icons-react";
4
+ import * as React from "react"
5
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
6
+ import { cn } from "@/lib/utils"
7
+
8
+ function Checkbox({
9
+ className,
10
+ ...props
11
+ }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
12
+ return (
13
+ <CheckboxPrimitive.Root
14
+ data-slot="checkbox"
15
+ className={cn(
16
+ "peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
17
+ className
18
+ )}
19
+ {...props}
20
+ >
21
+ <CheckboxPrimitive.Indicator
22
+ data-slot="checkbox-indicator"
23
+ className="grid place-content-center text-current transition-none"
24
+ >
25
+ <IconCheck className="size-3.5" />
26
+ </CheckboxPrimitive.Indicator>
27
+ </CheckboxPrimitive.Root>
28
+ )
29
+ }
30
+
31
+ export { Checkbox }