@beemafrica/ui 0.1.13 → 0.1.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beemafrica/ui",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "Shared Beem UI components built on shadcn/Radix",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -27,6 +27,7 @@
27
27
  "lucide-react": "^1.31.0",
28
28
  "next-themes": "^0.4.6",
29
29
  "radix-ui": "^1.6.7",
30
+ "recharts": "^3.8.0",
30
31
  "shadcn": "^4.16.2",
31
32
  "tailwind-merge": "^3.6.0",
32
33
  "tailwind-scrollbar": "^4.0.2",
@@ -0,0 +1,372 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "cn"
5
+ import * as RechartsPrimitive from "recharts"
6
+ import type { TooltipValueType } from "recharts"
7
+
8
+ // Format: { THEME_NAME: CSS_SELECTOR }
9
+ const THEMES = { light: "", dark: ".dark" } as const
10
+
11
+ const INITIAL_DIMENSION = { width: 320, height: 200 } as const
12
+ type TooltipNameType = number | string
13
+
14
+ export type ChartConfig = Record<
15
+ string,
16
+ {
17
+ label?: React.ReactNode
18
+ icon?: React.ComponentType
19
+ } & (
20
+ | { color?: string; theme?: never }
21
+ | { color?: never; theme: Record<keyof typeof THEMES, string> }
22
+ )
23
+ >
24
+
25
+ type ChartContextProps = {
26
+ config: ChartConfig
27
+ }
28
+
29
+ const ChartContext = React.createContext<ChartContextProps | null>(null)
30
+
31
+ function useChart() {
32
+ const context = React.useContext(ChartContext)
33
+
34
+ if (!context) {
35
+ throw new Error("useChart must be used within a <ChartContainer />")
36
+ }
37
+
38
+ return context
39
+ }
40
+
41
+ function ChartContainer({
42
+ id,
43
+ className,
44
+ children,
45
+ config,
46
+ initialDimension = INITIAL_DIMENSION,
47
+ ...props
48
+ }: React.ComponentProps<"div"> & {
49
+ config: ChartConfig
50
+ children: React.ComponentProps<
51
+ typeof RechartsPrimitive.ResponsiveContainer
52
+ >["children"]
53
+ initialDimension?: {
54
+ width: number
55
+ height: number
56
+ }
57
+ }) {
58
+ const uniqueId = React.useId()
59
+ const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}`
60
+
61
+ return (
62
+ <ChartContext.Provider value={{ config }}>
63
+ <div
64
+ data-slot="chart"
65
+ data-chart={chartId}
66
+ className={cn(
67
+ "flex aspect-video 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]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
68
+ className
69
+ )}
70
+ {...props}
71
+ >
72
+ <ChartStyle id={chartId} config={config} />
73
+ <RechartsPrimitive.ResponsiveContainer
74
+ initialDimension={initialDimension}
75
+ >
76
+ {children}
77
+ </RechartsPrimitive.ResponsiveContainer>
78
+ </div>
79
+ </ChartContext.Provider>
80
+ )
81
+ }
82
+
83
+ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
84
+ const colorConfig = Object.entries(config).filter(
85
+ ([, config]) => config.theme ?? config.color
86
+ )
87
+
88
+ if (!colorConfig.length) {
89
+ return null
90
+ }
91
+
92
+ return (
93
+ <style
94
+ dangerouslySetInnerHTML={{
95
+ __html: Object.entries(THEMES)
96
+ .map(
97
+ ([theme, prefix]) => `
98
+ ${prefix} [data-chart=${id}] {
99
+ ${colorConfig
100
+ .map(([key, itemConfig]) => {
101
+ const color =
102
+ itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ??
103
+ itemConfig.color
104
+ return color ? ` --color-${key}: ${color};` : null
105
+ })
106
+ .join("\n")}
107
+ }
108
+ `
109
+ )
110
+ .join("\n"),
111
+ }}
112
+ />
113
+ )
114
+ }
115
+
116
+ const ChartTooltip = RechartsPrimitive.Tooltip
117
+
118
+ function ChartTooltipContent({
119
+ active,
120
+ payload,
121
+ className,
122
+ indicator = "dot",
123
+ hideLabel = false,
124
+ hideIndicator = false,
125
+ label,
126
+ labelFormatter,
127
+ labelClassName,
128
+ formatter,
129
+ color,
130
+ nameKey,
131
+ labelKey,
132
+ }: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
133
+ React.ComponentProps<"div"> & {
134
+ hideLabel?: boolean
135
+ hideIndicator?: boolean
136
+ indicator?: "line" | "dot" | "dashed"
137
+ nameKey?: string
138
+ labelKey?: string
139
+ } & Omit<
140
+ RechartsPrimitive.DefaultTooltipContentProps<
141
+ TooltipValueType,
142
+ TooltipNameType
143
+ >,
144
+ "accessibilityLayer"
145
+ >) {
146
+ const { config } = useChart()
147
+
148
+ const tooltipLabel = React.useMemo(() => {
149
+ if (hideLabel || !payload?.length) {
150
+ return null
151
+ }
152
+
153
+ const [item] = payload
154
+ const key = `${labelKey ?? item?.dataKey ?? item?.name ?? "value"}`
155
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
156
+ const value =
157
+ !labelKey && typeof label === "string"
158
+ ? (config[label]?.label ?? label)
159
+ : itemConfig?.label
160
+
161
+ if (labelFormatter) {
162
+ return (
163
+ <div className={cn("font-medium", labelClassName)}>
164
+ {labelFormatter(value, payload)}
165
+ </div>
166
+ )
167
+ }
168
+
169
+ if (!value) {
170
+ return null
171
+ }
172
+
173
+ return <div className={cn("font-medium", labelClassName)}>{value}</div>
174
+ }, [
175
+ label,
176
+ labelFormatter,
177
+ payload,
178
+ hideLabel,
179
+ labelClassName,
180
+ config,
181
+ labelKey,
182
+ ])
183
+
184
+ if (!active || !payload?.length) {
185
+ return null
186
+ }
187
+
188
+ const nestLabel = payload.length === 1 && indicator !== "dot"
189
+
190
+ return (
191
+ <div
192
+ className={cn(
193
+ "grid min-w-32 items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
194
+ className
195
+ )}
196
+ >
197
+ {!nestLabel ? tooltipLabel : null}
198
+ <div className="grid gap-1.5">
199
+ {payload
200
+ .filter((item) => item.type !== "none")
201
+ .map((item, index) => {
202
+ const key = `${nameKey ?? item.name ?? item.dataKey ?? "value"}`
203
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
204
+ const indicatorColor = color ?? item.payload?.fill ?? item.color
205
+
206
+ return (
207
+ <div
208
+ key={index}
209
+ className={cn(
210
+ "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
211
+ indicator === "dot" && "items-center"
212
+ )}
213
+ >
214
+ {formatter && item?.value !== undefined && item.name ? (
215
+ formatter(item.value, item.name, item, index, item.payload)
216
+ ) : (
217
+ <>
218
+ {itemConfig?.icon ? (
219
+ <itemConfig.icon />
220
+ ) : (
221
+ !hideIndicator && (
222
+ <div
223
+ className={cn(
224
+ "shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
225
+ {
226
+ "h-2.5 w-2.5": indicator === "dot",
227
+ "w-1": indicator === "line",
228
+ "w-0 border-[1.5px] border-dashed bg-transparent":
229
+ indicator === "dashed",
230
+ "my-0.5": nestLabel && indicator === "dashed",
231
+ }
232
+ )}
233
+ style={
234
+ {
235
+ "--color-bg": indicatorColor,
236
+ "--color-border": indicatorColor,
237
+ } as React.CSSProperties
238
+ }
239
+ />
240
+ )
241
+ )}
242
+ <div
243
+ className={cn(
244
+ "flex flex-1 justify-between leading-none",
245
+ nestLabel ? "items-end" : "items-center"
246
+ )}
247
+ >
248
+ <div className="grid gap-1.5">
249
+ {nestLabel ? tooltipLabel : null}
250
+ <span className="text-muted-foreground">
251
+ {itemConfig?.label ?? item.name}
252
+ </span>
253
+ </div>
254
+ {item.value != null && (
255
+ <span className="font-mono font-medium text-foreground tabular-nums">
256
+ {typeof item.value === "number"
257
+ ? item.value.toLocaleString()
258
+ : String(item.value)}
259
+ </span>
260
+ )}
261
+ </div>
262
+ </>
263
+ )}
264
+ </div>
265
+ )
266
+ })}
267
+ </div>
268
+ </div>
269
+ )
270
+ }
271
+
272
+ const ChartLegend = RechartsPrimitive.Legend
273
+
274
+ function ChartLegendContent({
275
+ className,
276
+ hideIcon = false,
277
+ payload,
278
+ verticalAlign = "bottom",
279
+ nameKey,
280
+ }: React.ComponentProps<"div"> & {
281
+ hideIcon?: boolean
282
+ nameKey?: string
283
+ } & RechartsPrimitive.DefaultLegendContentProps) {
284
+ const { config } = useChart()
285
+
286
+ if (!payload?.length) {
287
+ return null
288
+ }
289
+
290
+ return (
291
+ <div
292
+ className={cn(
293
+ "flex items-center justify-center gap-4",
294
+ verticalAlign === "top" ? "pb-3" : "pt-3",
295
+ className
296
+ )}
297
+ >
298
+ {payload
299
+ .filter((item) => item.type !== "none")
300
+ .map((item, index) => {
301
+ const key = `${nameKey ?? item.dataKey ?? "value"}`
302
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
303
+
304
+ return (
305
+ <div
306
+ key={index}
307
+ className={cn(
308
+ "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
309
+ )}
310
+ >
311
+ {itemConfig?.icon && !hideIcon ? (
312
+ <itemConfig.icon />
313
+ ) : (
314
+ <div
315
+ className="h-2 w-2 shrink-0 rounded-[2px]"
316
+ style={{
317
+ backgroundColor: item.color,
318
+ }}
319
+ />
320
+ )}
321
+ {itemConfig?.label}
322
+ </div>
323
+ )
324
+ })}
325
+ </div>
326
+ )
327
+ }
328
+
329
+ function getPayloadConfigFromPayload(
330
+ config: ChartConfig,
331
+ payload: unknown,
332
+ key: string
333
+ ) {
334
+ if (typeof payload !== "object" || payload === null) {
335
+ return undefined
336
+ }
337
+
338
+ const payloadPayload =
339
+ "payload" in payload &&
340
+ typeof payload.payload === "object" &&
341
+ payload.payload !== null
342
+ ? payload.payload
343
+ : undefined
344
+
345
+ let configLabelKey: string = key
346
+
347
+ if (
348
+ key in payload &&
349
+ typeof payload[key as keyof typeof payload] === "string"
350
+ ) {
351
+ configLabelKey = payload[key as keyof typeof payload] as string
352
+ } else if (
353
+ payloadPayload &&
354
+ key in payloadPayload &&
355
+ typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
356
+ ) {
357
+ configLabelKey = payloadPayload[
358
+ key as keyof typeof payloadPayload
359
+ ] as string
360
+ }
361
+
362
+ return configLabelKey in config ? config[configLabelKey] : config[key]
363
+ }
364
+
365
+ export {
366
+ ChartContainer,
367
+ ChartTooltip,
368
+ ChartTooltipContent,
369
+ ChartLegend,
370
+ ChartLegendContent,
371
+ ChartStyle,
372
+ }
@@ -133,7 +133,7 @@ function Ticket({
133
133
  Product details
134
134
  </Button>
135
135
  </CollapsibleTrigger>
136
- <CollapsibleContent className="scrollbar scrollbar-thin scrollbar-thumb-rounded-full scrollbar-track-rounded-full flex max-h-[150px] flex-col items-start gap-2 overflow-y-auto p-2.5 pt-0 text-sm">
136
+ <CollapsibleContent className="flex max-h-[150px] flex-col items-start gap-2 overflow-y-auto p-2.5 pt-0 text-sm">
137
137
  {history.map((hs, index) => {
138
138
  return (
139
139
  // biome-ignore lint/suspicious/noArrayIndexKey: <explanation>