@srcroot/ui 0.0.37 → 0.0.40

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,187 @@
1
+ /**
2
+ * @srcroot/ui - Violet Theme (Tailwind 4)
3
+ * Vibrant purple and magenta connected to the brand identity
4
+ */
5
+
6
+ @import "tailwindcss";
7
+
8
+ :root {
9
+ --background: hsl(0 0% 100%);
10
+ --foreground: hsl(246 80% 60%);
11
+
12
+ --card: hsl(0 0% 100%);
13
+ --card-foreground: hsl(246 80% 60%);
14
+
15
+ --popover: hsl(0 0% 100%);
16
+ --popover-foreground: hsl(246 80% 60%);
17
+
18
+ --primary: hsl(246 80% 60%);
19
+ --primary-foreground: hsl(0 0% 100%);
20
+
21
+ --secondary: hsl(320 85% 55%);
22
+ --secondary-foreground: hsl(0 0% 100%);
23
+
24
+ --muted: hsl(246 20% 96%);
25
+ --muted-foreground: hsl(246 10% 45%);
26
+
27
+ --accent: hsl(320 85% 55%);
28
+ --accent-foreground: hsl(0 0% 100%);
29
+
30
+ --destructive: hsl(0 84.2% 60.2%);
31
+ --destructive-foreground: hsl(0 0% 98%);
32
+
33
+ --success: hsl(142.1 76.2% 36.3%);
34
+ --success-foreground: hsl(0 0% 98%);
35
+
36
+ --warning: hsl(45.4 93.4% 47.5%);
37
+ --warning-foreground: hsl(246 80% 60%);
38
+
39
+ --info: hsl(201.3 96.3% 32.2%);
40
+ --info-foreground: hsl(0 0% 98%);
41
+
42
+ --border: hsl(246 15% 90%);
43
+ --input: hsl(246 15% 90%);
44
+ --ring: hsl(246 80% 60%);
45
+
46
+ --radius: 0.5rem;
47
+
48
+ --sidebar-width: 16rem;
49
+ --sidebar-width-mobile: 18rem;
50
+ --sidebar-width-collapsed: 3rem;
51
+ --sidebar-width-icon: 3rem;
52
+ --header-height: 3.5rem;
53
+
54
+ --sidebar-background: hsl(0 0% 98%);
55
+ --sidebar-foreground: hsl(246 80% 60%);
56
+ --sidebar-primary: hsl(246 80% 60%);
57
+ --sidebar-primary-foreground: hsl(0 0% 98%);
58
+ --sidebar-accent: hsl(320 85% 55%);
59
+ --sidebar-accent-foreground: hsl(0 0% 98%);
60
+ --sidebar-border: hsl(246 15% 90%);
61
+ --sidebar-ring: hsl(246 80% 60%);
62
+ }
63
+
64
+ @theme inline {
65
+ --color-border: var(--border);
66
+ --color-input: var(--input);
67
+ --color-ring: var(--ring);
68
+ --color-background: var(--background);
69
+ --color-foreground: var(--foreground);
70
+
71
+ --color-primary: var(--primary);
72
+ --color-primary-foreground: var(--primary-foreground);
73
+
74
+ --color-secondary: var(--secondary);
75
+ --color-secondary-foreground: var(--secondary-foreground);
76
+
77
+ --color-destructive: var(--destructive);
78
+ --color-destructive-foreground: var(--destructive-foreground);
79
+
80
+ --color-muted: var(--muted);
81
+ --color-muted-foreground: var(--muted-foreground);
82
+
83
+ --color-accent: var(--accent);
84
+ --color-accent-foreground: var(--accent-foreground);
85
+
86
+ --color-popover: var(--popover);
87
+ --color-popover-foreground: var(--popover-foreground);
88
+
89
+ --color-card: var(--card);
90
+ --color-card-foreground: var(--card-foreground);
91
+
92
+ --color-sidebar: var(--sidebar-background);
93
+ --color-sidebar-foreground: var(--sidebar-foreground);
94
+ --color-sidebar-primary: var(--sidebar-primary);
95
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
96
+ --color-sidebar-accent: var(--sidebar-accent);
97
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
98
+ --color-sidebar-border: var(--sidebar-border);
99
+ --color-sidebar-ring: var(--sidebar-ring);
100
+
101
+ --radius-lg: var(--radius);
102
+ --radius-md: calc(var(--radius) - 2px);
103
+ --radius-sm: calc(var(--radius) - 4px);
104
+
105
+ --animate-accordion-down: accordion-down 0.2s ease-out;
106
+ --animate-accordion-up: accordion-up 0.2s ease-out;
107
+
108
+ @keyframes accordion-down {
109
+ from {
110
+ height: 0;
111
+ }
112
+
113
+ to {
114
+ height: var(--radix-accordion-content-height);
115
+ }
116
+ }
117
+
118
+ @keyframes accordion-up {
119
+ from {
120
+ height: var(--radix-accordion-content-height);
121
+ }
122
+
123
+ to {
124
+ height: 0;
125
+ }
126
+ }
127
+ }
128
+
129
+ @media (prefers-color-scheme: dark) {
130
+ :root {
131
+ --background: hsl(246 20% 6%);
132
+ --foreground: hsl(0 0% 98%);
133
+
134
+ --card: hsl(246 20% 8%);
135
+ --card-foreground: hsl(0 0% 98%);
136
+
137
+ --popover: hsl(246 20% 8%);
138
+ --popover-foreground: hsl(0 0% 98%);
139
+
140
+ --primary: hsl(246 80% 65%);
141
+ --primary-foreground: hsl(246 20% 6%);
142
+
143
+ --secondary: hsl(320 85% 60%);
144
+ --secondary-foreground: hsl(0 0% 100%);
145
+
146
+ --muted: hsl(246 20% 15%);
147
+ --muted-foreground: hsl(246 10% 60%);
148
+
149
+ --accent: hsl(320 85% 60%);
150
+ --accent-foreground: hsl(0 0% 100%);
151
+
152
+ --destructive: hsl(0 62.8% 30.6%);
153
+ --destructive-foreground: hsl(0 0% 98%);
154
+
155
+ --success: hsl(142.1 70.6% 45.3%);
156
+ --success-foreground: hsl(246 80% 60%);
157
+
158
+ --warning: hsl(48 96.5% 53.1%);
159
+ --warning-foreground: hsl(246 80% 60%);
160
+
161
+ --info: hsl(199.4 95.5% 53.8%);
162
+ --info-foreground: hsl(246 80% 60%);
163
+
164
+ --border: hsl(246 20% 18%);
165
+ --input: hsl(246 20% 18%);
166
+ --ring: hsl(246 80% 65%);
167
+
168
+ --sidebar-background: hsl(246 20% 10%);
169
+ --sidebar-foreground: hsl(0 0% 98%);
170
+ --sidebar-primary: hsl(246 80% 65%);
171
+ --sidebar-primary-foreground: hsl(246 20% 6%);
172
+ --sidebar-accent: hsl(320 85% 60%);
173
+ --sidebar-accent-foreground: hsl(0 0% 100%);
174
+ --sidebar-border: hsl(246 20% 18%);
175
+ --sidebar-ring: hsl(246 80% 65%);
176
+ }
177
+ }
178
+
179
+ body {
180
+ background: var(--background);
181
+ color: var(--foreground);
182
+ font-family: Arial, Helvetica, sans-serif;
183
+ }
184
+
185
+ .bg-gradient-brand {
186
+ background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%);
187
+ }
@@ -180,4 +180,8 @@ body {
180
180
  background: var(--background);
181
181
  color: var(--foreground);
182
182
  font-family: Arial, Helvetica, sans-serif;
183
+ }
184
+
185
+ .bg-gradient-brand {
186
+ background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%);
183
187
  }
