@beemafrica/ui 0.1.13 → 0.1.15
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 +4 -1
- package/src/components/calendar.tsx +222 -0
- package/src/components/chart.tsx +372 -0
- package/src/components/dropdown-menu.tsx +268 -0
- package/src/components/popover.tsx +88 -0
- package/src/components/scroll-area.tsx +54 -0
- package/src/components/select.tsx +191 -0
- package/src/components/skeleton.tsx +13 -0
- package/src/components/ticket.tsx +1 -1
- package/src/components/tooltip.tsx +56 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beemafrica/ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Shared Beem UI components built on shadcn/Radix",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -24,9 +24,12 @@
|
|
|
24
24
|
"clsx": "^2.1.1",
|
|
25
25
|
"cmdk": "^1.1.1",
|
|
26
26
|
"cn": "^0.2.6",
|
|
27
|
+
"date-fns": "^4.4.0",
|
|
27
28
|
"lucide-react": "^1.31.0",
|
|
28
29
|
"next-themes": "^0.4.6",
|
|
29
30
|
"radix-ui": "^1.6.7",
|
|
31
|
+
"react-day-picker": "^10.0.1",
|
|
32
|
+
"recharts": "^3.8.0",
|
|
30
33
|
"shadcn": "^4.16.2",
|
|
31
34
|
"tailwind-merge": "^3.6.0",
|
|
32
35
|
"tailwind-scrollbar": "^4.0.2",
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "cn"
|
|
5
|
+
import {
|
|
6
|
+
DayPicker,
|
|
7
|
+
getDefaultClassNames,
|
|
8
|
+
type DayButton,
|
|
9
|
+
type Locale,
|
|
10
|
+
} from "react-day-picker"
|
|
11
|
+
|
|
12
|
+
import { Button, buttonVariants } from "@beemafrica/ui/components/button"
|
|
13
|
+
import { ChevronLeftIcon, ChevronRightIcon, ChevronDownIcon } from "lucide-react"
|
|
14
|
+
|
|
15
|
+
function Calendar({
|
|
16
|
+
className,
|
|
17
|
+
classNames,
|
|
18
|
+
showOutsideDays = true,
|
|
19
|
+
captionLayout = "label",
|
|
20
|
+
buttonVariant = "ghost",
|
|
21
|
+
locale,
|
|
22
|
+
formatters,
|
|
23
|
+
components,
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<typeof DayPicker> & {
|
|
26
|
+
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
|
|
27
|
+
}) {
|
|
28
|
+
const defaultClassNames = getDefaultClassNames()
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<DayPicker
|
|
32
|
+
showOutsideDays={showOutsideDays}
|
|
33
|
+
className={cn(
|
|
34
|
+
"group/calendar bg-background p-2 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(7)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent",
|
|
35
|
+
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
|
36
|
+
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
|
37
|
+
className
|
|
38
|
+
)}
|
|
39
|
+
captionLayout={captionLayout}
|
|
40
|
+
locale={locale}
|
|
41
|
+
formatters={{
|
|
42
|
+
formatMonthDropdown: (date) =>
|
|
43
|
+
date.toLocaleString(locale?.code, { month: "short" }),
|
|
44
|
+
...formatters,
|
|
45
|
+
}}
|
|
46
|
+
classNames={{
|
|
47
|
+
root: cn("w-fit", defaultClassNames.root),
|
|
48
|
+
months: cn(
|
|
49
|
+
"relative flex flex-col gap-4 md:flex-row",
|
|
50
|
+
defaultClassNames.months
|
|
51
|
+
),
|
|
52
|
+
month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
|
|
53
|
+
nav: cn(
|
|
54
|
+
"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
|
|
55
|
+
defaultClassNames.nav
|
|
56
|
+
),
|
|
57
|
+
button_previous: cn(
|
|
58
|
+
buttonVariants({ variant: buttonVariant }),
|
|
59
|
+
"size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
|
|
60
|
+
defaultClassNames.button_previous
|
|
61
|
+
),
|
|
62
|
+
button_next: cn(
|
|
63
|
+
buttonVariants({ variant: buttonVariant }),
|
|
64
|
+
"size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
|
|
65
|
+
defaultClassNames.button_next
|
|
66
|
+
),
|
|
67
|
+
month_caption: cn(
|
|
68
|
+
"flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)",
|
|
69
|
+
defaultClassNames.month_caption
|
|
70
|
+
),
|
|
71
|
+
dropdowns: cn(
|
|
72
|
+
"flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium",
|
|
73
|
+
defaultClassNames.dropdowns
|
|
74
|
+
),
|
|
75
|
+
dropdown_root: cn(
|
|
76
|
+
"relative rounded-(--cell-radius)",
|
|
77
|
+
defaultClassNames.dropdown_root
|
|
78
|
+
),
|
|
79
|
+
dropdown: cn(
|
|
80
|
+
"absolute inset-0 bg-popover opacity-0",
|
|
81
|
+
defaultClassNames.dropdown
|
|
82
|
+
),
|
|
83
|
+
caption_label: cn(
|
|
84
|
+
"font-medium select-none",
|
|
85
|
+
captionLayout === "label"
|
|
86
|
+
? "text-sm"
|
|
87
|
+
: "flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
|
|
88
|
+
defaultClassNames.caption_label
|
|
89
|
+
),
|
|
90
|
+
month_grid: cn("w-full border-collapse", defaultClassNames.month_grid),
|
|
91
|
+
weekdays: cn("flex", defaultClassNames.weekdays),
|
|
92
|
+
weekday: cn(
|
|
93
|
+
"flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none",
|
|
94
|
+
defaultClassNames.weekday
|
|
95
|
+
),
|
|
96
|
+
week: cn("mt-2 flex w-full", defaultClassNames.week),
|
|
97
|
+
week_number_header: cn(
|
|
98
|
+
"w-(--cell-size) select-none",
|
|
99
|
+
defaultClassNames.week_number_header
|
|
100
|
+
),
|
|
101
|
+
week_number: cn(
|
|
102
|
+
"text-[0.8rem] text-muted-foreground select-none",
|
|
103
|
+
defaultClassNames.week_number
|
|
104
|
+
),
|
|
105
|
+
day: cn(
|
|
106
|
+
"group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-e-(--cell-radius)",
|
|
107
|
+
props.showWeekNumber
|
|
108
|
+
? "[&:nth-child(2)[data-selected=true]_button]:rounded-s-(--cell-radius)"
|
|
109
|
+
: "[&:first-child[data-selected=true]_button]:rounded-s-(--cell-radius)",
|
|
110
|
+
defaultClassNames.day
|
|
111
|
+
),
|
|
112
|
+
range_start: cn(
|
|
113
|
+
"relative isolate z-0 rounded-s-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:end-0 after:w-4 after:bg-muted",
|
|
114
|
+
defaultClassNames.range_start
|
|
115
|
+
),
|
|
116
|
+
range_middle: cn("rounded-none", defaultClassNames.range_middle),
|
|
117
|
+
range_end: cn(
|
|
118
|
+
"relative isolate z-0 rounded-e-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:start-0 after:w-4 after:bg-muted",
|
|
119
|
+
defaultClassNames.range_end
|
|
120
|
+
),
|
|
121
|
+
today: cn(
|
|
122
|
+
"rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none",
|
|
123
|
+
defaultClassNames.today
|
|
124
|
+
),
|
|
125
|
+
outside: cn(
|
|
126
|
+
"text-muted-foreground aria-selected:text-muted-foreground",
|
|
127
|
+
defaultClassNames.outside
|
|
128
|
+
),
|
|
129
|
+
disabled: cn(
|
|
130
|
+
"text-muted-foreground opacity-50",
|
|
131
|
+
defaultClassNames.disabled
|
|
132
|
+
),
|
|
133
|
+
hidden: cn("invisible", defaultClassNames.hidden),
|
|
134
|
+
...classNames,
|
|
135
|
+
}}
|
|
136
|
+
components={{
|
|
137
|
+
Root: ({ className, rootRef, ...props }) => {
|
|
138
|
+
return (
|
|
139
|
+
<div
|
|
140
|
+
data-slot="calendar"
|
|
141
|
+
ref={rootRef}
|
|
142
|
+
className={cn(className)}
|
|
143
|
+
{...props}
|
|
144
|
+
/>
|
|
145
|
+
)
|
|
146
|
+
},
|
|
147
|
+
Chevron: ({ className, orientation, ...props }) => {
|
|
148
|
+
if (orientation === "left") {
|
|
149
|
+
return (
|
|
150
|
+
<ChevronLeftIcon className={cn("rtl:rotate-180 size-4", className)} {...props} />
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (orientation === "right") {
|
|
155
|
+
return (
|
|
156
|
+
<ChevronRightIcon className={cn("rtl:rotate-180 size-4", className)} {...props} />
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<ChevronDownIcon className={cn("size-4", className)} {...props} />
|
|
162
|
+
)
|
|
163
|
+
},
|
|
164
|
+
DayButton: ({ ...props }) => (
|
|
165
|
+
<CalendarDayButton locale={locale} {...props} />
|
|
166
|
+
),
|
|
167
|
+
WeekNumber: ({ children, ...props }) => {
|
|
168
|
+
return (
|
|
169
|
+
<td {...props}>
|
|
170
|
+
<div className="flex size-(--cell-size) items-center justify-center text-center">
|
|
171
|
+
{children}
|
|
172
|
+
</div>
|
|
173
|
+
</td>
|
|
174
|
+
)
|
|
175
|
+
},
|
|
176
|
+
...components,
|
|
177
|
+
}}
|
|
178
|
+
{...props}
|
|
179
|
+
/>
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function CalendarDayButton({
|
|
184
|
+
className,
|
|
185
|
+
day,
|
|
186
|
+
modifiers,
|
|
187
|
+
locale,
|
|
188
|
+
...props
|
|
189
|
+
}: React.ComponentProps<typeof DayButton> & { locale?: Partial<Locale> }) {
|
|
190
|
+
const defaultClassNames = getDefaultClassNames()
|
|
191
|
+
|
|
192
|
+
const ref = React.useRef<HTMLButtonElement>(null)
|
|
193
|
+
React.useEffect(() => {
|
|
194
|
+
if (modifiers.focused) ref.current?.focus()
|
|
195
|
+
}, [modifiers.focused])
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<Button
|
|
199
|
+
ref={ref}
|
|
200
|
+
variant="ghost"
|
|
201
|
+
size="icon"
|
|
202
|
+
data-day={day.date.toLocaleDateString(locale?.code)}
|
|
203
|
+
data-selected-single={
|
|
204
|
+
modifiers.selected &&
|
|
205
|
+
!modifiers.range_start &&
|
|
206
|
+
!modifiers.range_end &&
|
|
207
|
+
!modifiers.range_middle
|
|
208
|
+
}
|
|
209
|
+
data-range-start={modifiers.range_start}
|
|
210
|
+
data-range-end={modifiers.range_end}
|
|
211
|
+
data-range-middle={modifiers.range_middle}
|
|
212
|
+
className={cn(
|
|
213
|
+
"relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-e-(--cell-radius) data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-s-(--cell-radius) data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70",
|
|
214
|
+
defaultClassNames.day,
|
|
215
|
+
className
|
|
216
|
+
)}
|
|
217
|
+
{...props}
|
|
218
|
+
/>
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export { Calendar, CalendarDayButton }
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "cn"
|
|
5
|
+
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
|
|
6
|
+
import { CheckIcon, ChevronRightIcon } from "lucide-react"
|
|
7
|
+
|
|
8
|
+
function DropdownMenu({
|
|
9
|
+
...props
|
|
10
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
|
11
|
+
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function DropdownMenuPortal({
|
|
15
|
+
...props
|
|
16
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
|
17
|
+
return (
|
|
18
|
+
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function DropdownMenuTrigger({
|
|
23
|
+
...props
|
|
24
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
|
25
|
+
return (
|
|
26
|
+
<DropdownMenuPrimitive.Trigger
|
|
27
|
+
data-slot="dropdown-menu-trigger"
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function DropdownMenuContent({
|
|
34
|
+
className,
|
|
35
|
+
align = "start",
|
|
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
|
+
align={align}
|
|
45
|
+
className={cn("z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) min-w-32 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 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 data-[state=closed]:overflow-hidden data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
</DropdownMenuPrimitive.Portal>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function DropdownMenuGroup({
|
|
53
|
+
...props
|
|
54
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
|
55
|
+
return (
|
|
56
|
+
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function DropdownMenuItem({
|
|
61
|
+
className,
|
|
62
|
+
inset,
|
|
63
|
+
variant = "default",
|
|
64
|
+
...props
|
|
65
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
|
66
|
+
inset?: boolean
|
|
67
|
+
variant?: "default" | "destructive"
|
|
68
|
+
}) {
|
|
69
|
+
return (
|
|
70
|
+
<DropdownMenuPrimitive.Item
|
|
71
|
+
data-slot="dropdown-menu-item"
|
|
72
|
+
data-inset={inset}
|
|
73
|
+
data-variant={variant}
|
|
74
|
+
className={cn(
|
|
75
|
+
"group/dropdown-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:ps-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
|
|
76
|
+
className
|
|
77
|
+
)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function DropdownMenuCheckboxItem({
|
|
84
|
+
className,
|
|
85
|
+
children,
|
|
86
|
+
checked,
|
|
87
|
+
inset,
|
|
88
|
+
...props
|
|
89
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem> & {
|
|
90
|
+
inset?: boolean
|
|
91
|
+
}) {
|
|
92
|
+
return (
|
|
93
|
+
<DropdownMenuPrimitive.CheckboxItem
|
|
94
|
+
data-slot="dropdown-menu-checkbox-item"
|
|
95
|
+
data-inset={inset}
|
|
96
|
+
className={cn(
|
|
97
|
+
"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pe-8 ps-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:ps-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
98
|
+
className
|
|
99
|
+
)}
|
|
100
|
+
checked={checked}
|
|
101
|
+
{...props}
|
|
102
|
+
>
|
|
103
|
+
<span
|
|
104
|
+
className="pointer-events-none absolute end-2 flex items-center justify-center"
|
|
105
|
+
data-slot="dropdown-menu-checkbox-item-indicator"
|
|
106
|
+
>
|
|
107
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
108
|
+
<CheckIcon
|
|
109
|
+
/>
|
|
110
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
111
|
+
</span>
|
|
112
|
+
{children}
|
|
113
|
+
</DropdownMenuPrimitive.CheckboxItem>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function DropdownMenuRadioGroup({
|
|
118
|
+
...props
|
|
119
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
|
120
|
+
return (
|
|
121
|
+
<DropdownMenuPrimitive.RadioGroup
|
|
122
|
+
data-slot="dropdown-menu-radio-group"
|
|
123
|
+
{...props}
|
|
124
|
+
/>
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function DropdownMenuRadioItem({
|
|
129
|
+
className,
|
|
130
|
+
children,
|
|
131
|
+
inset,
|
|
132
|
+
...props
|
|
133
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem> & {
|
|
134
|
+
inset?: boolean
|
|
135
|
+
}) {
|
|
136
|
+
return (
|
|
137
|
+
<DropdownMenuPrimitive.RadioItem
|
|
138
|
+
data-slot="dropdown-menu-radio-item"
|
|
139
|
+
data-inset={inset}
|
|
140
|
+
className={cn(
|
|
141
|
+
"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pe-8 ps-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:ps-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
142
|
+
className
|
|
143
|
+
)}
|
|
144
|
+
{...props}
|
|
145
|
+
>
|
|
146
|
+
<span
|
|
147
|
+
className="pointer-events-none absolute end-2 flex items-center justify-center"
|
|
148
|
+
data-slot="dropdown-menu-radio-item-indicator"
|
|
149
|
+
>
|
|
150
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
151
|
+
<CheckIcon
|
|
152
|
+
/>
|
|
153
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
154
|
+
</span>
|
|
155
|
+
{children}
|
|
156
|
+
</DropdownMenuPrimitive.RadioItem>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function DropdownMenuLabel({
|
|
161
|
+
className,
|
|
162
|
+
inset,
|
|
163
|
+
...props
|
|
164
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
|
165
|
+
inset?: boolean
|
|
166
|
+
}) {
|
|
167
|
+
return (
|
|
168
|
+
<DropdownMenuPrimitive.Label
|
|
169
|
+
data-slot="dropdown-menu-label"
|
|
170
|
+
data-inset={inset}
|
|
171
|
+
className={cn(
|
|
172
|
+
"px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:ps-7",
|
|
173
|
+
className
|
|
174
|
+
)}
|
|
175
|
+
{...props}
|
|
176
|
+
/>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function DropdownMenuSeparator({
|
|
181
|
+
className,
|
|
182
|
+
...props
|
|
183
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
|
184
|
+
return (
|
|
185
|
+
<DropdownMenuPrimitive.Separator
|
|
186
|
+
data-slot="dropdown-menu-separator"
|
|
187
|
+
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
|
188
|
+
{...props}
|
|
189
|
+
/>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function DropdownMenuShortcut({
|
|
194
|
+
className,
|
|
195
|
+
...props
|
|
196
|
+
}: React.ComponentProps<"span">) {
|
|
197
|
+
return (
|
|
198
|
+
<span
|
|
199
|
+
data-slot="dropdown-menu-shortcut"
|
|
200
|
+
className={cn(
|
|
201
|
+
"ms-auto text-xs tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground",
|
|
202
|
+
className
|
|
203
|
+
)}
|
|
204
|
+
{...props}
|
|
205
|
+
/>
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function DropdownMenuSub({
|
|
210
|
+
...props
|
|
211
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
|
212
|
+
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function DropdownMenuSubTrigger({
|
|
216
|
+
className,
|
|
217
|
+
inset,
|
|
218
|
+
children,
|
|
219
|
+
...props
|
|
220
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
221
|
+
inset?: boolean
|
|
222
|
+
}) {
|
|
223
|
+
return (
|
|
224
|
+
<DropdownMenuPrimitive.SubTrigger
|
|
225
|
+
data-slot="dropdown-menu-sub-trigger"
|
|
226
|
+
data-inset={inset}
|
|
227
|
+
className={cn(
|
|
228
|
+
"flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:ps-7 data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
229
|
+
className
|
|
230
|
+
)}
|
|
231
|
+
{...props}
|
|
232
|
+
>
|
|
233
|
+
{children}
|
|
234
|
+
<ChevronRightIcon className="rtl:rotate-180 ms-auto" />
|
|
235
|
+
</DropdownMenuPrimitive.SubTrigger>
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function DropdownMenuSubContent({
|
|
240
|
+
className,
|
|
241
|
+
...props
|
|
242
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
|
243
|
+
return (
|
|
244
|
+
<DropdownMenuPrimitive.SubContent
|
|
245
|
+
data-slot="dropdown-menu-sub-content"
|
|
246
|
+
className={cn("z-50 min-w-[96px] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
|
247
|
+
{...props}
|
|
248
|
+
/>
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export {
|
|
253
|
+
DropdownMenu,
|
|
254
|
+
DropdownMenuPortal,
|
|
255
|
+
DropdownMenuTrigger,
|
|
256
|
+
DropdownMenuContent,
|
|
257
|
+
DropdownMenuGroup,
|
|
258
|
+
DropdownMenuLabel,
|
|
259
|
+
DropdownMenuItem,
|
|
260
|
+
DropdownMenuCheckboxItem,
|
|
261
|
+
DropdownMenuRadioGroup,
|
|
262
|
+
DropdownMenuRadioItem,
|
|
263
|
+
DropdownMenuSeparator,
|
|
264
|
+
DropdownMenuShortcut,
|
|
265
|
+
DropdownMenuSub,
|
|
266
|
+
DropdownMenuSubTrigger,
|
|
267
|
+
DropdownMenuSubContent,
|
|
268
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "cn"
|
|
5
|
+
import { Popover as PopoverPrimitive } from "radix-ui"
|
|
6
|
+
|
|
7
|
+
function Popover({
|
|
8
|
+
...props
|
|
9
|
+
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
|
10
|
+
return <PopoverPrimitive.Root data-slot="popover" {...props} />
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function PopoverTrigger({
|
|
14
|
+
...props
|
|
15
|
+
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
|
16
|
+
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function PopoverContent({
|
|
20
|
+
className,
|
|
21
|
+
align = "center",
|
|
22
|
+
sideOffset = 4,
|
|
23
|
+
...props
|
|
24
|
+
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
|
25
|
+
return (
|
|
26
|
+
<PopoverPrimitive.Portal>
|
|
27
|
+
<PopoverPrimitive.Content
|
|
28
|
+
data-slot="popover-content"
|
|
29
|
+
align={align}
|
|
30
|
+
sideOffset={sideOffset}
|
|
31
|
+
className={cn(
|
|
32
|
+
"z-50 flex w-72 origin-(--radix-popover-content-transform-origin) flex-col gap-2.5 rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
33
|
+
className
|
|
34
|
+
)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
</PopoverPrimitive.Portal>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function PopoverAnchor({
|
|
42
|
+
...props
|
|
43
|
+
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
|
44
|
+
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
data-slot="popover-header"
|
|
51
|
+
className={cn("flex flex-col gap-0.5 text-sm", className)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function PopoverTitle({ className, ...props }: React.ComponentProps<"h2">) {
|
|
58
|
+
return (
|
|
59
|
+
<div
|
|
60
|
+
data-slot="popover-title"
|
|
61
|
+
className={cn("font-medium", className)}
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function PopoverDescription({
|
|
68
|
+
className,
|
|
69
|
+
...props
|
|
70
|
+
}: React.ComponentProps<"p">) {
|
|
71
|
+
return (
|
|
72
|
+
<p
|
|
73
|
+
data-slot="popover-description"
|
|
74
|
+
className={cn("text-muted-foreground", className)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
Popover,
|
|
82
|
+
PopoverAnchor,
|
|
83
|
+
PopoverContent,
|
|
84
|
+
PopoverDescription,
|
|
85
|
+
PopoverHeader,
|
|
86
|
+
PopoverTitle,
|
|
87
|
+
PopoverTrigger,
|
|
88
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "cn"
|
|
5
|
+
import { ScrollArea as ScrollAreaPrimitive } from "radix-ui"
|
|
6
|
+
|
|
7
|
+
function ScrollArea({
|
|
8
|
+
className,
|
|
9
|
+
children,
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
|
|
12
|
+
return (
|
|
13
|
+
<ScrollAreaPrimitive.Root
|
|
14
|
+
data-slot="scroll-area"
|
|
15
|
+
className={cn("relative", className)}
|
|
16
|
+
{...props}
|
|
17
|
+
>
|
|
18
|
+
<ScrollAreaPrimitive.Viewport
|
|
19
|
+
data-slot="scroll-area-viewport"
|
|
20
|
+
className="size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1"
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
</ScrollAreaPrimitive.Viewport>
|
|
24
|
+
<ScrollBar />
|
|
25
|
+
<ScrollAreaPrimitive.Corner />
|
|
26
|
+
</ScrollAreaPrimitive.Root>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ScrollBar({
|
|
31
|
+
className,
|
|
32
|
+
orientation = "vertical",
|
|
33
|
+
...props
|
|
34
|
+
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
|
|
35
|
+
return (
|
|
36
|
+
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
|
37
|
+
data-slot="scroll-area-scrollbar"
|
|
38
|
+
data-orientation={orientation}
|
|
39
|
+
orientation={orientation}
|
|
40
|
+
className={cn(
|
|
41
|
+
"flex touch-none p-px transition-colors select-none data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-s data-vertical:border-s-transparent",
|
|
42
|
+
className
|
|
43
|
+
)}
|
|
44
|
+
{...props}
|
|
45
|
+
>
|
|
46
|
+
<ScrollAreaPrimitive.ScrollAreaThumb
|
|
47
|
+
data-slot="scroll-area-thumb"
|
|
48
|
+
className="relative flex-1 rounded-full bg-border"
|
|
49
|
+
/>
|
|
50
|
+
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export { ScrollArea, ScrollBar }
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "cn"
|
|
5
|
+
import { Select as SelectPrimitive } from "radix-ui"
|
|
6
|
+
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"
|
|
7
|
+
|
|
8
|
+
function Select({
|
|
9
|
+
...props
|
|
10
|
+
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
|
11
|
+
return <SelectPrimitive.Root data-slot="select" {...props} />
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function SelectGroup({
|
|
15
|
+
className,
|
|
16
|
+
...props
|
|
17
|
+
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
|
18
|
+
return (
|
|
19
|
+
<SelectPrimitive.Group
|
|
20
|
+
data-slot="select-group"
|
|
21
|
+
className={cn("scroll-my-1 p-1", className)}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function SelectValue({
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
|
30
|
+
return <SelectPrimitive.Value data-slot="select-value" {...props} />
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function SelectTrigger({
|
|
34
|
+
className,
|
|
35
|
+
size = "default",
|
|
36
|
+
children,
|
|
37
|
+
...props
|
|
38
|
+
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
|
39
|
+
size?: "sm" | "default"
|
|
40
|
+
}) {
|
|
41
|
+
return (
|
|
42
|
+
<SelectPrimitive.Trigger
|
|
43
|
+
data-slot="select-trigger"
|
|
44
|
+
data-size={size}
|
|
45
|
+
className={cn(
|
|
46
|
+
"flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pe-2 ps-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
47
|
+
className
|
|
48
|
+
)}
|
|
49
|
+
{...props}
|
|
50
|
+
>
|
|
51
|
+
{children}
|
|
52
|
+
<SelectPrimitive.Icon asChild>
|
|
53
|
+
<ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />
|
|
54
|
+
</SelectPrimitive.Icon>
|
|
55
|
+
</SelectPrimitive.Trigger>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function SelectContent({
|
|
60
|
+
className,
|
|
61
|
+
children,
|
|
62
|
+
position = "item-aligned",
|
|
63
|
+
align = "center",
|
|
64
|
+
...props
|
|
65
|
+
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
|
66
|
+
return (
|
|
67
|
+
<SelectPrimitive.Portal>
|
|
68
|
+
<SelectPrimitive.Content
|
|
69
|
+
data-slot="select-content"
|
|
70
|
+
data-align-trigger={position === "item-aligned"}
|
|
71
|
+
className={cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none 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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", position ==="popper"&&"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 rtl:data-[side=left]:translate-x-1 data-[side=right]:translate-x-1 rtl:data-[side=right]:-translate-x-1 data-[side=top]:-translate-y-1", className )}
|
|
72
|
+
position={position}
|
|
73
|
+
align={align}
|
|
74
|
+
{...props}
|
|
75
|
+
>
|
|
76
|
+
<SelectScrollUpButton />
|
|
77
|
+
<SelectPrimitive.Viewport
|
|
78
|
+
data-position={position}
|
|
79
|
+
className={cn(
|
|
80
|
+
"data-[position=popper]:h-(--radix-select-trigger-height) data-[position=popper]:w-full data-[position=popper]:min-w-(--radix-select-trigger-width)",
|
|
81
|
+
position === "popper" && ""
|
|
82
|
+
)}
|
|
83
|
+
>
|
|
84
|
+
{children}
|
|
85
|
+
</SelectPrimitive.Viewport>
|
|
86
|
+
<SelectScrollDownButton />
|
|
87
|
+
</SelectPrimitive.Content>
|
|
88
|
+
</SelectPrimitive.Portal>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function SelectLabel({
|
|
93
|
+
className,
|
|
94
|
+
...props
|
|
95
|
+
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
|
96
|
+
return (
|
|
97
|
+
<SelectPrimitive.Label
|
|
98
|
+
data-slot="select-label"
|
|
99
|
+
className={cn("px-1.5 py-1 text-xs text-muted-foreground", className)}
|
|
100
|
+
{...props}
|
|
101
|
+
/>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function SelectItem({
|
|
106
|
+
className,
|
|
107
|
+
children,
|
|
108
|
+
...props
|
|
109
|
+
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
|
110
|
+
return (
|
|
111
|
+
<SelectPrimitive.Item
|
|
112
|
+
data-slot="select-item"
|
|
113
|
+
className={cn(
|
|
114
|
+
"relative flex w-full cursor-default items-center gap-1.5 rounded-md py-1 pe-8 ps-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
|
115
|
+
className
|
|
116
|
+
)}
|
|
117
|
+
{...props}
|
|
118
|
+
>
|
|
119
|
+
<span className="pointer-events-none absolute end-2 flex size-4 items-center justify-center">
|
|
120
|
+
<SelectPrimitive.ItemIndicator>
|
|
121
|
+
<CheckIcon className="pointer-events-none" />
|
|
122
|
+
</SelectPrimitive.ItemIndicator>
|
|
123
|
+
</span>
|
|
124
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
125
|
+
</SelectPrimitive.Item>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function SelectSeparator({
|
|
130
|
+
className,
|
|
131
|
+
...props
|
|
132
|
+
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
|
133
|
+
return (
|
|
134
|
+
<SelectPrimitive.Separator
|
|
135
|
+
data-slot="select-separator"
|
|
136
|
+
className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)}
|
|
137
|
+
{...props}
|
|
138
|
+
/>
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function SelectScrollUpButton({
|
|
143
|
+
className,
|
|
144
|
+
...props
|
|
145
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
|
146
|
+
return (
|
|
147
|
+
<SelectPrimitive.ScrollUpButton
|
|
148
|
+
data-slot="select-scroll-up-button"
|
|
149
|
+
className={cn(
|
|
150
|
+
"z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
|
|
151
|
+
className
|
|
152
|
+
)}
|
|
153
|
+
{...props}
|
|
154
|
+
>
|
|
155
|
+
<ChevronUpIcon
|
|
156
|
+
/>
|
|
157
|
+
</SelectPrimitive.ScrollUpButton>
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function SelectScrollDownButton({
|
|
162
|
+
className,
|
|
163
|
+
...props
|
|
164
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
|
165
|
+
return (
|
|
166
|
+
<SelectPrimitive.ScrollDownButton
|
|
167
|
+
data-slot="select-scroll-down-button"
|
|
168
|
+
className={cn(
|
|
169
|
+
"z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
|
|
170
|
+
className
|
|
171
|
+
)}
|
|
172
|
+
{...props}
|
|
173
|
+
>
|
|
174
|
+
<ChevronDownIcon
|
|
175
|
+
/>
|
|
176
|
+
</SelectPrimitive.ScrollDownButton>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export {
|
|
181
|
+
Select,
|
|
182
|
+
SelectContent,
|
|
183
|
+
SelectGroup,
|
|
184
|
+
SelectItem,
|
|
185
|
+
SelectLabel,
|
|
186
|
+
SelectScrollDownButton,
|
|
187
|
+
SelectScrollUpButton,
|
|
188
|
+
SelectSeparator,
|
|
189
|
+
SelectTrigger,
|
|
190
|
+
SelectValue,
|
|
191
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { cn } from "cn"
|
|
2
|
+
|
|
3
|
+
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
data-slot="skeleton"
|
|
7
|
+
className={cn("animate-pulse rounded-md bg-muted", className)}
|
|
8
|
+
{...props}
|
|
9
|
+
/>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { Skeleton }
|
|
@@ -133,7 +133,7 @@ function Ticket({
|
|
|
133
133
|
Product details
|
|
134
134
|
</Button>
|
|
135
135
|
</CollapsibleTrigger>
|
|
136
|
-
<CollapsibleContent className="
|
|
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>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "cn"
|
|
5
|
+
import { Tooltip as TooltipPrimitive } from "radix-ui"
|
|
6
|
+
|
|
7
|
+
function TooltipProvider({
|
|
8
|
+
delayDuration = 0,
|
|
9
|
+
...props
|
|
10
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
|
11
|
+
return (
|
|
12
|
+
<TooltipPrimitive.Provider
|
|
13
|
+
data-slot="tooltip-provider"
|
|
14
|
+
delayDuration={delayDuration}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function Tooltip({
|
|
21
|
+
...props
|
|
22
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
23
|
+
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function TooltipTrigger({
|
|
27
|
+
...props
|
|
28
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
29
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function TooltipContent({
|
|
33
|
+
className,
|
|
34
|
+
sideOffset = 0,
|
|
35
|
+
children,
|
|
36
|
+
...props
|
|
37
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
38
|
+
return (
|
|
39
|
+
<TooltipPrimitive.Portal>
|
|
40
|
+
<TooltipPrimitive.Content
|
|
41
|
+
data-slot="tooltip-content"
|
|
42
|
+
sideOffset={sideOffset}
|
|
43
|
+
className={cn(
|
|
44
|
+
"z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pe-1.5 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 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
45
|
+
className
|
|
46
|
+
)}
|
|
47
|
+
{...props}
|
|
48
|
+
>
|
|
49
|
+
{children}
|
|
50
|
+
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
|
|
51
|
+
</TooltipPrimitive.Content>
|
|
52
|
+
</TooltipPrimitive.Portal>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }
|