@brightweblabs/ui 0.1.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.
@@ -0,0 +1,381 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as RechartsPrimitive from "recharts"
5
+
6
+ import { cn } from "../lib/utils"
7
+
8
+ // Format: { THEME_NAME: CSS_SELECTOR }
9
+ const THEMES = { light: "", dark: ".dark" } as const
10
+
11
+ export type ChartConfig = {
12
+ [k in string]: {
13
+ label?: React.ReactNode
14
+ icon?: React.ComponentType
15
+ } & (
16
+ | { color?: string; theme?: never }
17
+ | { color?: never; theme: Record<keyof typeof THEMES, string> }
18
+ )
19
+ }
20
+
21
+ type ChartContextProps = {
22
+ config: ChartConfig
23
+ }
24
+
25
+ const ChartContext = React.createContext<ChartContextProps | null>(null)
26
+
27
+ function useChart() {
28
+ const context = React.useContext(ChartContext)
29
+
30
+ if (!context) {
31
+ throw new Error("useChart tem de ser usado dentro de um <ChartContainer />.")
32
+ }
33
+
34
+ return context
35
+ }
36
+
37
+ function ChartContainer({
38
+ id,
39
+ className,
40
+ children,
41
+ config,
42
+ ...props
43
+ }: React.ComponentProps<"div"> & {
44
+ config: ChartConfig
45
+ children: React.ComponentProps<
46
+ typeof RechartsPrimitive.ResponsiveContainer
47
+ >["children"]
48
+ }) {
49
+ const chartId = React.useMemo(() => {
50
+ if (id) {
51
+ return `chart-${id}`
52
+ }
53
+
54
+ const seed = Object.entries(config)
55
+ .sort(([a], [b]) => a.localeCompare(b))
56
+ .map(([key, itemConfig]) => {
57
+ const themeColors = itemConfig.theme
58
+ ? Object.entries(itemConfig.theme)
59
+ .sort(([a], [b]) => a.localeCompare(b))
60
+ .map(([theme, color]) => `${theme}:${color}`)
61
+ .join(",")
62
+ : ""
63
+
64
+ return `${key}:${itemConfig.color ?? ""}:${themeColors}`
65
+ })
66
+ .join("|")
67
+
68
+ let hash = 0
69
+ for (let index = 0; index < seed.length; index += 1) {
70
+ hash = (hash * 31 + seed.charCodeAt(index)) >>> 0
71
+ }
72
+
73
+ return `chart-${hash.toString(36)}`
74
+ }, [id, config])
75
+
76
+ return (
77
+ <ChartContext.Provider value={{ config }}>
78
+ <div
79
+ data-slot="chart"
80
+ data-chart={chartId}
81
+ className={cn(
82
+ "[&_.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",
83
+ className
84
+ )}
85
+ {...props}
86
+ >
87
+ <ChartStyle id={chartId} config={config} />
88
+ <RechartsPrimitive.ResponsiveContainer>
89
+ {children}
90
+ </RechartsPrimitive.ResponsiveContainer>
91
+ </div>
92
+ </ChartContext.Provider>
93
+ )
94
+ }
95
+
96
+ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
97
+ const colorConfig = Object.entries(config).filter(
98
+ ([, config]) => config.theme || config.color
99
+ )
100
+
101
+ if (!colorConfig.length) {
102
+ return null
103
+ }
104
+
105
+ return (
106
+ <style
107
+ dangerouslySetInnerHTML={{
108
+ __html: Object.entries(THEMES)
109
+ .map(
110
+ ([theme, prefix]) => `
111
+ ${prefix} [data-chart=${id}] {
112
+ ${colorConfig
113
+ .map(([key, itemConfig]) => {
114
+ const color =
115
+ itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
116
+ itemConfig.color
117
+ return color ? ` --color-${key}: ${color};` : null
118
+ })
119
+ .join("\n")}
120
+ }
121
+ `
122
+ )
123
+ .join("\n"),
124
+ }}
125
+ />
126
+ )
127
+ }
128
+
129
+ const ChartTooltip = RechartsPrimitive.Tooltip
130
+
131
+ function ChartTooltipContent({
132
+ active,
133
+ payload,
134
+ className,
135
+ indicator = "dot",
136
+ hideLabel = false,
137
+ hideIndicator = false,
138
+ label,
139
+ labelFormatter,
140
+ labelClassName,
141
+ formatter,
142
+ color,
143
+ nameKey,
144
+ labelKey,
145
+ }: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
146
+ React.ComponentProps<"div"> & {
147
+ hideLabel?: boolean
148
+ hideIndicator?: boolean
149
+ indicator?: "line" | "dot" | "dashed"
150
+ nameKey?: string
151
+ labelKey?: string
152
+ }) {
153
+ const { config } = useChart()
154
+
155
+ const tooltipLabel = React.useMemo(() => {
156
+ if (hideLabel || !payload?.length) {
157
+ return null
158
+ }
159
+
160
+ const [item] = payload
161
+ const key = `${labelKey || item?.dataKey || item?.name || "value"}`
162
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
163
+ const value =
164
+ !labelKey && typeof label === "string"
165
+ ? config[label as keyof typeof config]?.label || label
166
+ : itemConfig?.label
167
+
168
+ if (labelFormatter) {
169
+ return (
170
+ <div className={cn("font-medium", labelClassName)}>
171
+ {labelFormatter(value, payload)}
172
+ </div>
173
+ )
174
+ }
175
+
176
+ if (!value) {
177
+ return null
178
+ }
179
+
180
+ return <div className={cn("font-medium", labelClassName)}>{value}</div>
181
+ }, [
182
+ label,
183
+ labelFormatter,
184
+ payload,
185
+ hideLabel,
186
+ labelClassName,
187
+ config,
188
+ labelKey,
189
+ ])
190
+
191
+ if (!active || !payload?.length) {
192
+ return null
193
+ }
194
+
195
+ const nestLabel = payload.length === 1 && indicator !== "dot"
196
+
197
+ return (
198
+ <div
199
+ className={cn(
200
+ "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",
201
+ className
202
+ )}
203
+ >
204
+ {!nestLabel ? tooltipLabel : null}
205
+ <div className="grid gap-1.5">
206
+ {payload
207
+ .filter((item) => item.type !== "none")
208
+ .map((item, index) => {
209
+ const key = `${nameKey || item.name || item.dataKey || "value"}`
210
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
211
+ const indicatorColor = color || item.payload.fill || item.color
212
+
213
+ return (
214
+ <div
215
+ key={item.dataKey}
216
+ className={cn(
217
+ "[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
218
+ indicator === "dot" && "items-center"
219
+ )}
220
+ >
221
+ {formatter && item?.value !== undefined && item.name ? (
222
+ formatter(item.value, item.name, item, index, item.payload)
223
+ ) : (
224
+ <>
225
+ {itemConfig?.icon ? (
226
+ <itemConfig.icon />
227
+ ) : (
228
+ !hideIndicator && (
229
+ <div
230
+ className={cn(
231
+ "shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
232
+ {
233
+ "h-2.5 w-2.5": indicator === "dot",
234
+ "w-1": indicator === "line",
235
+ "w-0 border-[1.5px] border-dashed bg-transparent":
236
+ indicator === "dashed",
237
+ "my-0.5": nestLabel && indicator === "dashed",
238
+ }
239
+ )}
240
+ style={
241
+ {
242
+ "--color-bg": indicatorColor,
243
+ "--color-border": indicatorColor,
244
+ } as React.CSSProperties
245
+ }
246
+ />
247
+ )
248
+ )}
249
+ <div
250
+ className={cn(
251
+ "flex flex-1 justify-between leading-none",
252
+ nestLabel ? "items-end" : "items-center"
253
+ )}
254
+ >
255
+ <div className="grid gap-1.5">
256
+ {nestLabel ? tooltipLabel : null}
257
+ <span className="text-muted-foreground">
258
+ {itemConfig?.label || item.name}
259
+ </span>
260
+ </div>
261
+ {item.value && (
262
+ <span className="text-foreground font-mono font-medium tabular-nums">
263
+ {item.value.toLocaleString()}
264
+ </span>
265
+ )}
266
+ </div>
267
+ </>
268
+ )}
269
+ </div>
270
+ )
271
+ })}
272
+ </div>
273
+ </div>
274
+ )
275
+ }
276
+
277
+ const ChartLegend = RechartsPrimitive.Legend
278
+
279
+ function ChartLegendContent({
280
+ className,
281
+ hideIcon = false,
282
+ payload,
283
+ verticalAlign = "bottom",
284
+ nameKey,
285
+ }: React.ComponentProps<"div"> &
286
+ Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
287
+ hideIcon?: boolean
288
+ nameKey?: string
289
+ }) {
290
+ const { config } = useChart()
291
+
292
+ if (!payload?.length) {
293
+ return null
294
+ }
295
+
296
+ return (
297
+ <div
298
+ className={cn(
299
+ "flex items-center justify-center gap-4",
300
+ verticalAlign === "top" ? "pb-3" : "pt-3",
301
+ className
302
+ )}
303
+ >
304
+ {payload
305
+ .filter((item) => item.type !== "none")
306
+ .map((item) => {
307
+ const key = `${nameKey || item.dataKey || "value"}`
308
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
309
+
310
+ return (
311
+ <div
312
+ key={item.value}
313
+ className={cn(
314
+ "[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3"
315
+ )}
316
+ >
317
+ {itemConfig?.icon && !hideIcon ? (
318
+ <itemConfig.icon />
319
+ ) : (
320
+ <div
321
+ className="h-2 w-2 shrink-0 rounded-[2px]"
322
+ style={{
323
+ backgroundColor: item.color,
324
+ }}
325
+ />
326
+ )}
327
+ {itemConfig?.label}
328
+ </div>
329
+ )
330
+ })}
331
+ </div>
332
+ )
333
+ }
334
+
335
+ // Helper to extract item config from a payload.
336
+ function getPayloadConfigFromPayload(
337
+ config: ChartConfig,
338
+ payload: unknown,
339
+ key: string
340
+ ) {
341
+ if (typeof payload !== "object" || payload === null) {
342
+ return undefined
343
+ }
344
+
345
+ const payloadPayload =
346
+ "payload" in payload &&
347
+ typeof payload.payload === "object" &&
348
+ payload.payload !== null
349
+ ? payload.payload
350
+ : undefined
351
+
352
+ let configLabelKey: string = key
353
+
354
+ if (
355
+ key in payload &&
356
+ typeof payload[key as keyof typeof payload] === "string"
357
+ ) {
358
+ configLabelKey = payload[key as keyof typeof payload] as string
359
+ } else if (
360
+ payloadPayload &&
361
+ key in payloadPayload &&
362
+ typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
363
+ ) {
364
+ configLabelKey = payloadPayload[
365
+ key as keyof typeof payloadPayload
366
+ ] as string
367
+ }
368
+
369
+ return configLabelKey in config
370
+ ? config[configLabelKey]
371
+ : config[key as keyof typeof config]
372
+ }
373
+
374
+ export {
375
+ ChartContainer,
376
+ ChartTooltip,
377
+ ChartTooltipContent,
378
+ ChartLegend,
379
+ ChartLegendContent,
380
+ ChartStyle,
381
+ }
@@ -0,0 +1,257 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5
+ import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
6
+
7
+ import { cn } from "../lib/utils"
8
+
9
+ function DropdownMenu({
10
+ ...props
11
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
12
+ return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
13
+ }
14
+
15
+ function DropdownMenuPortal({
16
+ ...props
17
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
18
+ return (
19
+ <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
20
+ )
21
+ }
22
+
23
+ function DropdownMenuTrigger({
24
+ ...props
25
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
26
+ return (
27
+ <DropdownMenuPrimitive.Trigger
28
+ data-slot="dropdown-menu-trigger"
29
+ {...props}
30
+ />
31
+ )
32
+ }
33
+
34
+ function DropdownMenuContent({
35
+ className,
36
+ sideOffset = 4,
37
+ ...props
38
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
39
+ return (
40
+ <DropdownMenuPrimitive.Portal>
41
+ <DropdownMenuPrimitive.Content
42
+ data-slot="dropdown-menu-content"
43
+ sideOffset={sideOffset}
44
+ className={cn(
45
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-[1300] max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+ </DropdownMenuPrimitive.Portal>
51
+ )
52
+ }
53
+
54
+ function DropdownMenuGroup({
55
+ ...props
56
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
57
+ return (
58
+ <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
59
+ )
60
+ }
61
+
62
+ function DropdownMenuItem({
63
+ className,
64
+ inset,
65
+ variant = "default",
66
+ ...props
67
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
68
+ inset?: boolean
69
+ variant?: "default" | "destructive"
70
+ }) {
71
+ return (
72
+ <DropdownMenuPrimitive.Item
73
+ data-slot="dropdown-menu-item"
74
+ data-inset={inset}
75
+ data-variant={variant}
76
+ className={cn(
77
+ "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
78
+ className
79
+ )}
80
+ {...props}
81
+ />
82
+ )
83
+ }
84
+
85
+ function DropdownMenuCheckboxItem({
86
+ className,
87
+ children,
88
+ checked,
89
+ ...props
90
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
91
+ return (
92
+ <DropdownMenuPrimitive.CheckboxItem
93
+ data-slot="dropdown-menu-checkbox-item"
94
+ className={cn(
95
+ "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
96
+ className
97
+ )}
98
+ checked={checked}
99
+ {...props}
100
+ >
101
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
102
+ <DropdownMenuPrimitive.ItemIndicator>
103
+ <CheckIcon className="size-4" />
104
+ </DropdownMenuPrimitive.ItemIndicator>
105
+ </span>
106
+ {children}
107
+ </DropdownMenuPrimitive.CheckboxItem>
108
+ )
109
+ }
110
+
111
+ function DropdownMenuRadioGroup({
112
+ ...props
113
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
114
+ return (
115
+ <DropdownMenuPrimitive.RadioGroup
116
+ data-slot="dropdown-menu-radio-group"
117
+ {...props}
118
+ />
119
+ )
120
+ }
121
+
122
+ function DropdownMenuRadioItem({
123
+ className,
124
+ children,
125
+ ...props
126
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
127
+ return (
128
+ <DropdownMenuPrimitive.RadioItem
129
+ data-slot="dropdown-menu-radio-item"
130
+ className={cn(
131
+ "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
132
+ className
133
+ )}
134
+ {...props}
135
+ >
136
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
137
+ <DropdownMenuPrimitive.ItemIndicator>
138
+ <CircleIcon className="size-2 fill-current" />
139
+ </DropdownMenuPrimitive.ItemIndicator>
140
+ </span>
141
+ {children}
142
+ </DropdownMenuPrimitive.RadioItem>
143
+ )
144
+ }
145
+
146
+ function DropdownMenuLabel({
147
+ className,
148
+ inset,
149
+ ...props
150
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
151
+ inset?: boolean
152
+ }) {
153
+ return (
154
+ <DropdownMenuPrimitive.Label
155
+ data-slot="dropdown-menu-label"
156
+ data-inset={inset}
157
+ className={cn(
158
+ "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
159
+ className
160
+ )}
161
+ {...props}
162
+ />
163
+ )
164
+ }
165
+
166
+ function DropdownMenuSeparator({
167
+ className,
168
+ ...props
169
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
170
+ return (
171
+ <DropdownMenuPrimitive.Separator
172
+ data-slot="dropdown-menu-separator"
173
+ className={cn("bg-border -mx-1 my-1 h-px", className)}
174
+ {...props}
175
+ />
176
+ )
177
+ }
178
+
179
+ function DropdownMenuShortcut({
180
+ className,
181
+ ...props
182
+ }: React.ComponentProps<"span">) {
183
+ return (
184
+ <span
185
+ data-slot="dropdown-menu-shortcut"
186
+ className={cn(
187
+ "text-muted-foreground ml-auto text-xs tracking-widest",
188
+ className
189
+ )}
190
+ {...props}
191
+ />
192
+ )
193
+ }
194
+
195
+ function DropdownMenuSub({
196
+ ...props
197
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
198
+ return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
199
+ }
200
+
201
+ function DropdownMenuSubTrigger({
202
+ className,
203
+ inset,
204
+ children,
205
+ ...props
206
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
207
+ inset?: boolean
208
+ }) {
209
+ return (
210
+ <DropdownMenuPrimitive.SubTrigger
211
+ data-slot="dropdown-menu-sub-trigger"
212
+ data-inset={inset}
213
+ className={cn(
214
+ "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
215
+ className
216
+ )}
217
+ {...props}
218
+ >
219
+ {children}
220
+ <ChevronRightIcon className="ml-auto size-4" />
221
+ </DropdownMenuPrimitive.SubTrigger>
222
+ )
223
+ }
224
+
225
+ function DropdownMenuSubContent({
226
+ className,
227
+ ...props
228
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
229
+ return (
230
+ <DropdownMenuPrimitive.SubContent
231
+ data-slot="dropdown-menu-sub-content"
232
+ className={cn(
233
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-[1300] min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
234
+ className
235
+ )}
236
+ {...props}
237
+ />
238
+ )
239
+ }
240
+
241
+ export {
242
+ DropdownMenu,
243
+ DropdownMenuPortal,
244
+ DropdownMenuTrigger,
245
+ DropdownMenuContent,
246
+ DropdownMenuGroup,
247
+ DropdownMenuLabel,
248
+ DropdownMenuItem,
249
+ DropdownMenuCheckboxItem,
250
+ DropdownMenuRadioGroup,
251
+ DropdownMenuRadioItem,
252
+ DropdownMenuSeparator,
253
+ DropdownMenuShortcut,
254
+ DropdownMenuSub,
255
+ DropdownMenuSubTrigger,
256
+ DropdownMenuSubContent,
257
+ }