@gentleduck/registry-ui 0.2.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.
Files changed (175) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/index.css +3 -0
  3. package/package.json +59 -0
  4. package/src/_old/_table/index.ts +5 -0
  5. package/src/_old/_table/table-advanced.constants.tsx +24 -0
  6. package/src/_old/_table/table-advanced.tsx +311 -0
  7. package/src/_old/_table/table-advanced.types.ts +272 -0
  8. package/src/_old/_table/table.constants.ts +2 -0
  9. package/src/_old/_table/table.hook.tsx +115 -0
  10. package/src/_old/_table/table.lib.ts +85 -0
  11. package/src/_old/_table/table.tsx +916 -0
  12. package/src/_old/_table/table.types.ts +118 -0
  13. package/src/_old/_table/todo.md +11 -0
  14. package/src/_old/_upload/index.ts +9 -0
  15. package/src/_old/_upload/todo.md +38 -0
  16. package/src/_old/_upload/upload-advanced-chunks.tsx +1624 -0
  17. package/src/_old/_upload/upload-advanced.tsx +507 -0
  18. package/src/_old/_upload/upload-sonner.tsx +58 -0
  19. package/src/_old/_upload/upload.assets.tsx +239 -0
  20. package/src/_old/_upload/upload.constants.tsx +75 -0
  21. package/src/_old/_upload/upload.dto.ts +19 -0
  22. package/src/_old/_upload/upload.lib.tsx +630 -0
  23. package/src/_old/_upload/upload.tsx +491 -0
  24. package/src/_old/_upload/upload.types.ts +436 -0
  25. package/src/accordion/accordion.tsx +247 -0
  26. package/src/accordion/index.ts +1 -0
  27. package/src/alert/alert.constants.ts +17 -0
  28. package/src/alert/alert.tsx +52 -0
  29. package/src/alert/index.ts +2 -0
  30. package/src/alert-dialog/alert-dialog.tsx +107 -0
  31. package/src/alert-dialog/index.ts +1 -0
  32. package/src/aspect-ratio/aspect-ratio.tsx +33 -0
  33. package/src/aspect-ratio/index.ts +1 -0
  34. package/src/audio/audio-record.tsx +776 -0
  35. package/src/audio/audio-visualizer.tsx +377 -0
  36. package/src/audio/audio.libs.ts +5 -0
  37. package/src/audio/audio.types.ts +50 -0
  38. package/src/audio/index.ts +2 -0
  39. package/src/avatar/avatar.tsx +78 -0
  40. package/src/avatar/index.ts +1 -0
  41. package/src/badge/badge.constants.ts +38 -0
  42. package/src/badge/badge.tsx +19 -0
  43. package/src/badge/index.ts +2 -0
  44. package/src/breadcrumb/breadcrumb.tsx +119 -0
  45. package/src/breadcrumb/index.ts +1 -0
  46. package/src/button/button.constants.ts +44 -0
  47. package/src/button/button.tsx +79 -0
  48. package/src/button/button.types.ts +38 -0
  49. package/src/button/index.ts +3 -0
  50. package/src/button-group/button-group.constants.ts +26 -0
  51. package/src/button-group/button-group.tsx +65 -0
  52. package/src/button-group/index.ts +2 -0
  53. package/src/calendar/calendar.tsx +191 -0
  54. package/src/calendar/index.ts +1 -0
  55. package/src/card/card.tsx +81 -0
  56. package/src/card/index.ts +1 -0
  57. package/src/carousel/carousel.tsx +211 -0
  58. package/src/carousel/carousel.types.ts +23 -0
  59. package/src/carousel/index.ts +2 -0
  60. package/src/chart/chart.libs.ts +27 -0
  61. package/src/chart/chart.tsx +260 -0
  62. package/src/chart/chart.types.ts +38 -0
  63. package/src/chart/index.ts +3 -0
  64. package/src/checkbox/checkbox.tsx +144 -0
  65. package/src/checkbox/checkbox.types.ts +24 -0
  66. package/src/checkbox/index.ts +2 -0
  67. package/src/collapsible/collapsible.tsx +151 -0
  68. package/src/collapsible/index.ts +1 -0
  69. package/src/combobox/combobox.tsx +132 -0
  70. package/src/combobox/index.ts +1 -0
  71. package/src/command/command.tsx +192 -0
  72. package/src/command/command.types.ts +11 -0
  73. package/src/command/index.ts +2 -0
  74. package/src/context-menu/context-menu.tsx +178 -0
  75. package/src/context-menu/index.ts +1 -0
  76. package/src/dialog/dialog-responsive.tsx +137 -0
  77. package/src/dialog/dialog.tsx +97 -0
  78. package/src/dialog/index.ts +2 -0
  79. package/src/direction/direction.tsx +13 -0
  80. package/src/direction/index.ts +1 -0
  81. package/src/drawer/drawer.tsx +185 -0
  82. package/src/drawer/index.ts +1 -0
  83. package/src/dropdown-menu/dropdown-menu.tsx +181 -0
  84. package/src/dropdown-menu/index.ts +1 -0
  85. package/src/empty/empty.constants.ts +15 -0
  86. package/src/empty/empty.tsx +73 -0
  87. package/src/empty/index.ts +2 -0
  88. package/src/field/field.constants.ts +22 -0
  89. package/src/field/field.tsx +203 -0
  90. package/src/field/index.ts +2 -0
  91. package/src/hover-card/hover-card.tsx +79 -0
  92. package/src/hover-card/index.ts +1 -0
  93. package/src/input/index.ts +1 -0
  94. package/src/input/input.tsx +45 -0
  95. package/src/input-group/index.ts +1 -0
  96. package/src/input-group/input-group.tsx +170 -0
  97. package/src/input-otp/index.ts +1 -0
  98. package/src/input-otp/input-otp.tsx +66 -0
  99. package/src/item/index.ts +2 -0
  100. package/src/item/item.constants.ts +22 -0
  101. package/src/item/item.tsx +185 -0
  102. package/src/json-editor/index.ts +4 -0
  103. package/src/json-editor/json-editor.hooks.ts +21 -0
  104. package/src/json-editor/json-editor.libs.ts +34 -0
  105. package/src/json-editor/json-editor.tsx +425 -0
  106. package/src/json-editor/json-editor.types.ts +80 -0
  107. package/src/json-editor/json-editor.view.tsx +110 -0
  108. package/src/json-editor/json-text-area.tsx +7 -0
  109. package/src/kbd/index.ts +1 -0
  110. package/src/kbd/kbd.tsx +39 -0
  111. package/src/label/index.ts +1 -0
  112. package/src/label/label.tsx +28 -0
  113. package/src/menubar/index.ts +1 -0
  114. package/src/menubar/menubar.tsx +213 -0
  115. package/src/navigation-menu/index.ts +1 -0
  116. package/src/navigation-menu/navigation-menu.tsx +152 -0
  117. package/src/pagination/index.ts +2 -0
  118. package/src/pagination/pagination.tsx +191 -0
  119. package/src/pagination/pagination.types.ts +17 -0
  120. package/src/popover/index.ts +1 -0
  121. package/src/popover/popover.tsx +35 -0
  122. package/src/preview-panel/index.ts +3 -0
  123. package/src/preview-panel/preview-panel-dialog.tsx +99 -0
  124. package/src/preview-panel/preview-panel.tsx +389 -0
  125. package/src/preview-panel/preview-panel.types.ts +49 -0
  126. package/src/progress/index.ts +1 -0
  127. package/src/progress/progress.tsx +32 -0
  128. package/src/radio-group/index.ts +1 -0
  129. package/src/radio-group/radio-group.tsx +92 -0
  130. package/src/resizable/index.ts +1 -0
  131. package/src/resizable/resizable.tsx +52 -0
  132. package/src/scroll-area/index.ts +1 -0
  133. package/src/scroll-area/scroll-area.tsx +30 -0
  134. package/src/select/index.ts +1 -0
  135. package/src/select/select.tsx +138 -0
  136. package/src/separator/index.ts +1 -0
  137. package/src/separator/separator.tsx +28 -0
  138. package/src/sheet/index.ts +2 -0
  139. package/src/sheet/sheet.constants.tsx +20 -0
  140. package/src/sheet/sheet.tsx +92 -0
  141. package/src/sidebar/index.ts +4 -0
  142. package/src/sidebar/sidebar.constants.ts +30 -0
  143. package/src/sidebar/sidebar.hooks.ts +13 -0
  144. package/src/sidebar/sidebar.tsx +676 -0
  145. package/src/sidebar/sidebar.types.ts +28 -0
  146. package/src/skeleton/index.ts +1 -0
  147. package/src/skeleton/skeleton.tsx +22 -0
  148. package/src/slider/index.ts +1 -0
  149. package/src/slider/slider.tsx +57 -0
  150. package/src/sonner/index.ts +4 -0
  151. package/src/sonner/sonner.chunks.tsx +80 -0
  152. package/src/sonner/sonner.libs.ts +13 -0
  153. package/src/sonner/sonner.tsx +31 -0
  154. package/src/sonner/sonner.types.ts +9 -0
  155. package/src/switch/index.ts +1 -0
  156. package/src/switch/switch.tsx +63 -0
  157. package/src/table/index.ts +1 -0
  158. package/src/table/table.tsx +95 -0
  159. package/src/tabs/index.ts +1 -0
  160. package/src/tabs/tabs.tsx +151 -0
  161. package/src/textarea/index.ts +1 -0
  162. package/src/textarea/textarea.tsx +24 -0
  163. package/src/toggle/index.ts +2 -0
  164. package/src/toggle/toggle.constants.ts +22 -0
  165. package/src/toggle/toggle.tsx +24 -0
  166. package/src/toggle-group/index.ts +1 -0
  167. package/src/toggle-group/toggle-group.tsx +69 -0
  168. package/src/tooltip/index.ts +1 -0
  169. package/src/tooltip/tooltip.tsx +32 -0
  170. package/src/upload/index.ts +1 -0
  171. package/src/upload/upload.constants.tsx +19 -0
  172. package/src/upload/upload.libs.ts +97 -0
  173. package/src/upload/upload.tsx +340 -0
  174. package/src/upload/upload.types.ts +44 -0
  175. package/tsconfig.json +25 -0