@@ -0,0 +1,371 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { ResponsiveContainer } from "recharts"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ // Format: { THEME_NAME: CSS_VARIABLE }
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 must be used within a <ChartContainer />")
32
+ }
33
+
34
+ return context
35
+ }
36
+
37
+ const ChartContainer = React.forwardRef<
38
+ HTMLDivElement,
39
+ React.ComponentProps<"div"> & {
40
+ config: ChartConfig
41
+ children: React.ComponentProps<typeof ResponsiveContainer>["children"]
42
+ }
43
+ >(({ id, className, children, config, ...props }, ref) => {
44
+ const uniqueId = React.useId()
45
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
46
+
47
+ return (
48
+ <ChartContext.Provider value={{ config }}>
49
+ <div
50
+ data-chart={chartId}
51
+ ref={ref}
52
+ className={cn(
53
+ "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-none [&_.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-none [&_.recharts-surface]:outline-none",
54
+ className
55
+ )}
56
+ {...props}
57
+ >
58
+ <ChartStyle id={chartId} config={config} />
59
+ <ResponsiveContainer>
60
+ {children}
61
+ </ResponsiveContainer>
62
+ </div>
63
+ </ChartContext.Provider>
64
+ )
65
+ })
66
+ ChartContainer.displayName = "ChartContainer"
67
+
68
+ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
69
+ const colorConfig = Object.entries(config).filter(
70
+ ([_, config]) => config.theme || config.color
71
+ )
72
+
73
+ if (!colorConfig.length) {
74
+ return null
75
+ }
76
+
77
+ return (
78
+ <style
79
+ dangerouslySetInnerHTML={{
80
+ __html: Object.entries(THEMES)
81
+ .map(
82
+ ([theme, prefix]) => `
83
+ ${prefix} [data-chart=${id}] {
84
+ ${colorConfig
85
+ .map(([key, itemConfig]) => {
86
+ const color =
87
+ itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
88
+ itemConfig.color
89
+ return color ? ` --color-${key}: ${color};` : null
90
+ })
91
+ .join("\n")}
92
+ }
93
+ `
94
+ )
95
+ .join("\n"),
96
+ }}
97
+ />
98
+ )
99
+ }
100
+
101
+ const ChartTooltip = ChartTooltipPrimitive
102
+
103
+ const ChartTooltipContent = React.forwardRef<
104
+ HTMLDivElement,
105
+ React.ComponentProps<typeof ChartTooltipPrimitive> &
106
+ React.ComponentProps<"div"> & {
107
+ hideLabel?: boolean
108
+ hideIndicator?: boolean
109
+ indicator?: "line" | "dot" | "dashed"
110
+ nameKey?: string
111
+ labelKey?: string
112
+ }
113
+ >(
114
+ (
115
+ {
116
+ active,
117
+ payload,
118
+ className,
119
+ indicator = "dot",
120
+ hideLabel = false,
121
+ hideIndicator = false,
122
+ label,
123
+ labelFormatter,
124
+ labelClassName,
125
+ formatter,
126
+ color,
127
+ nameKey,
128
+ labelKey,
129
+ },
130
+ ref
131
+ ) => {
132
+ const { config } = useChart()
133
+
134
+ const tooltipLabel = React.useMemo(() => {
135
+ if (hideLabel || !payload?.length) {
136
+ return null
137
+ }
138
+
139
+ const [item] = payload
140
+ const key = `${labelKey || item.dataKey || item.name || "value"}`
141
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
142
+ const value =
143
+ !labelKey && typeof label === "string"
144
+ ? config[label as keyof typeof config]?.label || label
145
+ : itemConfig?.label
146
+
147
+ if (labelFormatter) {
148
+ return (
149
+ <div className={cn("font-medium", labelClassName)}>
150
+ {labelFormatter(value, payload)}
151
+ </div>
152
+ )
153
+ }
154
+
155
+ return value ? (
156
+ <div className={cn("font-medium", labelClassName)}>{value}</div>
157
+ ) : null
158
+ }, [
159
+ label,
160
+ labelFormatter,
161
+ payload,
162
+ hideLabel,
163
+ labelClassName,
164
+ config,
165
+ labelKey,
166
+ ])
167
+
168
+ if (!active || !payload?.length) {
169
+ return null
170
+ }
171
+
172
+ const nestLabel = payload.length === 1 && indicator !== "dot"
173
+
174
+ return (
175
+ <div
176
+ ref={ref}
177
+ className={cn(
178
+ "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",
179
+ className
180
+ )}
181
+ >
182
+ {!nestLabel ? tooltipLabel : null}
183
+ <div className="grid gap-1.5">
184
+ {payload.map((item, index) => {
185
+ const key = `${nameKey || item.name || item.dataKey || "value"}`
186
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
187
+ const indicatorColor = color || item.payload.fill || item.color
188
+
189
+ return (
190
+ <div
191
+ key={item.dataKey}
192
+ className={cn(
193
+ "flex items-center gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
194
+ indicator === "dot" && "items-center"
195
+ )}
196
+ >
197
+ {formatter && item?.value !== undefined && item.name ? (
198
+ formatter(item.value, item.name, item, index, item.payload)
199
+ ) : (
200
+ <>
201
+ {itemConfig?.icon ? (
202
+ <itemConfig.icon />
203
+ ) : (
204
+ !hideIndicator && (
205
+ <div
206
+ className={cn(
207
+ "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
208
+ {
209
+ "h-2.5 w-2.5": indicator === "dot",
210
+ "w-1": indicator === "line",
211
+ "w-0 border-[1.5px] border-dashed bg-transparent":
212
+ indicator === "dashed",
213
+ "my-0.5": nestLabel && indicator === "dashed",
214
+ }
215
+ )}
216
+ style={
217
+ {
218
+ "--color-bg": indicatorColor,
219
+ "--color-border": indicatorColor,
220
+ } as React.CSSProperties
221
+ }
222
+ />
223
+ )
224
+ )}
225
+ <div
226
+ className={cn(
227
+ "flex flex-1 justify-between leading-none",
228
+ nestLabel ? "items-end" : "items-center"
229
+ )}
230
+ >
231
+ <div className="grid gap-1.5">
232
+ {nestLabel ? tooltipLabel : null}
233
+ <span className="text-muted-foreground">
234
+ {itemConfig?.label || item.name}
235
+ </span>
236
+ </div>
237
+ {item.value && (
238
+ <span className="font-mono font-medium text-foreground tabular-nums">
239
+ {item.value.toLocaleString()}
240
+ </span>
241
+ )}
242
+ </div>
243
+ </>
244
+ )}
245
+ </div>
246
+ )
247
+ })}
248
+ </div>
249
+ </div>
250
+ )
251
+ }
252
+ )
253
+ ChartTooltipContent.displayName = "ChartTooltip"
254
+
255
+ const ChartLegend = ChartLegendPrimitive
256
+
257
+ const ChartLegendContent = React.forwardRef<
258
+ HTMLDivElement,
259
+ React.ComponentProps<"div"> &
260
+ Pick<React.ComponentProps<typeof ChartLegendPrimitive>, "payload" | "verticalAlign"> & {
261
+ hideIcon?: boolean
262
+ nameKey?: string
263
+ }
264
+ >(
265
+ (
266
+ { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
267
+ ref
268
+ ) => {
269
+ const { config } = useChart()
270
+
271
+ if (!payload?.length) {
272
+ return null
273
+ }
274
+
275
+ return (
276
+ <div
277
+ ref={ref}
278
+ className={cn(
279
+ "flex items-center justify-center gap-4",
280
+ verticalAlign === "top" ? "pb-3" : "pt-3",
281
+ className
282
+ )}
283
+ >
284
+ {payload.map((item) => {
285
+ const key = `${nameKey || item.dataKey || "value"}`
286
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
287
+
288
+ return (
289
+ <div
290
+ key={item.value}
291
+ className={cn(
292
+ "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
293
+ )}
294
+ >
295
+ {itemConfig?.icon && !hideIcon ? (
296
+ <itemConfig.icon />
297
+ ) : (
298
+ <div
299
+ className="h-2 w-2 shrink-0 rounded-[2px]"
300
+ style={{
301
+ backgroundColor: item.color,
302
+ }}
303
+ />
304
+ )}
305
+ {itemConfig?.label}
306
+ </div>
307
+ )
308
+ })}
309
+ </div>
310
+ )
311
+ }
312
+ )
313
+ ChartLegendContent.displayName = "ChartLegend"
314
+
315
+ // Helper to extract item config from a payload.
316
+ function getPayloadConfigFromPayload(
317
+ config: ChartConfig,
318
+ payload: unknown,
319
+ key: string
320
+ ) {
321
+ if (typeof payload !== "object" || payload === null) {
322
+ return undefined
323
+ }
324
+
325
+ const payloadPayload =
326
+ "payload" in payload &&
327
+ typeof payload.payload === "object" &&
328
+ payload.payload !== null
329
+ ? payload.payload
330
+ : undefined
331
+
332
+ let configLabelKey: string = key
333
+
334
+ if (
335
+ key in payload &&
336
+ typeof payload[key as keyof typeof payload] === "string"
337
+ ) {
338
+ configLabelKey = payload[key as keyof typeof payload] as string
339
+ } else if (
340
+ payloadPayload &&
341
+ key in payloadPayload &&
342
+ typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
343
+ ) {
344
+ configLabelKey = payloadPayload[
345
+ key as keyof typeof payloadPayload
346
+ ] as string
347
+ }
348
+
349
+ return configLabelKey in config
350
+ ? config[configLabelKey]
351
+ : config[key as keyof typeof config]
352
+ }
353
+
354
+ // Recharts Primitive Imports - We import specific components to avoid large bundle reference but for now let's just use * and assume tree shaking or specific imports in consuming app.
355
+ // However, the above code uses RechartsPrimitive.Tooltip and Legend.
356
+ // Ideally we should import them from recharts.
357
+
358
+ import {
359
+ Legend as ChartLegendPrimitive,
360
+ Tooltip as ChartTooltipPrimitive,
361
+ ResponsiveContainer,
362
+ } from "recharts"
363
+
364
+ export {
365
+ ChartContainer,
366
+ ChartTooltip,
367
+ ChartTooltipContent,
368
+ ChartLegend,
369
+ ChartLegendContent,
370
+ ChartStyle,
371
+ }
@@ -1,7 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from "react"
4
- import { PanelLeft } from "lucide-react"
4
+ import { LuPanelLeft } from "react-icons/lu";
5
5
  import { cva, type VariantProps } from "class-variance-authority"
6
6
 
7
7
  import { cn } from "@/lib/utils"
@@ -236,7 +236,7 @@ const SidebarTrigger = React.forwardRef<
236
236
  }}
237
237
  {...props}
238
238
  >
239
- <PanelLeft />
239
+ <LuPanelLeft />
240
240
  <span className="sr-only">Toggle Sidebar</span>
241
241
  </Button>
242
242
  )