@@ -0,0 +1,211 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@gentleduck/libs/cn'
4
+ import { type Direction, useDirection } from '@gentleduck/primitives/direction'
5
+ import useEmblaCarousel from 'embla-carousel-react'
6
+ import { ArrowLeft, ArrowRight } from 'lucide-react'
7
+ import * as React from 'react'
8
+ import { Button } from '../button'
9
+ import type { CarouselApi, CarouselContextProps, CarouselProps } from './carousel.types'
10
+
11
+ const CarouselContext = React.createContext<CarouselContextProps | null>(null)
12
+
13
+ function useCarousel() {
14
+ const context = React.useContext(CarouselContext)
15
+
16
+ if (!context) {
17
+ throw new Error('useCarousel must be used within a <Carousel />')
18
+ }
19
+
20
+ return context
21
+ }
22
+
23
+ const Carousel = React.forwardRef<HTMLElement, React.HTMLAttributes<HTMLDivElement> & CarouselProps>(
24
+ ({ orientation = 'horizontal', opts, setApi, plugins, className, children, dir, ...props }, ref) => {
25
+ const direction = useDirection(dir as Direction)
26
+ const [carouselRef, api] = useEmblaCarousel(
27
+ {
28
+ ...opts,
29
+ axis: orientation === 'horizontal' ? 'x' : 'y',
30
+ },
31
+ plugins,
32
+ )
33
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
34
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
35
+
36
+ const onSelect = React.useCallback((api: CarouselApi) => {
37
+ if (!api) {
38
+ return
39
+ }
40
+
41
+ setCanScrollPrev(api.canScrollPrev())
42
+ setCanScrollNext(api.canScrollNext())
43
+ }, [])
44
+
45
+ const scrollPrev = React.useCallback(() => {
46
+ api?.scrollPrev()
47
+ }, [api])
48
+
49
+ const scrollNext = React.useCallback(() => {
50
+ api?.scrollNext()
51
+ }, [api])
52
+
53
+ const handleKeyDown = React.useCallback(
54
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
55
+ if ((event.key === 'ArrowLeft' && direction === 'ltr') || (event.key === 'ArrowRight' && direction === 'rtl')) {
56
+ event.preventDefault()
57
+ scrollPrev()
58
+ } else if (
59
+ (event.key === 'ArrowRight' && direction === 'ltr') ||
60
+ (event.key === 'ArrowLeft' && direction === 'rtl')
61
+ ) {
62
+ event.preventDefault()
63
+ scrollNext()
64
+ }
65
+ },
66
+ [scrollPrev, scrollNext, direction],
67
+ )
68
+
69
+ React.useEffect(() => {
70
+ if (!api || !setApi) {
71
+ return
72
+ }
73
+
74
+ setApi(api)
75
+ }, [api, setApi])
76
+
77
+ React.useEffect(() => {
78
+ if (!api) {
79
+ return
80
+ }
81
+
82
+ onSelect(api)
83
+ api.on('reInit', onSelect)
84
+ api.on('select', onSelect)
85
+
86
+ return () => {
87
+ api?.off('select', onSelect)
88
+ }
89
+ }, [api, onSelect])
90
+
91
+ return (
92
+ <CarouselContext.Provider
93
+ value={{
94
+ api: api,
95
+ canScrollNext,
96
+ canScrollPrev,
97
+ carouselRef,
98
+ opts,
99
+ orientation: orientation || (opts?.axis === 'y' ? 'vertical' : 'horizontal'),
100
+ scrollNext,
101
+ scrollPrev,
102
+ }}>
103
+ <section
104
+ aria-roledescription="carousel"
105
+ className={cn('relative', className)}
106
+ data-slot="carousel"
107
+ dir={direction}
108
+ onKeyDownCapture={handleKeyDown}
109
+ ref={ref}
110
+ {...props}>
111
+ {children}
112
+ </section>
113
+ </CarouselContext.Provider>
114
+ )
115
+ },
116
+ )
117
+ Carousel.displayName = 'Carousel'
118
+
119
+ const CarouselContent = React.forwardRef<HTMLUListElement, React.HTMLAttributes<HTMLUListElement>>(
120
+ ({ className, ...props }, ref) => {
121
+ const { carouselRef, orientation } = useCarousel()
122
+
123
+ return (
124
+ <div className="overflow-hidden" data-slot="carousel-content" ref={carouselRef}>
125
+ <ul
126
+ className={cn('flex', orientation === 'horizontal' ? '-ms-4' : '-mt-4 flex-col', className)}
127
+ ref={ref}
128
+ {...props}
129
+ />
130
+ </div>
131
+ )
132
+ },
133
+ )
134
+ CarouselContent.displayName = 'CarouselContent'
135
+
136
+ const CarouselItem = React.forwardRef<HTMLLIElement, React.HTMLAttributes<HTMLLIElement>>(
137
+ ({ className, ...props }, ref) => {
138
+ const { orientation } = useCarousel()
139
+
140
+ return (
141
+ <li
142
+ className={cn('min-w-0 shrink-0 grow-0 basis-full', orientation === 'horizontal' ? 'ps-4' : 'pt-4', className)}
143
+ data-slot="carousel-item"
144
+ ref={ref}
145
+ {...props}
146
+ aria-roledescription="slide"
147
+ />
148
+ )
149
+ },
150
+ )
151
+ CarouselItem.displayName = 'CarouselItem'
152
+
153
+ const CarouselPrevious = React.forwardRef<
154
+ React.ComponentRef<typeof Button>,
155
+ React.ComponentPropsWithoutRef<typeof Button> & { text?: string }
156
+ >(({ className, variant = 'outline', size = 'icon', text = 'Previous slide', ...props }, ref) => {
157
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
158
+
159
+ return (
160
+ <Button
161
+ className={cn(
162
+ 'absolute h-8 w-8 rounded-full',
163
+ orientation === 'horizontal'
164
+ ? '-start-12 top-1/2 -translate-y-1/2'
165
+ : '-top-12 left-1/2 -translate-x-1/2 rotate-90',
166
+ className,
167
+ )}
168
+ data-slot="carousel-previous"
169
+ disabled={!canScrollPrev}
170
+ onClick={scrollPrev}
171
+ ref={ref}
172
+ size={size}
173
+ variant={variant}
174
+ {...props}>
175
+ <ArrowLeft aria-hidden="true" className="h-4 w-4 rtl:rotate-180" />
176
+ <span className="sr-only">{text}</span>
177
+ </Button>
178
+ )
179
+ })
180
+ CarouselPrevious.displayName = 'CarouselPrevious'
181
+
182
+ const CarouselNext = React.forwardRef<
183
+ React.ComponentRef<typeof Button>,
184
+ React.ComponentPropsWithoutRef<typeof Button> & { text?: string }
185
+ >(({ className, variant = 'outline', size = 'icon', text = 'Next slide', ...props }, ref) => {
186
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
187
+
188
+ return (
189
+ <Button
190
+ className={cn(
191
+ 'absolute h-8 w-8 rounded-full',
192
+ orientation === 'horizontal'
193
+ ? '-end-12 top-1/2 -translate-y-1/2'
194
+ : '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',
195
+ className,
196
+ )}
197
+ data-slot="carousel-next"
198
+ disabled={!canScrollNext}
199
+ onClick={scrollNext}
200
+ ref={ref}
201
+ size={size}
202
+ variant={variant}
203
+ {...props}>
204
+ <ArrowRight aria-hidden="true" className="h-4 w-4 rtl:rotate-180" />
205
+ <span className="sr-only">{text}</span>
206
+ </Button>
207
+ )
208
+ })
209
+ CarouselNext.displayName = 'CarouselNext'
210
+
211
+ export { type CarouselApi, Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext }
@@ -0,0 +1,23 @@
1
+ import type useEmblaCarousel from 'embla-carousel-react'
2
+ import type { UseEmblaCarouselType } from 'embla-carousel-react'
3
+
4
+ export type CarouselApi = UseEmblaCarouselType[1]
5
+ export type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
6
+ export type CarouselOptions = UseCarouselParameters[0]
7
+ export type CarouselPlugin = UseCarouselParameters[1]
8
+
9
+ export type CarouselProps = {
10
+ opts?: CarouselOptions
11
+ plugins?: CarouselPlugin
12
+ orientation?: 'horizontal' | 'vertical'
13
+ setApi?: (api: CarouselApi) => void
14
+ }
15
+
16
+ export type CarouselContextProps = {
17
+ carouselRef: ReturnType<typeof useEmblaCarousel>[0]
18
+ api: ReturnType<typeof useEmblaCarousel>[1]
19
+ scrollPrev: () => void
20
+ scrollNext: () => void
21
+ canScrollPrev: boolean
22
+ canScrollNext: boolean
23
+ } & CarouselProps
@@ -0,0 +1,2 @@
1
+ export * from './carousel'
2
+ export * from './carousel.types'
@@ -0,0 +1,27 @@
1
+ import type { ChartConfig } from './chart.types'
2
+
3
+ // Helper to extract item config from a payload.
4
+ export function getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) {
5
+ if (typeof payload !== 'object' || payload === null) {
6
+ return undefined
7
+ }
8
+
9
+ const payloadPayload =
10
+ 'payload' in payload && typeof payload.payload === 'object' && payload.payload !== null
11
+ ? payload.payload
12
+ : undefined
13
+
14
+ let configLabelKey: string = key
15
+
16
+ if (key in payload && typeof payload[key as keyof typeof payload] === 'string') {
17
+ configLabelKey = payload[key as keyof typeof payload] as string
18
+ } else if (
19
+ payloadPayload &&
20
+ key in payloadPayload &&
21
+ typeof payloadPayload[key as keyof typeof payloadPayload] === 'string'
22
+ ) {
23
+ configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string
24
+ }
25
+
26
+ return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config]
27
+ }
@@ -0,0 +1,260 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@gentleduck/libs/cn'
4
+ import { type Direction, useDirection } from '@gentleduck/primitives/direction'
5
+ import * as React from 'react'
6
+ import * as RechartsPrimitive from 'recharts'
7
+ import { getPayloadConfigFromPayload } from './chart.libs'
8
+ import type {
9
+ ChartContainerProps,
10
+ ChartContextProps,
11
+ ChartLegendContentProps,
12
+ ChartStyleProps,
13
+ ChartTooltipContentProps,
14
+ } from './chart.types'
15
+
16
+ // Format: { THEME_NAME: CSS_SELECTOR }
17
+ export const THEMES = { dark: '.dark', light: '' } as const
18
+
19
+ const ChartContext = React.createContext<ChartContextProps | null>(null)
20
+
21
+ function useChart() {
22
+ const context = React.useContext(ChartContext)
23
+
24
+ if (!context) {
25
+ throw new Error('useChart must be used within a <ChartContainer />')
26
+ }
27
+
28
+ return context
29
+ }
30
+
31
+ const ChartContainer = ({ id, className, children, config, ref, dir, ...props }: ChartContainerProps) => {
32
+ const uniqueId = React.useId()
33
+ const chartId = `chart-${id || uniqueId.replace(/:/g, '')}`
34
+ const direction = useDirection(dir as Direction)
35
+
36
+ return (
37
+ <ChartContext.Provider value={{ config }}>
38
+ <div
39
+ className={cn(
40
+ "flex aspect-video min-w-0 justify-center text-xs [&_.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-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.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 [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-hidden [&_.recharts-surface]:outline-hidden",
41
+ className,
42
+ )}
43
+ {...props}
44
+ data-chart={chartId}
45
+ data-slot="chart-container"
46
+ dir={direction}
47
+ ref={ref}>
48
+ <ChartStyle config={config} id={chartId} />
49
+ <RechartsPrimitive.ResponsiveContainer minWidth={0}>{children}</RechartsPrimitive.ResponsiveContainer>
50
+ </div>
51
+ </ChartContext.Provider>
52
+ )
53
+ }
54
+
55
+ const ChartStyle = ({ id, config }: ChartStyleProps) => {
56
+ const colorConfig = Object.entries(config).filter(([_, config]) => config.theme || config.color)
57
+
58
+ if (!colorConfig.length) {
59
+ return null
60
+ }
61
+
62
+ return (
63
+ <style
64
+ dangerouslySetInnerHTML={{
65
+ __html: Object.entries(THEMES)
66
+ .map(
67
+ ([theme, prefix]) => `
68
+ ${prefix} [data-chart=${id}] {
69
+ ${colorConfig
70
+ .map(([key, itemConfig]) => {
71
+ const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || itemConfig.color
72
+ return color ? ` --color-${key}: ${color};` : null
73
+ })
74
+ .join('\n')}
75
+ }
76
+ `,
77
+ )
78
+ .join('\n'),
79
+ }}
80
+ />
81
+ )
82
+ }
83
+
84
+ const ChartTooltip = RechartsPrimitive.Tooltip
85
+
86
+ const ChartTooltipContent = ({
87
+ active,
88
+ payload,
89
+ className,
90
+ indicator = 'dot',
91
+ hideLabel = false,
92
+ hideIndicator = false,
93
+ label,
94
+ labelFormatter,
95
+ labelClassName,
96
+ formatter,
97
+ ref,
98
+ color,
99
+ nameKey,
100
+ labelKey,
101
+ }: ChartTooltipContentProps) => {
102
+ const { config } = useChart()
103
+
104
+ const tooltipLabel = React.useMemo(() => {
105
+ if (hideLabel || !payload?.length) {
106
+ return null
107
+ }
108
+
109
+ const [item] = payload
110
+ const key = `${labelKey || item?.dataKey || item?.name || 'value'}`
111
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
112
+ const value =
113
+ !labelKey && typeof label === 'string' ? config[label as keyof typeof config]?.label || label : itemConfig?.label
114
+
115
+ if (labelFormatter) {
116
+ return (
117
+ <div className={cn('font-medium', labelClassName)} data-slot="tooltip-label">
118
+ {labelFormatter(value, payload)}
119
+ </div>
120
+ )
121
+ }
122
+
123
+ if (!value) {
124
+ return null
125
+ }
126
+
127
+ return (
128
+ <div className={cn('font-medium', labelClassName)} data-slot="tooltip-label">
129
+ {value}
130
+ </div>
131
+ )
132
+ }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey])
133
+
134
+ if (!active || !payload?.length) {
135
+ return null
136
+ }
137
+
138
+ const nestLabel = payload.length === 1 && indicator !== 'dot'
139
+
140
+ return (
141
+ <div
142
+ className={cn(
143
+ 'grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl',
144
+ className,
145
+ )}
146
+ data-slot="tooltip-content"
147
+ ref={ref}>
148
+ {nestLabel ? null : tooltipLabel}
149
+ <div className="grid gap-1.5" data-slot="tooltip-items">
150
+ {payload.map((item: RechartsPrimitive.TooltipPayloadEntry<string | number, string | number>, index: number) => {
151
+ const key = `${nameKey || item.name || item.dataKey || 'value'}`
152
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
153
+ const indicatorColor = color || item.payload?.fill || item.color
154
+
155
+ return (
156
+ <div
157
+ className={cn(
158
+ 'flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground',
159
+ indicator === 'dot' && 'items-center',
160
+ )}
161
+ data-slot="tooltip-item"
162
+ key={String(item.dataKey)}>
163
+ {formatter && item?.value !== undefined && item.name ? (
164
+ formatter(item.value, item.name, item, index, item.payload)
165
+ ) : (
166
+ <>
167
+ {itemConfig?.icon ? (
168
+ <itemConfig.icon />
169
+ ) : (
170
+ !hideIndicator && (
171
+ <div
172
+ className={cn('shrink-0 rounded-[2px] border-[var(--color-border)] bg-[var(--color-bg)]', {
173
+ 'h-2.5 w-2.5': indicator === 'dot',
174
+ 'my-0.5': nestLabel && indicator === 'dashed',
175
+ 'w-0 border-[1.5px] border-dashed bg-transparent': indicator === 'dashed',
176
+ 'w-1': indicator === 'line',
177
+ })}
178
+ style={
179
+ {
180
+ '--color-bg': indicatorColor,
181
+ '--color-border': indicatorColor,
182
+ } as React.CSSProperties
183
+ }
184
+ />
185
+ )
186
+ )}
187
+ <div
188
+ className={cn(
189
+ 'flex flex-1 justify-between leading-none',
190
+ nestLabel ? 'items-end' : 'items-center',
191
+ )}>
192
+ <div className="grid gap-1.5">
193
+ {nestLabel ? tooltipLabel : null}
194
+ <span className="text-muted-foreground">{itemConfig?.label || item.name}</span>
195
+ </div>
196
+ {item.value && (
197
+ <span className="font-medium font-mono text-foreground tabular-nums">
198
+ {item.value.toLocaleString()}
199
+ </span>
200
+ )}
201
+ </div>
202
+ </>
203
+ )}
204
+ </div>
205
+ )
206
+ })}
207
+ </div>
208
+ </div>
209
+ )
210
+ }
211
+
212
+ const ChartLegend = RechartsPrimitive.Legend
213
+
214
+ const ChartLegendContent = ({
215
+ className,
216
+ hideIcon = false,
217
+ payload,
218
+ verticalAlign = 'bottom',
219
+ ref,
220
+ nameKey,
221
+ }: ChartLegendContentProps) => {
222
+ const { config } = useChart()
223
+
224
+ if (!payload?.length) {
225
+ return null
226
+ }
227
+
228
+ return (
229
+ <div
230
+ className={cn('flex items-center justify-center gap-4', verticalAlign === 'top' ? 'pb-3' : 'pt-3', className)}
231
+ data-slot="legend-content"
232
+ ref={ref}>
233
+ {payload?.map((item: RechartsPrimitive.LegendPayload) => {
234
+ const key = `${nameKey || item.dataKey || 'value'}`
235
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
236
+
237
+ return (
238
+ <div
239
+ className={cn('flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground')}
240
+ data-slot="legend-item"
241
+ key={item.value}>
242
+ {itemConfig?.icon && !hideIcon ? (
243
+ <itemConfig.icon />
244
+ ) : (
245
+ <div
246
+ className="h-2 w-2 shrink-0 rounded-[2px]"
247
+ style={{
248
+ backgroundColor: item.color,
249
+ }}
250
+ />
251
+ )}
252
+ {itemConfig?.label}
253
+ </div>
254
+ )
255
+ })}
256
+ </div>
257
+ )
258
+ }
259
+
260
+ export { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, ChartStyle }
@@ -0,0 +1,38 @@
1
+ import type * as RechartsPrimitive from 'recharts'
2
+ import type { THEMES } from './chart'
3
+ export type ChartConfig = {
4
+ [k in string]: {
5
+ label?: React.ReactNode
6
+ icon?: React.ComponentType
7
+ } & ({ color?: string; theme?: never } | { color?: never; theme: Record<keyof typeof THEMES, string> })
8
+ }
9
+
10
+ export type ChartContextProps = {
11
+ config: ChartConfig
12
+ }
13
+
14
+ export type ChartContainerProps = React.HTMLProps<HTMLDivElement> & {
15
+ config: ChartConfig
16
+ children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>['children']
17
+ }
18
+ export type ChartStyleProps = { id: string; config: ChartConfig }
19
+
20
+ export type ChartTooltipContentProps = Partial<
21
+ Omit<RechartsPrimitive.TooltipProps<string | number, string | number>, 'content'>
22
+ > &
23
+ React.HTMLProps<HTMLDivElement> & {
24
+ hideLabel?: boolean
25
+ hideIndicator?: boolean
26
+ indicator?: 'line' | 'dot' | 'dashed'
27
+ nameKey?: string
28
+ labelKey?: string
29
+ payload?: RechartsPrimitive.TooltipPayloadEntry<string | number, string | number>[]
30
+ active?: boolean
31
+ }
32
+
33
+ export type ChartLegendContentProps = React.HTMLProps<HTMLDivElement> & {
34
+ payload?: RechartsPrimitive.LegendPayload[]
35
+ verticalAlign?: 'top' | 'middle' | 'bottom'
36
+ hideIcon?: boolean
37
+ nameKey?: string
38
+ }
@@ -0,0 +1,3 @@
1
+ export * from './chart'
2
+ export * from './chart.libs'
3
+ export * from './chart.types'