diff --git a/.gitignore b/.gitignore index 9491a2f..77b4d6c 100644 --- a/.gitignore +++ b/.gitignore @@ -360,4 +360,6 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd + +shellui-installation-tests/ \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index ff54005..48cece3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ - 0.1.1 + 0.2.0 diff --git a/NET9/BlazorInteractiveServer/BlazorInteractiveServer.csproj b/NET9/BlazorInteractiveServer/BlazorInteractiveServer.csproj index 908684a..80618ef 100644 --- a/NET9/BlazorInteractiveServer/BlazorInteractiveServer.csproj +++ b/NET9/BlazorInteractiveServer/BlazorInteractiveServer.csproj @@ -8,6 +8,7 @@ + diff --git a/NET9/BlazorInteractiveServer/Components/Demo/ChartsDemo.razor b/NET9/BlazorInteractiveServer/Components/Demo/ChartsDemo.razor new file mode 100644 index 0000000..c4a147b --- /dev/null +++ b/NET9/BlazorInteractiveServer/Components/Demo/ChartsDemo.razor @@ -0,0 +1,141 @@ +@using BlazorInteractiveServer.Components.UI +@using BlazorInteractiveServer.Components.UI.Variants +@using ApexCharts + +
+

Bar Chart

+ +
+ +
+

Line Chart

+ +
+ +
+

Area Chart

+ +
+ +
+

Pie Chart

+ +
+ +
+

Multi-Series Chart

+ + + + +
+ +
+

Chart Themes

+
+
+

Default Theme

+ +
+
+

Colorful Theme

+ +
+
+

Monochrome Theme

+ +
+
+
+ +@code { + private List SalesData { get; set; } = new(); + private List ProfitData { get; set; } = new(); + private List CategoryData { get; set; } = new(); + + protected override void OnInitialized() + { + SalesData = new List + { + new SalesData { Month = "Jan", Amount = 120 }, + new SalesData { Month = "Feb", Amount = 150 }, + new SalesData { Month = "Mar", Amount = 180 }, + new SalesData { Month = "Apr", Amount = 200 }, + new SalesData { Month = "May", Amount = 170 }, + new SalesData { Month = "Jun", Amount = 220 } + }; + + ProfitData = new List + { + new SalesData { Month = "Jan", Amount = 40 }, + new SalesData { Month = "Feb", Amount = 50 }, + new SalesData { Month = "Mar", Amount = 60 }, + new SalesData { Month = "Apr", Amount = 70 }, + new SalesData { Month = "May", Amount = 55 }, + new SalesData { Month = "Jun", Amount = 80 } + }; + + CategoryData = new List + { + new CategoryData { Category = "Desktop", Value = 45 }, + new CategoryData { Category = "Mobile", Value = 35 }, + new CategoryData { Category = "Tablet", Value = 20 } + }; + } + +} \ No newline at end of file diff --git a/NET9/BlazorInteractiveServer/Components/Demo/FileUploadDemo.razor b/NET9/BlazorInteractiveServer/Components/Demo/FileUploadDemo.razor index 5fdbc2b..12e229d 100644 --- a/NET9/BlazorInteractiveServer/Components/Demo/FileUploadDemo.razor +++ b/NET9/BlazorInteractiveServer/Components/Demo/FileUploadDemo.razor @@ -100,14 +100,14 @@ private List _imageFiles = new(); private Dictionary _imageUrls = new(); - private async Task HandleFileSelection(InputFileChangeEventArgs e) + private void HandleFileSelection(InputFileChangeEventArgs e) { var file = e.File; _selectedFiles.Clear(); _selectedFiles.Add(file); } - private async Task HandleMultipleFileSelection(InputFileChangeEventArgs e) + private void HandleMultipleFileSelection(InputFileChangeEventArgs e) { var files = e.GetMultipleFiles(5); _multipleFiles.Clear(); diff --git a/NET9/BlazorInteractiveServer/Components/Models/ChartModels.cs b/NET9/BlazorInteractiveServer/Components/Models/ChartModels.cs new file mode 100644 index 0000000..11521fe --- /dev/null +++ b/NET9/BlazorInteractiveServer/Components/Models/ChartModels.cs @@ -0,0 +1,13 @@ +namespace BlazorInteractiveServer.Components.Models; + +public class SalesData +{ + public string Month { get; set; } = ""; + public decimal Amount { get; set; } +} + +public class CategoryData +{ + public string Category { get; set; } = ""; + public decimal Value { get; set; } +} \ No newline at end of file diff --git a/NET9/BlazorInteractiveServer/Components/Pages/Home.razor b/NET9/BlazorInteractiveServer/Components/Pages/Home.razor index 2547e0b..041f098 100644 --- a/NET9/BlazorInteractiveServer/Components/Pages/Home.razor +++ b/NET9/BlazorInteractiveServer/Components/Pages/Home.razor @@ -1,4 +1,4 @@ -@page "/" +@page "/" @using BlazorInteractiveServer.Components.UI @using BlazorInteractiveServer.Components.Models @using BlazorInteractiveServer.Components.Demo @@ -29,6 +29,7 @@ +
diff --git a/NET9/BlazorInteractiveServer/Components/UI/AreaChart.razor b/NET9/BlazorInteractiveServer/Components/UI/AreaChart.razor new file mode 100644 index 0000000..98c3585 --- /dev/null +++ b/NET9/BlazorInteractiveServer/Components/UI/AreaChart.razor @@ -0,0 +1,26 @@ +@namespace BlazorInteractiveServer.Components.UI +@using ApexCharts +@inherits Chart +@typeparam TItem where TItem : class + +
+ + + +
+ +@code { + [Parameter] public IEnumerable? Data { get; set; } + [Parameter] public string Name { get; set; } = "Data"; + [Parameter] public Func? XValue { get; set; } + [Parameter] public Func? YValue { get; set; } +} diff --git a/NET9/BlazorInteractiveServer/Components/UI/BarChart.razor b/NET9/BlazorInteractiveServer/Components/UI/BarChart.razor new file mode 100644 index 0000000..d768d4d --- /dev/null +++ b/NET9/BlazorInteractiveServer/Components/UI/BarChart.razor @@ -0,0 +1,26 @@ +@namespace BlazorInteractiveServer.Components.UI +@using ApexCharts +@inherits Chart +@typeparam TItem where TItem : class + +
+ + + +
+ +@code { + [Parameter] public IEnumerable? Data { get; set; } + [Parameter] public string Name { get; set; } = "Data"; + [Parameter] public Func? XValue { get; set; } + [Parameter] public Func? YValue { get; set; } +} diff --git a/NET9/BlazorInteractiveServer/Components/UI/Chart.razor b/NET9/BlazorInteractiveServer/Components/UI/Chart.razor new file mode 100644 index 0000000..5f1f94c --- /dev/null +++ b/NET9/BlazorInteractiveServer/Components/UI/Chart.razor @@ -0,0 +1,59 @@ +@namespace BlazorInteractiveServer.Components.UI +@using ApexCharts +@using BlazorInteractiveServer.Components.UI.Variants +@typeparam TItem where TItem : class + +
+ + @ChildContent + +
+ +@code { + [Parameter] public ChartTheme Theme { get; set; } = ChartTheme.Default; + [Parameter] public string? Title { get; set; } + [Parameter] public string? Subtitle { get; set; } + [Parameter] public string? Class { get; set; } + [Parameter] public string? Height { get; set; } = "400px"; + [Parameter] public string? Width { get; set; } = "100%"; + [Parameter] public RenderFragment? ChildContent { get; set; } + + private ApexCharts.ApexChartOptions? _chartOptions; + + protected ApexCharts.ApexChartOptions ChartOptions + { + get + { + if (_chartOptions == null) + { + _chartOptions = ChartVariants.GetOptions(Theme); + + // Apply title if provided + if (!string.IsNullOrEmpty(Title)) + { + _chartOptions.Title = _chartOptions.Title ?? new ApexCharts.Title(); + _chartOptions.Title.Text = Title; + } + + // Apply subtitle if provided + if (!string.IsNullOrEmpty(Subtitle)) + { + _chartOptions.Subtitle = _chartOptions.Subtitle ?? new ApexCharts.Subtitle(); + _chartOptions.Subtitle.Text = Subtitle; + } + } + return _chartOptions; + } + set => _chartOptions = value; + } + + protected string ComputedClass => Shell.Cn( + "border border-border bg-card text-card-foreground overflow-hidden", + "[border-radius:var(--radius)] [box-shadow:var(--shadow)]", + Class + ); +} diff --git a/NET9/BlazorInteractiveServer/Components/UI/ChartSeries.razor b/NET9/BlazorInteractiveServer/Components/UI/ChartSeries.razor new file mode 100644 index 0000000..6827913 --- /dev/null +++ b/NET9/BlazorInteractiveServer/Components/UI/ChartSeries.razor @@ -0,0 +1,18 @@ +@namespace BlazorInteractiveServer.Components.UI +@using ApexCharts +@typeparam TItem where TItem : class + + + +@code { + [Parameter] public IEnumerable? Data { get; set; } + [Parameter] public string Name { get; set; } = "Series"; + [Parameter] public SeriesType SeriesType { get; set; } = SeriesType.Line; + [Parameter] public Func? XValue { get; set; } + [Parameter] public Func? YValue { get; set; } +} diff --git a/NET9/BlazorInteractiveServer/Components/UI/ChartVariants.cs b/NET9/BlazorInteractiveServer/Components/UI/ChartVariants.cs new file mode 100644 index 0000000..7b674ad --- /dev/null +++ b/NET9/BlazorInteractiveServer/Components/UI/ChartVariants.cs @@ -0,0 +1,216 @@ +using System.Collections.Generic; + +namespace BlazorInteractiveServer.Components.UI.Variants; + +/* Chart color themes */ +public enum ChartTheme +{ + Default, // Balanced color palette suitable for light and dark modes + Colorful, // Vibrant colors for emphasis + Monochrome // Grayscale for minimal aesthetic +} + +/* ApexCharts configuration optimized for ShellUI theme system. + Note: ApexCharts doesn't support CSS variables in color arrays, so we use literal colors. + Text and borders use CSS variables (styled via CSS) for automatic dark mode support. */ +public static class ChartVariants +{ + // Creates chart options with theme-aware styling + public static ApexCharts.ApexChartOptions GetOptions(ChartTheme theme = ChartTheme.Default) where TItem : class + { + return new ApexCharts.ApexChartOptions + { + Chart = new ApexCharts.Chart + { + Background = "transparent", + FontFamily = "inherit", // Inherits from CSS + Toolbar = new ApexCharts.Toolbar + { + Show = true, + Tools = new ApexCharts.Tools + { + Download = true, + Selection = true, + Zoom = true, + Zoomin = true, + Zoomout = true, + Pan = true, + Reset = true + } + } + }, + + Theme = new ApexCharts.Theme(), + Colors = GetThemeColors(theme), + + Stroke = new ApexCharts.Stroke + { + Width = 2, + Curve = ApexCharts.Curve.Smooth, + LineCap = ApexCharts.LineCap.Round + }, + + Fill = new ApexCharts.Fill { Opacity = 0.85 }, + + DataLabels = new ApexCharts.DataLabels + { + Enabled = false + }, + + Grid = new ApexCharts.Grid + { + StrokeDashArray = 3, + Position = ApexCharts.GridPosition.Back + }, + + Xaxis = new ApexCharts.XAxis + { + Type = ApexCharts.XAxisType.Category, // Use category for proper label display + Labels = new ApexCharts.XAxisLabels + { + Style = new ApexCharts.AxisLabelStyle + { + FontSize = "12px" + } + }, + AxisBorder = new ApexCharts.AxisBorder + { + Show = true + }, + AxisTicks = new ApexCharts.AxisTicks + { + Show = true + } + }, + + Yaxis = new List + { + new ApexCharts.YAxis + { + Labels = new ApexCharts.YAxisLabels + { + Style = new ApexCharts.AxisLabelStyle + { + FontSize = "12px" + } + } + } + }, + + Legend = new ApexCharts.Legend + { + Show = true, + Position = ApexCharts.LegendPosition.Bottom, + FontSize = "13px", + Markers = new ApexCharts.LegendMarkers + { + Width = 10, + Height = 10, + Radius = 2 + } + }, + + Tooltip = new ApexCharts.Tooltip + { + Enabled = true, + Shared = true, + Intersect = false, + Style = new ApexCharts.TooltipStyle + { + FontSize = "12px" + }, + Custom = @"function({ series, seriesIndex, dataPointIndex, w }) { + const xLabel = w.globals.labels[dataPointIndex] || ''; + let html = '
'; + + if (xLabel) { + html += '
' + xLabel + '
'; + } + + w.globals.initialSeries.forEach((s, idx) => { + const color = w.config.colors[idx]; + const name = s.name || ''; + const value = series[idx] && series[idx][dataPointIndex] !== undefined + ? series[idx][dataPointIndex] + : '-'; + + html += '
' + + '' + + '' + name + ':' + + '' + value + '' + + '
'; + }); + + html += '
'; + return html; + }" + }, + + Title = new ApexCharts.Title + { + Style = new ApexCharts.TitleStyle + { + FontSize = "16px", + FontWeight = "600" + } + }, + + Subtitle = new ApexCharts.Subtitle + { + Style = new ApexCharts.SubtitleStyle + { + FontSize = "14px" + } + }, + + PlotOptions = new ApexCharts.PlotOptions + { + Bar = new ApexCharts.PlotOptionsBar + { + BorderRadius = 4, + ColumnWidth = "60%" + }, + Pie = new ApexCharts.PlotOptionsPie + { + ExpandOnClick = true + } + } + }; + } + + /* Returns color palette based on selected theme. + Colors are literal values because ApexCharts doesn't support CSS variables in color arrays. */ + private static List GetThemeColors(ChartTheme theme) + { + return theme switch + { + ChartTheme.Default => new List + { + "#2563eb", // blue-600 + "#dc2626", // red-600 + "#16a34a", // green-600 + "#ca8a04", // yellow-600 + "#9333ea" // purple-600 + }, + ChartTheme.Colorful => new List + { + "#3b82f6", // blue-500 + "#ef4444", // red-500 + "#10b981", // green-500 + "#f59e0b", // yellow-500 + "#8b5cf6", // purple-500 + "#ec4899", // pink-500 + "#06b6d4" // cyan-500 + }, + ChartTheme.Monochrome => new List + { + "#64748b", // slate-500 - works in both light/dark + "#94a3b8", // slate-400 + "#cbd5e1", // slate-300 + "#475569", // slate-600 + "#334155" // slate-700 + }, + _ => GetThemeColors(ChartTheme.Default) + }; + } +} diff --git a/NET9/BlazorInteractiveServer/Components/UI/LineChart.razor b/NET9/BlazorInteractiveServer/Components/UI/LineChart.razor new file mode 100644 index 0000000..da1e037 --- /dev/null +++ b/NET9/BlazorInteractiveServer/Components/UI/LineChart.razor @@ -0,0 +1,26 @@ +@namespace BlazorInteractiveServer.Components.UI +@using ApexCharts +@inherits Chart +@typeparam TItem where TItem : class + +
+ + + +
+ +@code { + [Parameter] public IEnumerable? Data { get; set; } + [Parameter] public string Name { get; set; } = "Data"; + [Parameter] public Func? XValue { get; set; } + [Parameter] public Func? YValue { get; set; } +} diff --git a/NET9/BlazorInteractiveServer/Components/UI/MultiSeriesChart.razor b/NET9/BlazorInteractiveServer/Components/UI/MultiSeriesChart.razor new file mode 100644 index 0000000..301cedd --- /dev/null +++ b/NET9/BlazorInteractiveServer/Components/UI/MultiSeriesChart.razor @@ -0,0 +1,18 @@ +@namespace BlazorInteractiveServer.Components.UI +@using ApexCharts +@inherits Chart +@typeparam TItem where TItem : class + +
+ + @ChildContent + +
+ +@code { + // ChildContent is inherited from Chart +} diff --git a/NET9/BlazorInteractiveServer/Components/UI/PieChart.razor b/NET9/BlazorInteractiveServer/Components/UI/PieChart.razor new file mode 100644 index 0000000..9d86bfc --- /dev/null +++ b/NET9/BlazorInteractiveServer/Components/UI/PieChart.razor @@ -0,0 +1,59 @@ +@namespace BlazorInteractiveServer.Components.UI +@using ApexCharts +@inherits Chart +@typeparam TItem where TItem : class + +
+ + + +
+ +@code { + [Parameter] public IEnumerable? Data { get; set; } + [Parameter] public string Name { get; set; } = "Data"; + [Parameter] public Func? XValue { get; set; } + [Parameter] public Func? YValue { get; set; } + + protected override void OnParametersSet() + { + base.OnParametersSet(); + + if (ChartOptions.Chart != null) + { + ChartOptions.Chart.Type = ApexCharts.ChartType.Pie; + } + + /* Custom tooltip for pie chart */ + ChartOptions.Tooltip = new ApexCharts.Tooltip + { + Enabled = true, + Style = new ApexCharts.TooltipStyle + { + FontSize = "12px" + }, + Custom = @"function({ series, seriesIndex, dataPointIndex, w }) { + const label = w.globals.labels[seriesIndex]; + const value = series[seriesIndex]; + const color = w.config.colors[seriesIndex]; + + return '
' + + '
' + + '' + + '' + label + ':' + + '' + value + '' + + '
' + + '
'; + }" + }; + } +} diff --git a/NET9/BlazorInteractiveServer/shellui.json b/NET9/BlazorInteractiveServer/shellui.json index ef941db..f8f24b1 100644 --- a/NET9/BlazorInteractiveServer/shellui.json +++ b/NET9/BlazorInteractiveServer/shellui.json @@ -5,6 +5,7 @@ "Tailwind": { "Enabled": true, "Version": "4.1.0", + "Method": "standalone", "ConfigPath": "tailwind.config.js", "CssPath": "wwwroot/app.css" }, @@ -266,6 +267,54 @@ "Version": "0.1.0", "InstalledAt": "2025-10-15T23:17:17.3769239Z", "IsCustomized": false + }, + { + "Name": "chart-variants", + "Version": "0.1.1", + "InstalledAt": "2026-01-17T01:41:18.8862813Z", + "IsCustomized": false + }, + { + "Name": "chart", + "Version": "0.1.1", + "InstalledAt": "2026-01-17T01:41:18.9171276Z", + "IsCustomized": false + }, + { + "Name": "bar-chart", + "Version": "0.1.1", + "InstalledAt": "2026-01-17T01:19:53.1893178Z", + "IsCustomized": false + }, + { + "Name": "line-chart", + "Version": "0.1.1", + "InstalledAt": "2026-01-17T01:19:53.2179832Z", + "IsCustomized": false + }, + { + "Name": "pie-chart", + "Version": "0.1.1", + "InstalledAt": "2026-01-17T01:19:53.2396791Z", + "IsCustomized": false + }, + { + "Name": "area-chart", + "Version": "0.1.1", + "InstalledAt": "2026-01-17T01:19:53.2738342Z", + "IsCustomized": false + }, + { + "Name": "chart-series", + "Version": "0.1.1", + "InstalledAt": "2026-01-17T01:19:53.3363663Z", + "IsCustomized": false + }, + { + "Name": "multi-series-chart", + "Version": "0.1.1", + "InstalledAt": "2026-01-17T01:19:53.3598418Z", + "IsCustomized": false } ], "ProjectType": 1 diff --git a/NET9/BlazorInteractiveServer/wwwroot/app.css b/NET9/BlazorInteractiveServer/wwwroot/app.css index 311e936..0df1ab0 100644 --- a/NET9/BlazorInteractiveServer/wwwroot/app.css +++ b/NET9/BlazorInteractiveServer/wwwroot/app.css @@ -110,6 +110,8 @@ --font-weight-semibold: 600; --font-weight-bold: 700; --tracking-tight: -0.025em; + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); --shadow-2xs: var(--shadow-2xs); --shadow-xs: var(--shadow-xs); --shadow-sm: var(--shadow-sm); @@ -810,6 +812,9 @@ .appearance-none { appearance: none; } + .grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } .grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } @@ -960,6 +965,9 @@ .overflow-y-auto { overflow-y: auto; } + .\[border-radius\:var\(--radius\)\] { + border-radius: var(--radius); + } .rounded { border-radius: 0.25rem; } @@ -1421,6 +1429,9 @@ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentColor); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } + .\[box-shadow\:var\(--shadow\)\] { + box-shadow: var(--shadow); + } .ring-ring { --tw-ring-color: var(--ring); } @@ -1995,6 +2006,11 @@ grid-template-columns: repeat(2, minmax(0, 1fr)); } } + .md\:grid-cols-3 { + @media (width >= 48rem) { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + } .md\:p-8 { @media (width >= 48rem) { padding: calc(var(--spacing) * 8); @@ -2258,110 +2274,110 @@ } } :root { - --background: oklch(0.9900 0 0); - --foreground: oklch(0 0 0); + --background: oklch(1 0 0); + --foreground: oklch(0.1450 0 0); --card: oklch(1 0 0); - --card-foreground: oklch(0 0 0); - --popover: oklch(0.9900 0 0); - --popover-foreground: oklch(0 0 0); - --primary: oklch(0 0 0); - --primary-foreground: oklch(1 0 0); - --secondary: oklch(0.9400 0 0); - --secondary-foreground: oklch(0 0 0); + --card-foreground: oklch(0.1450 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.1450 0 0); + --primary: oklch(0.2050 0 0); + --primary-foreground: oklch(0.9850 0 0); + --secondary: oklch(0.9700 0 0); + --secondary-foreground: oklch(0.2050 0 0); --muted: oklch(0.9700 0 0); - --muted-foreground: oklch(0.4400 0 0); - --accent: oklch(0.9400 0 0); - --accent-foreground: oklch(0 0 0); - --destructive: oklch(0.6300 0.1900 23.0300); + --muted-foreground: oklch(0.5560 0 0); + --accent: oklch(0.9700 0 0); + --accent-foreground: oklch(0.2050 0 0); + --destructive: oklch(0.5770 0.2450 27.3250); --destructive-foreground: oklch(1 0 0); - --border: oklch(0.9200 0 0); - --input: oklch(0.9400 0 0); - --ring: oklch(0 0 0); - --chart-1: oklch(0.8100 0.1700 75.3500); - --chart-2: oklch(0.5500 0.2200 264.5300); - --chart-3: oklch(0.7200 0 0); - --chart-4: oklch(0.9200 0 0); - --chart-5: oklch(0.5600 0 0); - --sidebar: oklch(0.9900 0 0); - --sidebar-foreground: oklch(0 0 0); - --sidebar-primary: oklch(0 0 0); - --sidebar-primary-foreground: oklch(1 0 0); - --sidebar-accent: oklch(0.9400 0 0); - --sidebar-accent-foreground: oklch(0 0 0); - --sidebar-border: oklch(0.9400 0 0); - --sidebar-ring: oklch(0 0 0); - --font-sans: Geist, sans-serif; - --font-serif: Georgia, serif; - --font-mono: Geist Mono, monospace; - --radius: 0.5rem; - --shadow-x: 0px; + --border: oklch(0.9220 0 0); + --input: oklch(0.9220 0 0); + --ring: oklch(0.7080 0 0); + --chart-1: oklch(0.8100 0.1000 252); + --chart-2: oklch(0.6200 0.1900 260); + --chart-3: oklch(0.5500 0.2200 263); + --chart-4: oklch(0.4900 0.2200 264); + --chart-5: oklch(0.4200 0.1800 266); + --sidebar: oklch(0.9850 0 0); + --sidebar-foreground: oklch(0.1450 0 0); + --sidebar-primary: oklch(0.2050 0 0); + --sidebar-primary-foreground: oklch(0.9850 0 0); + --sidebar-accent: oklch(0.9700 0 0); + --sidebar-accent-foreground: oklch(0.2050 0 0); + --sidebar-border: oklch(0.9220 0 0); + --sidebar-ring: oklch(0.7080 0 0); + --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --radius: 0.625rem; + --shadow-x: 0; --shadow-y: 1px; - --shadow-blur: 2px; + --shadow-blur: 3px; --shadow-spread: 0px; - --shadow-opacity: 0.18; - --shadow-color: hsl(0 0% 0%); - --shadow-2xs: 0px 1px 2px 0px hsl(0 0% 0% / 0.09); - --shadow-xs: 0px 1px 2px 0px hsl(0 0% 0% / 0.09); - --shadow-sm: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18); - --shadow: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18); - --shadow-md: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 2px 4px -1px hsl(0 0% 0% / 0.18); - --shadow-lg: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 4px 6px -1px hsl(0 0% 0% / 0.18); - --shadow-xl: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 8px 10px -1px hsl(0 0% 0% / 0.18); - --shadow-2xl: 0px 1px 2px 0px hsl(0 0% 0% / 0.45); + --shadow-opacity: 0.1; + --shadow-color: oklch(0 0 0); + --shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); + --shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); + --shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10); + --shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10); + --shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10); + --shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10); + --shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10); + --shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25); --tracking-normal: 0em; --spacing: 0.25rem; } .dark { - --background: oklch(0 0 0); - --foreground: oklch(1 0 0); - --card: oklch(0.1400 0 0); - --card-foreground: oklch(1 0 0); - --popover: oklch(0.1800 0 0); - --popover-foreground: oklch(1 0 0); - --primary: oklch(1 0 0); - --primary-foreground: oklch(0 0 0); - --secondary: oklch(0.2500 0 0); - --secondary-foreground: oklch(1 0 0); - --muted: oklch(0.2300 0 0); - --muted-foreground: oklch(0.7200 0 0); - --accent: oklch(0.3200 0 0); - --accent-foreground: oklch(1 0 0); - --destructive: oklch(0.6900 0.2000 23.9100); - --destructive-foreground: oklch(0 0 0); - --border: oklch(0.2600 0 0); - --input: oklch(0.3200 0 0); - --ring: oklch(0.7200 0 0); - --chart-1: oklch(0.8100 0.1700 75.3500); - --chart-2: oklch(0.5800 0.2100 260.8400); - --chart-3: oklch(0.5600 0 0); - --chart-4: oklch(0.4400 0 0); - --chart-5: oklch(0.9200 0 0); - --sidebar: oklch(0.1800 0 0); - --sidebar-foreground: oklch(1 0 0); - --sidebar-primary: oklch(1 0 0); - --sidebar-primary-foreground: oklch(0 0 0); - --sidebar-accent: oklch(0.3200 0 0); - --sidebar-accent-foreground: oklch(1 0 0); - --sidebar-border: oklch(0.3200 0 0); - --sidebar-ring: oklch(0.7200 0 0); - --font-sans: Geist, sans-serif; - --font-serif: Georgia, serif; - --font-mono: Geist Mono, monospace; - --radius: 0.5rem; - --shadow-x: 0px; + --background: oklch(0.1450 0 0); + --foreground: oklch(0.9850 0 0); + --card: oklch(0.2050 0 0); + --card-foreground: oklch(0.9850 0 0); + --popover: oklch(0.2690 0 0); + --popover-foreground: oklch(0.9850 0 0); + --primary: oklch(0.9220 0 0); + --primary-foreground: oklch(0.2050 0 0); + --secondary: oklch(0.2690 0 0); + --secondary-foreground: oklch(0.9850 0 0); + --muted: oklch(0.2690 0 0); + --muted-foreground: oklch(0.7080 0 0); + --accent: oklch(0.3710 0 0); + --accent-foreground: oklch(0.9850 0 0); + --destructive: oklch(0.7040 0.1910 22.2160); + --destructive-foreground: oklch(0.9850 0 0); + --border: oklch(0.2750 0 0); + --input: oklch(0.3250 0 0); + --ring: oklch(0.5560 0 0); + --chart-1: oklch(0.8100 0.1000 252); + --chart-2: oklch(0.6200 0.1900 260); + --chart-3: oklch(0.5500 0.2200 263); + --chart-4: oklch(0.4900 0.2200 264); + --chart-5: oklch(0.4200 0.1800 266); + --sidebar: oklch(0.2050 0 0); + --sidebar-foreground: oklch(0.9850 0 0); + --sidebar-primary: oklch(0.4880 0.2430 264.3760); + --sidebar-primary-foreground: oklch(0.9850 0 0); + --sidebar-accent: oklch(0.2690 0 0); + --sidebar-accent-foreground: oklch(0.9850 0 0); + --sidebar-border: oklch(0.2750 0 0); + --sidebar-ring: oklch(0.4390 0 0); + --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --radius: 0.625rem; + --shadow-x: 0; --shadow-y: 1px; - --shadow-blur: 2px; + --shadow-blur: 3px; --shadow-spread: 0px; - --shadow-opacity: 0.18; - --shadow-color: hsl(0 0% 0%); - --shadow-2xs: 0px 1px 2px 0px hsl(0 0% 0% / 0.09); - --shadow-xs: 0px 1px 2px 0px hsl(0 0% 0% / 0.09); - --shadow-sm: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18); - --shadow: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18); - --shadow-md: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 2px 4px -1px hsl(0 0% 0% / 0.18); - --shadow-lg: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 4px 6px -1px hsl(0 0% 0% / 0.18); - --shadow-xl: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 8px 10px -1px hsl(0 0% 0% / 0.18); - --shadow-2xl: 0px 1px 2px 0px hsl(0 0% 0% / 0.45); + --shadow-opacity: 0.1; + --shadow-color: oklch(0 0 0); + --shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); + --shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); + --shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10); + --shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10); + --shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10); + --shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10); + --shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10); + --shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25); } html, body, :host { font-family: var(--font-sans) !important; @@ -2404,6 +2420,193 @@ html, body, :host { transform: translate(-50%, 0) rotate(360deg) translateX(calc((var(--size, 1rem) - 0.5rem) / 2)) rotate(-360deg); } } +.apexcharts-canvas { + background: transparent !important; +} +.apexcharts-tooltip { + z-index: 9999 !important; + position: absolute !important; + background: oklch(from var(--popover) l c h / 0.95) !important; + border: 1px solid var(--border) !important; + border-radius: var(--radius-md) !important; + box-shadow: var(--shadow-lg) !important; + padding: 6px 8px !important; + font-family: var(--font-sans) !important; + font-size: 11px !important; + line-height: 1.4 !important; + color: var(--popover-foreground) !important; +} +@supports ((-webkit-backdrop-filter: none) or (backdrop-filter: none)) { + .apexcharts-tooltip { + backdrop-filter: blur(10px) !important; + -webkit-backdrop-filter: blur(10px) !important; + } +} +.apexcharts-tooltip-title { + background: oklch(from var(--muted) l c h / 0.5) !important; + border-bottom: 1px solid var(--border) !important; + color: var(--foreground) !important; + font-family: var(--font-sans) !important; + font-weight: 600 !important; + font-size: 11px !important; + padding: 4px 6px !important; + margin: -6px -8px 4px -8px !important; +} +.custom-tooltip { + padding: 0 !important; + display: flex !important; + flex-direction: column !important; + gap: 6px !important; +} +.custom-tooltip-title { + font-size: 12px !important; + font-weight: 600 !important; + color: var(--foreground) !important; + line-height: 1.2 !important; + padding-bottom: 4px !important; + border-bottom: 1px solid var(--border) !important; + margin-bottom: 2px !important; +} +.custom-tooltip-item { + display: flex !important; + align-items: center !important; + gap: 8px !important; + padding: 0 !important; + line-height: 1 !important; +} +.custom-tooltip-marker { + width: 8px !important; + height: 8px !important; + border-radius: 2px !important; + flex-shrink: 0 !important; + display: block !important; +} +.custom-tooltip-label { + font-size: 12px !important; + font-weight: 500 !important; + color: var(--muted-foreground) !important; + line-height: 1 !important; +} +.custom-tooltip-value { + font-size: 12px !important; + font-weight: 600 !important; + color: var(--popover-foreground) !important; + line-height: 1 !important; + margin-left: 4px !important; +} +.apexcharts-tooltip-series-group { + padding: 3px 0 !important; + display: grid !important; + grid-template-columns: 8px 1fr !important; + align-items: center !important; + gap: 8px !important; +} +.apexcharts-tooltip-marker { + width: 8px !important; + height: 8px !important; + border-radius: 2px !important; + margin: 0 !important; + padding: 0 !important; +} +.apexcharts-tooltip-text { + font-family: var(--font-sans) !important; + font-size: 11px !important; + color: var(--popover-foreground) !important; + padding: 0 !important; + margin: 0 !important; + line-height: 1.1 !important; +} +.apexcharts-tooltip-text-y-label, .apexcharts-tooltip-text-y-value { + line-height: 1.1 !important; +} +.apexcharts-tooltip-text-y-label { + font-weight: 500 !important; + color: var(--muted-foreground) !important; +} +.apexcharts-tooltip-text-y-value { + font-weight: 600 !important; + color: var(--popover-foreground) !important; + margin-left: 4px !important; +} +.apexcharts-xaxistooltip, .apexcharts-yaxistooltip { + z-index: 9998 !important; + background: oklch(from var(--popover) l c h / 0.95) !important; + border: 1px solid var(--border) !important; + border-radius: var(--radius-sm) !important; + box-shadow: var(--shadow-md) !important; + font-family: var(--font-sans) !important; + font-size: 11px !important; + color: var(--popover-foreground) !important; + padding: 4px 8px !important; +} +.apexcharts-xaxistooltip-bottom:before, .apexcharts-xaxistooltip-bottom:after { + border-bottom-color: var(--border) !important; +} +.apexcharts-legend { + padding: 8px 0 4px 0 !important; +} +.apexcharts-legend-series { + display: inline-flex !important; + align-items: center !important; + margin: 0 8px 4px 0 !important; + gap: 6px !important; +} +.apexcharts-legend-text { + color: var(--foreground) !important; + font-family: var(--font-sans) !important; + font-size: 13px !important; + font-weight: 500 !important; +} +.apexcharts-legend-marker { + border-radius: 2px !important; + flex-shrink: 0 !important; +} +.apexcharts-toolbar { + z-index: 100 !important; +} +.apexcharts-menu { + z-index: 9999 !important; + background: var(--popover) !important; + border: 1px solid var(--border) !important; + border-radius: var(--radius-md) !important; + box-shadow: var(--shadow-xl) !important; + padding: 4px !important; +} +.apexcharts-menu-item { + color: var(--foreground) !important; + font-family: var(--font-sans) !important; + font-size: 13px !important; + padding: 6px 12px !important; + border-radius: var(--radius-sm) !important; + transition: background-color 150ms !important; +} +.apexcharts-menu-item:hover { + background: var(--accent) !important; + color: var(--accent-foreground) !important; +} +.apexcharts-toolbar-item { + color: var(--muted-foreground) !important; + transition: color 150ms !important; +} +.apexcharts-toolbar-item:hover { + color: var(--foreground) !important; +} +.apexcharts-gridline { + stroke: var(--border) !important; + stroke-opacity: 0.5 !important; +} +.apexcharts-text { + fill: var(--muted-foreground) !important; + font-family: var(--font-sans) !important; +} +.apexcharts-xaxis-label, .apexcharts-yaxis-label { + fill: var(--muted-foreground) !important; +} +.apexcharts-datalabel, .apexcharts-datalabel-label, .apexcharts-datalabel-value { + fill: var(--foreground) !important; + font-family: var(--font-sans) !important; + font-weight: 500 !important; +} @property --tw-translate-x { syntax: "*"; inherits: false; diff --git a/NET9/BlazorInteractiveServer/wwwroot/input.css b/NET9/BlazorInteractiveServer/wwwroot/input.css index 1428af4..76fe35f 100644 --- a/NET9/BlazorInteractiveServer/wwwroot/input.css +++ b/NET9/BlazorInteractiveServer/wwwroot/input.css @@ -1,111 +1,111 @@ @import "tailwindcss"; :root { - --background: oklch(0.9900 0 0); - --foreground: oklch(0 0 0); + --background: oklch(1 0 0); + --foreground: oklch(0.1450 0 0); --card: oklch(1 0 0); - --card-foreground: oklch(0 0 0); - --popover: oklch(0.9900 0 0); - --popover-foreground: oklch(0 0 0); - --primary: oklch(0 0 0); - --primary-foreground: oklch(1 0 0); - --secondary: oklch(0.9400 0 0); - --secondary-foreground: oklch(0 0 0); + --card-foreground: oklch(0.1450 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.1450 0 0); + --primary: oklch(0.2050 0 0); + --primary-foreground: oklch(0.9850 0 0); + --secondary: oklch(0.9700 0 0); + --secondary-foreground: oklch(0.2050 0 0); --muted: oklch(0.9700 0 0); - --muted-foreground: oklch(0.4400 0 0); - --accent: oklch(0.9400 0 0); - --accent-foreground: oklch(0 0 0); - --destructive: oklch(0.6300 0.1900 23.0300); + --muted-foreground: oklch(0.5560 0 0); + --accent: oklch(0.9700 0 0); + --accent-foreground: oklch(0.2050 0 0); + --destructive: oklch(0.5770 0.2450 27.3250); --destructive-foreground: oklch(1 0 0); - --border: oklch(0.9200 0 0); - --input: oklch(0.9400 0 0); - --ring: oklch(0 0 0); - --chart-1: oklch(0.8100 0.1700 75.3500); - --chart-2: oklch(0.5500 0.2200 264.5300); - --chart-3: oklch(0.7200 0 0); - --chart-4: oklch(0.9200 0 0); - --chart-5: oklch(0.5600 0 0); - --sidebar: oklch(0.9900 0 0); - --sidebar-foreground: oklch(0 0 0); - --sidebar-primary: oklch(0 0 0); - --sidebar-primary-foreground: oklch(1 0 0); - --sidebar-accent: oklch(0.9400 0 0); - --sidebar-accent-foreground: oklch(0 0 0); - --sidebar-border: oklch(0.9400 0 0); - --sidebar-ring: oklch(0 0 0); - --font-sans: Geist, sans-serif; - --font-serif: Georgia, serif; - --font-mono: Geist Mono, monospace; - --radius: 0.5rem; - --shadow-x: 0px; + --border: oklch(0.9220 0 0); + --input: oklch(0.9220 0 0); + --ring: oklch(0.7080 0 0); + --chart-1: oklch(0.8100 0.1000 252); + --chart-2: oklch(0.6200 0.1900 260); + --chart-3: oklch(0.5500 0.2200 263); + --chart-4: oklch(0.4900 0.2200 264); + --chart-5: oklch(0.4200 0.1800 266); + --sidebar: oklch(0.9850 0 0); + --sidebar-foreground: oklch(0.1450 0 0); + --sidebar-primary: oklch(0.2050 0 0); + --sidebar-primary-foreground: oklch(0.9850 0 0); + --sidebar-accent: oklch(0.9700 0 0); + --sidebar-accent-foreground: oklch(0.2050 0 0); + --sidebar-border: oklch(0.9220 0 0); + --sidebar-ring: oklch(0.7080 0 0); + --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --radius: 0.625rem; + --shadow-x: 0; --shadow-y: 1px; - --shadow-blur: 2px; + --shadow-blur: 3px; --shadow-spread: 0px; - --shadow-opacity: 0.18; - --shadow-color: hsl(0 0% 0%); - --shadow-2xs: 0px 1px 2px 0px hsl(0 0% 0% / 0.09); - --shadow-xs: 0px 1px 2px 0px hsl(0 0% 0% / 0.09); - --shadow-sm: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18); - --shadow: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18); - --shadow-md: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 2px 4px -1px hsl(0 0% 0% / 0.18); - --shadow-lg: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 4px 6px -1px hsl(0 0% 0% / 0.18); - --shadow-xl: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 8px 10px -1px hsl(0 0% 0% / 0.18); - --shadow-2xl: 0px 1px 2px 0px hsl(0 0% 0% / 0.45); + --shadow-opacity: 0.1; + --shadow-color: oklch(0 0 0); + --shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); + --shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); + --shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10); + --shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10); + --shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10); + --shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10); + --shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10); + --shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25); --tracking-normal: 0em; --spacing: 0.25rem; } .dark { - --background: oklch(0 0 0); - --foreground: oklch(1 0 0); - --card: oklch(0.1400 0 0); - --card-foreground: oklch(1 0 0); - --popover: oklch(0.1800 0 0); - --popover-foreground: oklch(1 0 0); - --primary: oklch(1 0 0); - --primary-foreground: oklch(0 0 0); - --secondary: oklch(0.2500 0 0); - --secondary-foreground: oklch(1 0 0); - --muted: oklch(0.2300 0 0); - --muted-foreground: oklch(0.7200 0 0); - --accent: oklch(0.3200 0 0); - --accent-foreground: oklch(1 0 0); - --destructive: oklch(0.6900 0.2000 23.9100); - --destructive-foreground: oklch(0 0 0); - --border: oklch(0.2600 0 0); - --input: oklch(0.3200 0 0); - --ring: oklch(0.7200 0 0); - --chart-1: oklch(0.8100 0.1700 75.3500); - --chart-2: oklch(0.5800 0.2100 260.8400); - --chart-3: oklch(0.5600 0 0); - --chart-4: oklch(0.4400 0 0); - --chart-5: oklch(0.9200 0 0); - --sidebar: oklch(0.1800 0 0); - --sidebar-foreground: oklch(1 0 0); - --sidebar-primary: oklch(1 0 0); - --sidebar-primary-foreground: oklch(0 0 0); - --sidebar-accent: oklch(0.3200 0 0); - --sidebar-accent-foreground: oklch(1 0 0); - --sidebar-border: oklch(0.3200 0 0); - --sidebar-ring: oklch(0.7200 0 0); - --font-sans: Geist, sans-serif; - --font-serif: Georgia, serif; - --font-mono: Geist Mono, monospace; - --radius: 0.5rem; - --shadow-x: 0px; + --background: oklch(0.1450 0 0); + --foreground: oklch(0.9850 0 0); + --card: oklch(0.2050 0 0); + --card-foreground: oklch(0.9850 0 0); + --popover: oklch(0.2690 0 0); + --popover-foreground: oklch(0.9850 0 0); + --primary: oklch(0.9220 0 0); + --primary-foreground: oklch(0.2050 0 0); + --secondary: oklch(0.2690 0 0); + --secondary-foreground: oklch(0.9850 0 0); + --muted: oklch(0.2690 0 0); + --muted-foreground: oklch(0.7080 0 0); + --accent: oklch(0.3710 0 0); + --accent-foreground: oklch(0.9850 0 0); + --destructive: oklch(0.7040 0.1910 22.2160); + --destructive-foreground: oklch(0.9850 0 0); + --border: oklch(0.2750 0 0); + --input: oklch(0.3250 0 0); + --ring: oklch(0.5560 0 0); + --chart-1: oklch(0.8100 0.1000 252); + --chart-2: oklch(0.6200 0.1900 260); + --chart-3: oklch(0.5500 0.2200 263); + --chart-4: oklch(0.4900 0.2200 264); + --chart-5: oklch(0.4200 0.1800 266); + --sidebar: oklch(0.2050 0 0); + --sidebar-foreground: oklch(0.9850 0 0); + --sidebar-primary: oklch(0.4880 0.2430 264.3760); + --sidebar-primary-foreground: oklch(0.9850 0 0); + --sidebar-accent: oklch(0.2690 0 0); + --sidebar-accent-foreground: oklch(0.9850 0 0); + --sidebar-border: oklch(0.2750 0 0); + --sidebar-ring: oklch(0.4390 0 0); + --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --radius: 0.625rem; + --shadow-x: 0; --shadow-y: 1px; - --shadow-blur: 2px; + --shadow-blur: 3px; --shadow-spread: 0px; - --shadow-opacity: 0.18; - --shadow-color: hsl(0 0% 0%); - --shadow-2xs: 0px 1px 2px 0px hsl(0 0% 0% / 0.09); - --shadow-xs: 0px 1px 2px 0px hsl(0 0% 0% / 0.09); - --shadow-sm: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18); - --shadow: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18); - --shadow-md: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 2px 4px -1px hsl(0 0% 0% / 0.18); - --shadow-lg: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 4px 6px -1px hsl(0 0% 0% / 0.18); - --shadow-xl: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 8px 10px -1px hsl(0 0% 0% / 0.18); - --shadow-2xl: 0px 1px 2px 0px hsl(0 0% 0% / 0.45); + --shadow-opacity: 0.1; + --shadow-color: oklch(0 0 0); + --shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); + --shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); + --shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10); + --shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10); + --shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10); + --shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10); + --shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10); + --shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25); } @theme inline { @@ -160,7 +160,6 @@ --shadow-xl: var(--shadow-xl); --shadow-2xl: var(--shadow-2xl); } - /* Ensure font-family updates when theme changes - must come after @theme inline */ html, body, :host { font-family: var(--font-sans) !important; @@ -186,3 +185,239 @@ html, body, :host { 0% { transform: translate(-50%, 0) rotate(0deg) translateX(calc((var(--size, 1rem) - 0.5rem) / 2)) rotate(0deg); } 100% { transform: translate(-50%, 0) rotate(360deg) translateX(calc((var(--size, 1rem) - 0.5rem) / 2)) rotate(-360deg); } } + +/* ApexCharts Theme Integration */ +.apexcharts-canvas { + background: transparent !important; +} + +/* Tooltip - Uses theme variables for automatic light/dark adaptation */ +.apexcharts-tooltip { + z-index: 9999 !important; + position: absolute !important; + background: oklch(from var(--popover) l c h / 0.95) !important; + border: 1px solid var(--border) !important; + border-radius: var(--radius-md) !important; + box-shadow: var(--shadow-lg) !important; + padding: 6px 8px !important; + font-family: var(--font-sans) !important; + font-size: 11px !important; + line-height: 1.4 !important; + color: var(--popover-foreground) !important; +} + +/* Backdrop blur with browser support fallback */ +@supports ((-webkit-backdrop-filter: none) or (backdrop-filter: none)) { + .apexcharts-tooltip { + backdrop-filter: blur(10px) !important; + -webkit-backdrop-filter: blur(10px) !important; + } +} + +/* Tooltip title */ +.apexcharts-tooltip-title { + background: oklch(from var(--muted) l c h / 0.5) !important; + border-bottom: 1px solid var(--border) !important; + color: var(--foreground) !important; + font-family: var(--font-sans) !important; + font-weight: 600 !important; + font-size: 11px !important; + padding: 4px 6px !important; + margin: -6px -8px 4px -8px !important; +} + +/* Custom Tooltip - Full control over structure */ +.custom-tooltip { + padding: 0 !important; + display: flex !important; + flex-direction: column !important; + gap: 6px !important; +} + +.custom-tooltip-title { + font-size: 12px !important; + font-weight: 600 !important; + color: var(--foreground) !important; + line-height: 1.2 !important; + padding-bottom: 4px !important; + border-bottom: 1px solid var(--border) !important; + margin-bottom: 2px !important; +} + +.custom-tooltip-item { + display: flex !important; + align-items: center !important; + gap: 8px !important; + padding: 0 !important; + line-height: 1 !important; +} + +.custom-tooltip-marker { + width: 8px !important; + height: 8px !important; + border-radius: 2px !important; + flex-shrink: 0 !important; + display: block !important; +} + +.custom-tooltip-label { + font-size: 12px !important; + font-weight: 500 !important; + color: var(--muted-foreground) !important; + line-height: 1 !important; +} + +.custom-tooltip-value { + font-size: 12px !important; + font-weight: 600 !important; + color: var(--popover-foreground) !important; + line-height: 1 !important; + margin-left: 4px !important; +} + +/* Fallback for default ApexCharts tooltip */ +.apexcharts-tooltip-series-group { + padding: 3px 0 !important; + display: grid !important; + grid-template-columns: 8px 1fr !important; + align-items: center !important; + gap: 8px !important; +} + +.apexcharts-tooltip-marker { + width: 8px !important; + height: 8px !important; + border-radius: 2px !important; + margin: 0 !important; + padding: 0 !important; +} + +.apexcharts-tooltip-text { + font-family: var(--font-sans) !important; + font-size: 11px !important; + color: var(--popover-foreground) !important; + padding: 0 !important; + margin: 0 !important; + line-height: 1.1 !important; +} + +.apexcharts-tooltip-text-y-label, +.apexcharts-tooltip-text-y-value { + line-height: 1.1 !important; +} + +.apexcharts-tooltip-text-y-label { + font-weight: 500 !important; + color: var(--muted-foreground) !important; +} + +.apexcharts-tooltip-text-y-value { + font-weight: 600 !important; + color: var(--popover-foreground) !important; + margin-left: 4px !important; +} + +/* Axis tooltips */ +.apexcharts-xaxistooltip, +.apexcharts-yaxistooltip { + z-index: 9998 !important; + background: oklch(from var(--popover) l c h / 0.95) !important; + border: 1px solid var(--border) !important; + border-radius: var(--radius-sm) !important; + box-shadow: var(--shadow-md) !important; + font-family: var(--font-sans) !important; + font-size: 11px !important; + color: var(--popover-foreground) !important; + padding: 4px 8px !important; +} + +.apexcharts-xaxistooltip-bottom:before, +.apexcharts-xaxistooltip-bottom:after { + border-bottom-color: var(--border) !important; +} + +/* Legend */ +.apexcharts-legend { + padding: 8px 0 4px 0 !important; +} + +.apexcharts-legend-series { + display: inline-flex !important; + align-items: center !important; + margin: 0 8px 4px 0 !important; + gap: 6px !important; +} + +.apexcharts-legend-text { + color: var(--foreground) !important; + font-family: var(--font-sans) !important; + font-size: 13px !important; + font-weight: 500 !important; +} + +.apexcharts-legend-marker { + border-radius: 2px !important; + flex-shrink: 0 !important; +} + +/* Toolbar */ +.apexcharts-toolbar { + z-index: 100 !important; +} + +.apexcharts-menu { + z-index: 9999 !important; + background: var(--popover) !important; + border: 1px solid var(--border) !important; + border-radius: var(--radius-md) !important; + box-shadow: var(--shadow-xl) !important; + padding: 4px !important; +} + +.apexcharts-menu-item { + color: var(--foreground) !important; + font-family: var(--font-sans) !important; + font-size: 13px !important; + padding: 6px 12px !important; + border-radius: var(--radius-sm) !important; + transition: background-color 150ms !important; +} + +.apexcharts-menu-item:hover { + background: var(--accent) !important; + color: var(--accent-foreground) !important; +} + +.apexcharts-toolbar-item { + color: var(--muted-foreground) !important; + transition: color 150ms !important; +} + +.apexcharts-toolbar-item:hover { + color: var(--foreground) !important; +} + +/* Grid and axis */ +.apexcharts-gridline { + stroke: var(--border) !important; + stroke-opacity: 0.5 !important; +} + +.apexcharts-text { + fill: var(--muted-foreground) !important; + font-family: var(--font-sans) !important; +} + +.apexcharts-xaxis-label, +.apexcharts-yaxis-label { + fill: var(--muted-foreground) !important; +} + +/* Data labels */ +.apexcharts-datalabel, +.apexcharts-datalabel-label, +.apexcharts-datalabel-value { + fill: var(--foreground) !important; + font-family: var(--font-sans) !important; + font-weight: 500 !important; +} diff --git a/README.md b/README.md index e5440fe..ced729f 100644 --- a/README.md +++ b/README.md @@ -29,15 +29,15 @@ ShellUI transforms Blazor component development with a hybrid approach: - **CLI-First**: Copy components to YOUR codebase for full control (`shellui add button`) - **NuGet Option**: Traditional package install for quick starts (`dotnet add package ShellUI.Components`) - **Choose your workflow**: Use CLI for customization, NuGet for speed, or mix both -- Powered by Tailwind CSS v4.1.17 (standalone CLI - no Node.js required!) +- Powered by Tailwind CSS v4.1.18 (standalone CLI - no Node.js required!) - Best of both worlds: flexibility when you need it, convenience when you want it -## Current Status: 73 Components Complete! 🎉 +## Current Status: 80 Components Complete! 🎉 **ShellUI is now fully functional!** We've completed: - ✅ **CLI Tool** (`dotnet tool install -g ShellUI.CLI`) - ✅ **NuGet Package** (`dotnet add package ShellUI.Components`) -- ✅ **73 Production-Ready Components** with Tailwind v4.1.17 +- ✅ **80 Production-Ready Components** with Tailwind v4.1.18 - ✅ **Hybrid Workflow** (CLI + NuGet) - ✅ **No Node.js Required** (Standalone Tailwind CLI) - ✅ **Comprehensive Documentation** @@ -129,7 +129,7 @@ Dropdown, Command, ContextMenu, HoverCard, ThemeToggle, EmptyState, FileUpload **Advanced Components (16):** Calendar, DatePicker, DateRangePicker, TimePicker, Combobox, AlertDialog, Carousel, CarouselItem, CarouselContent, CarouselPrevious, CarouselNext, CarouselDots -### ✅ Tailwind CSS v4.1.17 Integration +### ✅ Tailwind CSS v4.1.18 Integration **Two Setup Methods:** @@ -147,7 +147,7 @@ shellui init # Choose "standalone" shellui init # Choose "npm" # Or: dotnet shellui init ``` -- Installs `tailwindcss@^4.1.17` + `@tailwindcss/cli@^4.1.17` +- Installs `tailwindcss@^4.1.18` + `@tailwindcss/cli@^4.1.18` - Uses `npx @tailwindcss/cli` for builds - Requires Node.js @@ -186,7 +186,7 @@ shellui init # Choose "npm" ## Design Principles 1. **Copy, Don't Install**: Components are copied to your project, not imported from a package -2. **Tailwind-First**: All styling uses Tailwind CSS v4.1.17 utility classes +2. **Tailwind-First**: All styling uses Tailwind CSS v4.1.18 utility classes 3. **Accessible by Default**: WCAG 2.1 AA compliant out of the box 4. **Composable**: Build complex components from simple ones 5. **Customizable**: Modify any component to fit your needs @@ -213,7 +213,7 @@ shellui init # Choose "npm" **Use both:** Start with NuGet, migrate to CLI for components you customize heavily! -### Why Tailwind v4.1.17? +### Why Tailwind v4.1.18? - Latest stable version with v4 features - Better performance than v3 - Improved dark mode support @@ -284,7 +284,7 @@ Simply edit the component file in `Components/UI/` - it's yours to modify! - .NET 8.0 or higher - **Choice of Tailwind setup:** - **Standalone CLI** (recommended): No Node.js required - - **npm**: Requires Node.js, uses `tailwindcss@^4.1.17` + - **npm**: Requires Node.js, uses `tailwindcss@^4.1.18` ## Comparison with Existing Solutions @@ -293,7 +293,7 @@ Simply edit the component file in `Components/UI/` - it's yours to modify! | CLI Installation | ✅ | ❌ | ❌ | ❌ | | NuGet Package | ✅ | ✅ | ✅ | ✅ | | Component Ownership (CLI) | ✅ | ❌ | ❌ | ❌ | -| Tailwind CSS | ✅ (v4.1.17) | ❌ | ❌ | ❌ | +| Tailwind CSS | ✅ (v4.1.18) | ❌ | ❌ | ❌ | | No Node.js Required | ✅ | N/A | N/A | N/A | | Hybrid Workflow | ✅ | ❌ | ❌ | ❌ | | Free & Open Source | ✅ | ✅ | Partial | ✅ | @@ -334,10 +334,10 @@ dotnet add package ShellUI.Components **📋 NuGet Package Setup Guide:** -#### 1. **Install Tailwind CSS v4.1.17** +#### 1. **Install Tailwind CSS v4.1.18** ```bash # Download the standalone Tailwind CLI -curl -L https://github.com/tailwindlabs/tailwindcss/releases/download/v4.1.17/tailwindcss-linux-x64 -o tailwindcss +curl -L https://github.com/tailwindlabs/tailwindcss/releases/download/v4.1.18/tailwindcss-linux-x64 -o tailwindcss chmod +x tailwindcss sudo mv tailwindcss /usr/local/bin/ ``` @@ -566,7 +566,7 @@ Add Tailwind CSS to your layout: **📋 NuGet Package Checklist:** - ✅ Install package: `dotnet add package ShellUI.Components` - ✅ **Remove Bootstrap**: Delete `wwwroot/lib/bootstrap/` folder and `wwwroot/css/bootstrap*.css` files -- ✅ **Install Tailwind**: Download Tailwind CLI v4.1.17 +- ✅ **Install Tailwind**: Download Tailwind CLI v4.1.18 - ✅ **Include theme CSS**: Add ShellUI theme CSS link to your main layout - ✅ **Add using**: `@using ShellUI.Components` in `_Imports.razor` - ✅ **Create config**: `wwwroot/tailwind.config.js` @@ -610,7 +610,7 @@ MIT License - See LICENSE.txt for details ## Status -**✅ Production Ready:** 69 components, CLI + NuGet, Tailwind v4.1.17 integration +**✅ Production Ready:** 69 components, CLI + NuGet, Tailwind v4.1.18 integration **🚀 Ready to use today!** --- diff --git a/docs/RELEASE_NOTES.md b/docs/RELEASE_NOTES.md index d9a9021..c05f329 100644 --- a/docs/RELEASE_NOTES.md +++ b/docs/RELEASE_NOTES.md @@ -1,23 +1,54 @@ -# ShellUI v0.1.1 🔧 +# ShellUI v0.2.0 📊 -> Hotfix release - Package publishing fix +> Feature release - Charts & Data Visualization + +## ✨ New Features + +### Charts & Data Visualization (7 new components) +Built on **ApexCharts.Blazor** with full ShellUI theme integration: + +- **Chart** - Base chart component with theme-aware styling +- **BarChart** - Vertical bar charts +- **LineChart** - Smooth line charts +- **AreaChart** - Filled area charts +- **PieChart** - Pie charts with custom tooltips +- **MultiSeriesChart** - Multiple data series on one chart +- **ChartSeries** - Flexible series composition + +### Chart Themes +Three built-in color themes via `ChartTheme` enum: +- **Default** - Professional blue-based palette (blue, red, green, yellow, purple) +- **Colorful** - Vibrant multi-color palette with 7 colors +- **Monochrome** - Slate grays for minimal aesthetic + +### Theme-Aware Chart Containers +Charts automatically use your theme's CSS variables: +- `var(--radius)` for border radius +- `var(--shadow)` for box shadow +- `var(--border)`, `var(--card)`, `var(--card-foreground)` for colors + +### Custom Tooltips +Fully custom HTML tooltips replacing ApexCharts defaults: +- Compact, shadcn-inspired design +- Proper marker/text alignment via flexbox +- Multi-series support (shows all values at a data point) +- Light/dark mode support via CSS variables +- Separate pie chart tooltip using `seriesIndex` ## 🐛 Bug Fixes -### Fixed Package Publishing -- **Prevented `ShellUI.Core` from being published separately** - Set `IsPackable=false` on `ShellUI.Core` project -- **Updated release workflow** - Now only publishes the 2 intended packages: - - ✅ `ShellUI.CLI` - CLI tool for component management - - ✅ `ShellUI.Components` - Component library package -- **Updated documentation** - README now correctly reflects that only 2 packages are published +- **Fixed version mismatch** - Component versions now correctly read from assembly metadata instead of hardcoded fallback +- **Fixed Tailwind version in config** - `shellui.json` now correctly shows Tailwind v4.1.18 for all install methods +- **Fixed CS1998 warnings** - Removed unnecessary `async` from synchronous methods in `ComponentInstaller` -### What Changed -- `ShellUI.Core` is an internal dependency of `ShellUI.Components` and should not be published as a standalone package -- Future releases will only publish the 2 intended packages +## 🔧 Improvements -## 📦 Installation +- **Tailwind CSS updated to v4.1.18** +- **Component count: 80** (73 existing + 7 new chart components) +- **X-axis labels** - Charts use `XAxisType.Category` for proper string label display +- **Version system** - Fallback version now reads `AssemblyInformationalVersion` baked at build time -No changes to installation process: +## 📦 Installation ```bash # Install CLI globally @@ -26,8 +57,8 @@ dotnet tool install -g ShellUI.CLI # Initialize your project shellui init -# Add components -shellui add button badge alert card +# Add chart components +shellui add chart bar-chart line-chart pie-chart area-chart multi-series-chart ``` Or via NuGet: @@ -35,6 +66,11 @@ Or via NuGet: dotnet add package ShellUI.Components ``` +**Note:** Charts require the `Blazor-ApexCharts` NuGet package: +```bash +dotnet add package Blazor-ApexCharts +``` + ## 🔗 Links - **Documentation**: https://shellui.dev @@ -43,4 +79,4 @@ dotnet add package ShellUI.Components --- -**Full Changelog**: https://github.com/shellui-dev/shellui/compare/v0.1.0...v0.1.1 +**Full Changelog**: https://github.com/shellui-dev/shellui/compare/v0.1.1...v0.2.0 diff --git a/src/ShellUI.CLI/Services/ComponentInstaller.cs b/src/ShellUI.CLI/Services/ComponentInstaller.cs index cb34952..ccb94c6 100644 --- a/src/ShellUI.CLI/Services/ComponentInstaller.cs +++ b/src/ShellUI.CLI/Services/ComponentInstaller.cs @@ -60,13 +60,14 @@ public static async Task InstallComponents(string[] components, bool force) await AnsiConsole.Status() .Spinner(Spinner.Known.Dots) .SpinnerStyle(Style.Parse("green")) - .StartAsync("Installing components...", async ctx => + .StartAsync("Installing components...", ctx => { foreach (var componentName in componentList) { ctx.Status($"Installing {componentName}..."); InstallComponentWithDependencies(componentName, config, projectInfo, force, installedSet, ref successCount, ref skippedCount, failedComponents); } + return Task.CompletedTask; }); // Update config @@ -86,10 +87,10 @@ await AnsiConsole.Status() AnsiConsole.MarkupLine($"[red]Failed: {string.Join(", ", failedComponents)}[/]"); } - public static async Task InstallComponentForInitAsync(string componentName, ProjectInfo projectInfo) + public static Task InstallComponentForInitAsync(string componentName, ProjectInfo projectInfo) { var metadata = ComponentRegistry.Components.GetValueOrDefault(componentName.ToLower()); - if (metadata == null) return; + if (metadata == null) return Task.CompletedTask; var config = new ShellUIConfig { @@ -104,6 +105,8 @@ public static async Task InstallComponentForInitAsync(string componentName, Proj { AnsiConsole.MarkupLine($"[green]✅ Installed:[/] {componentName}"); } + + return Task.CompletedTask; } public static void InstallComponent(string componentName, ComponentMetadata metadata, bool force, bool skipConfig = false) diff --git a/src/ShellUI.CLI/Services/InitService.cs b/src/ShellUI.CLI/Services/InitService.cs index e19d570..be32479 100644 --- a/src/ShellUI.CLI/Services/InitService.cs +++ b/src/ShellUI.CLI/Services/InitService.cs @@ -106,7 +106,7 @@ await AnsiConsole.Status() Tailwind = new TailwindConfig { Enabled = true, - Version = method == "npm" ? "4.1.17" : "3.4.0", + Version = "4.1.18", Method = method, CssPath = "wwwroot/app.css" } @@ -191,8 +191,8 @@ private static async Task SetupTailwindNpmAsync() // Install Tailwind CSS packages (v4 with @tailwindcss/cli) AnsiConsole.MarkupLine("[cyan]Installing Tailwind CSS packages...[/]"); - await RunNpmCommandAsync("install", "-D", "tailwindcss@^4.1.17", "@tailwindcss/cli@^4.1.17"); - AnsiConsole.MarkupLine("[green]Installed:[/] tailwindcss v4.1.17, @tailwindcss/cli"); + await RunNpmCommandAsync("install", "-D", "tailwindcss@^4.1.18", "@tailwindcss/cli@^4.1.18"); + AnsiConsole.MarkupLine("[green]Installed:[/] tailwindcss v4.1.18, @tailwindcss/cli"); // Create CSS files AnsiConsole.MarkupLine("[cyan]Creating CSS files...[/]"); diff --git a/src/ShellUI.CLI/Services/TailwindDownloader.cs b/src/ShellUI.CLI/Services/TailwindDownloader.cs index 6a0ffb6..23420b9 100644 --- a/src/ShellUI.CLI/Services/TailwindDownloader.cs +++ b/src/ShellUI.CLI/Services/TailwindDownloader.cs @@ -6,7 +6,7 @@ namespace ShellUI.CLI.Services; public class TailwindDownloader { - private const string TailwindVersion = "v4.1.17"; + private const string TailwindVersion = "v4.1.18"; private const string BaseUrl = "https://github.com/tailwindlabs/tailwindcss/releases/download"; public static async Task EnsureTailwindCliAsync(string projectRoot) diff --git a/src/ShellUI.Components/Components/AreaChart.razor b/src/ShellUI.Components/Components/AreaChart.razor new file mode 100644 index 0000000..5095c30 --- /dev/null +++ b/src/ShellUI.Components/Components/AreaChart.razor @@ -0,0 +1,26 @@ +@namespace ShellUI.Components +@using ApexCharts +@inherits Chart +@typeparam TItem where TItem : class + +
+ + + +
+ +@code { + [Parameter] public IEnumerable? Data { get; set; } + [Parameter] public string Name { get; set; } = "Data"; + [Parameter] public Func? XValue { get; set; } + [Parameter] public Func? YValue { get; set; } +} \ No newline at end of file diff --git a/src/ShellUI.Components/Components/BarChart.razor b/src/ShellUI.Components/Components/BarChart.razor new file mode 100644 index 0000000..55bda1c --- /dev/null +++ b/src/ShellUI.Components/Components/BarChart.razor @@ -0,0 +1,26 @@ +@namespace ShellUI.Components +@using ApexCharts +@inherits Chart +@typeparam TItem where TItem : class + +
+ + + +
+ +@code { + [Parameter] public IEnumerable? Data { get; set; } + [Parameter] public string Name { get; set; } = "Data"; + [Parameter] public Func? XValue { get; set; } + [Parameter] public Func? YValue { get; set; } +} \ No newline at end of file diff --git a/src/ShellUI.Components/Components/Chart.razor b/src/ShellUI.Components/Components/Chart.razor new file mode 100644 index 0000000..2afbcb4 --- /dev/null +++ b/src/ShellUI.Components/Components/Chart.razor @@ -0,0 +1,58 @@ +@namespace ShellUI.Components +@using ApexCharts +@typeparam TItem where TItem : class + +
+ + @ChildContent + +
+ +@code { + [Parameter] public ChartTheme Theme { get; set; } = ChartTheme.Default; + [Parameter] public string? Title { get; set; } + [Parameter] public string? Subtitle { get; set; } + [Parameter] public string? Class { get; set; } + [Parameter] public string? Height { get; set; } = "400px"; + [Parameter] public string? Width { get; set; } = "100%"; + [Parameter] public RenderFragment? ChildContent { get; set; } + + private ApexCharts.ApexChartOptions? _chartOptions; + + protected ApexCharts.ApexChartOptions ChartOptions + { + get + { + if (_chartOptions == null) + { + _chartOptions = ChartVariants.GetOptions(Theme); + + // Apply title if provided + if (!string.IsNullOrEmpty(Title)) + { + _chartOptions.Title = _chartOptions.Title ?? new ApexCharts.Title(); + _chartOptions.Title.Text = Title; + } + + // Apply subtitle if provided + if (!string.IsNullOrEmpty(Subtitle)) + { + _chartOptions.Subtitle = _chartOptions.Subtitle ?? new ApexCharts.Subtitle(); + _chartOptions.Subtitle.Text = Subtitle; + } + } + return _chartOptions; + } + set => _chartOptions = value; + } + + protected string ComputedClass => Shell.Cn( + "border border-border bg-card text-card-foreground overflow-hidden", + "[border-radius:var(--radius)] [box-shadow:var(--shadow)]", + Class + ); +} \ No newline at end of file diff --git a/src/ShellUI.Components/Components/ChartSeries.razor b/src/ShellUI.Components/Components/ChartSeries.razor new file mode 100644 index 0000000..8bac7f0 --- /dev/null +++ b/src/ShellUI.Components/Components/ChartSeries.razor @@ -0,0 +1,18 @@ +@namespace ShellUI.Components +@using ApexCharts +@typeparam TItem where TItem : class + + + +@code { + [Parameter] public IEnumerable? Data { get; set; } + [Parameter] public string Name { get; set; } = "Series"; + [Parameter] public SeriesType SeriesType { get; set; } = SeriesType.Line; + [Parameter] public Func? XValue { get; set; } + [Parameter] public Func? YValue { get; set; } +} \ No newline at end of file diff --git a/src/ShellUI.Components/Components/LineChart.razor b/src/ShellUI.Components/Components/LineChart.razor new file mode 100644 index 0000000..bb7a282 --- /dev/null +++ b/src/ShellUI.Components/Components/LineChart.razor @@ -0,0 +1,26 @@ +@namespace ShellUI.Components +@using ApexCharts +@inherits Chart +@typeparam TItem where TItem : class + +
+ + + +
+ +@code { + [Parameter] public IEnumerable? Data { get; set; } + [Parameter] public string Name { get; set; } = "Data"; + [Parameter] public Func? XValue { get; set; } + [Parameter] public Func? YValue { get; set; } +} \ No newline at end of file diff --git a/src/ShellUI.Components/Components/MultiSeriesChart.razor b/src/ShellUI.Components/Components/MultiSeriesChart.razor new file mode 100644 index 0000000..6e2bc7c --- /dev/null +++ b/src/ShellUI.Components/Components/MultiSeriesChart.razor @@ -0,0 +1,17 @@ +@namespace ShellUI.Components +@using ApexCharts +@inherits Chart +@typeparam TItem where TItem : class + + + @ChildContent + + +@code { + [Parameter] public new RenderFragment? ChildContent { get; set; } +} \ No newline at end of file diff --git a/src/ShellUI.Components/Components/PieChart.razor b/src/ShellUI.Components/Components/PieChart.razor new file mode 100644 index 0000000..52e7e26 --- /dev/null +++ b/src/ShellUI.Components/Components/PieChart.razor @@ -0,0 +1,59 @@ +@namespace ShellUI.Components +@using ApexCharts +@inherits Chart +@typeparam TItem where TItem : class + +
+ + + +
+ +@code { + [Parameter] public IEnumerable? Data { get; set; } + [Parameter] public string Name { get; set; } = "Data"; + [Parameter] public Func? XValue { get; set; } + [Parameter] public Func? YValue { get; set; } + + protected override void OnParametersSet() + { + base.OnParametersSet(); + + if (ChartOptions.Chart != null) + { + ChartOptions.Chart.Type = ApexCharts.ChartType.Pie; + } + + /* Custom tooltip for pie chart */ + ChartOptions.Tooltip = new ApexCharts.Tooltip + { + Enabled = true, + Style = new ApexCharts.TooltipStyle + { + FontSize = "12px" + }, + Custom = @"function({ series, seriesIndex, dataPointIndex, w }) { + const label = w.globals.labels[seriesIndex]; + const value = series[seriesIndex]; + const color = w.config.colors[seriesIndex]; + + return '
' + + '
' + + '' + + '' + label + ':' + + '' + value + '' + + '
' + + '
'; + }" + }; + } +} \ No newline at end of file diff --git a/src/ShellUI.Components/ShellUI.Components.csproj b/src/ShellUI.Components/ShellUI.Components.csproj index 5cd4573..79a842b 100644 --- a/src/ShellUI.Components/ShellUI.Components.csproj +++ b/src/ShellUI.Components/ShellUI.Components.csproj @@ -34,6 +34,7 @@ + diff --git a/src/ShellUI.Components/Variants/ChartVariants.cs b/src/ShellUI.Components/Variants/ChartVariants.cs new file mode 100644 index 0000000..d718866 --- /dev/null +++ b/src/ShellUI.Components/Variants/ChartVariants.cs @@ -0,0 +1,216 @@ +using System.Collections.Generic; + +namespace ShellUI.Components; + +/* Chart color themes */ +public enum ChartTheme +{ + Default, // Balanced color palette suitable for light and dark modes + Colorful, // Vibrant colors for emphasis + Monochrome // Grayscale for minimal aesthetic +} + +/* ApexCharts configuration optimized for ShellUI theme system. + Note: ApexCharts doesn't support CSS variables in color arrays, so we use literal colors. + Text and borders use CSS variables (styled via CSS) for automatic dark mode support. */ +public static class ChartVariants +{ + // Creates chart options with theme-aware styling + public static ApexCharts.ApexChartOptions GetOptions(ChartTheme theme = ChartTheme.Default) where TItem : class + { + return new ApexCharts.ApexChartOptions + { + Chart = new ApexCharts.Chart + { + Background = "transparent", + FontFamily = "inherit", // Inherits from CSS + Toolbar = new ApexCharts.Toolbar + { + Show = true, + Tools = new ApexCharts.Tools + { + Download = true, + Selection = true, + Zoom = true, + Zoomin = true, + Zoomout = true, + Pan = true, + Reset = true + } + } + }, + + Theme = new ApexCharts.Theme(), + Colors = GetThemeColors(theme), + + Stroke = new ApexCharts.Stroke + { + Width = 2, + Curve = ApexCharts.Curve.Smooth, + LineCap = ApexCharts.LineCap.Round + }, + + Fill = new ApexCharts.Fill { Opacity = 0.85 }, + + DataLabels = new ApexCharts.DataLabels + { + Enabled = false + }, + + Grid = new ApexCharts.Grid + { + StrokeDashArray = 3, + Position = ApexCharts.GridPosition.Back + }, + + Xaxis = new ApexCharts.XAxis + { + Type = ApexCharts.XAxisType.Category, // Use category for proper label display + Labels = new ApexCharts.XAxisLabels + { + Style = new ApexCharts.AxisLabelStyle + { + FontSize = "12px" + } + }, + AxisBorder = new ApexCharts.AxisBorder + { + Show = true + }, + AxisTicks = new ApexCharts.AxisTicks + { + Show = true + } + }, + + Yaxis = new List + { + new ApexCharts.YAxis + { + Labels = new ApexCharts.YAxisLabels + { + Style = new ApexCharts.AxisLabelStyle + { + FontSize = "12px" + } + } + } + }, + + Legend = new ApexCharts.Legend + { + Show = true, + Position = ApexCharts.LegendPosition.Bottom, + FontSize = "13px", + Markers = new ApexCharts.LegendMarkers + { + Width = 10, + Height = 10, + Radius = 2 + } + }, + + Tooltip = new ApexCharts.Tooltip + { + Enabled = true, + Shared = true, + Intersect = false, + Style = new ApexCharts.TooltipStyle + { + FontSize = "12px" + }, + Custom = @"function({ series, seriesIndex, dataPointIndex, w }) { + const xLabel = w.globals.labels[dataPointIndex] || ''; + let html = '
'; + + if (xLabel) { + html += '
' + xLabel + '
'; + } + + w.globals.initialSeries.forEach((s, idx) => { + const color = w.config.colors[idx]; + const name = s.name || ''; + const value = series[idx] && series[idx][dataPointIndex] !== undefined + ? series[idx][dataPointIndex] + : '-'; + + html += '
' + + '' + + '' + name + ':' + + '' + value + '' + + '
'; + }); + + html += '
'; + return html; + }" + }, + + Title = new ApexCharts.Title + { + Style = new ApexCharts.TitleStyle + { + FontSize = "16px", + FontWeight = "600" + } + }, + + Subtitle = new ApexCharts.Subtitle + { + Style = new ApexCharts.SubtitleStyle + { + FontSize = "14px" + } + }, + + PlotOptions = new ApexCharts.PlotOptions + { + Bar = new ApexCharts.PlotOptionsBar + { + BorderRadius = 4, + ColumnWidth = "60%" + }, + Pie = new ApexCharts.PlotOptionsPie + { + ExpandOnClick = true + } + } + }; + } + + /* Returns color palette based on selected theme. + Colors are literal values because ApexCharts doesn't support CSS variables in color arrays. */ + private static List GetThemeColors(ChartTheme theme) + { + return theme switch + { + ChartTheme.Default => new List + { + "#2563eb", // blue-600 + "#dc2626", // red-600 + "#16a34a", // green-600 + "#ca8a04", // yellow-600 + "#9333ea" // purple-600 + }, + ChartTheme.Colorful => new List + { + "#3b82f6", // blue-500 + "#ef4444", // red-500 + "#10b981", // green-500 + "#f59e0b", // yellow-500 + "#8b5cf6", // purple-500 + "#ec4899", // pink-500 + "#06b6d4" // cyan-500 + }, + ChartTheme.Monochrome => new List + { + "#64748b", // slate-500 - works in both light/dark + "#94a3b8", // slate-400 + "#cbd5e1", // slate-300 + "#475569", // slate-600 + "#334155" // slate-700 + }, + _ => GetThemeColors(ChartTheme.Default) + }; + } +} \ No newline at end of file diff --git a/src/ShellUI.Components/_Imports.razor b/src/ShellUI.Components/_Imports.razor index 7728512..e7fe4f3 100644 --- a/src/ShellUI.Components/_Imports.razor +++ b/src/ShellUI.Components/_Imports.razor @@ -1 +1,2 @@ -@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web +@using ApexCharts diff --git a/src/ShellUI.Core/Models/ComponentMetadata.cs b/src/ShellUI.Core/Models/ComponentMetadata.cs index 32f7ad5..560a207 100644 --- a/src/ShellUI.Core/Models/ComponentMetadata.cs +++ b/src/ShellUI.Core/Models/ComponentMetadata.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Reflection; namespace ShellUI.Core.Models; @@ -24,11 +25,9 @@ public class ComponentMetadata private static string GetCurrentVersion() { - // For templates, return the version from Directory.Build.props - // This will be resolved at runtime when templates are used + // Try to read version from Directory.Build.props at runtime try { - // Try to read from Directory.Build.props var currentDir = AppDomain.CurrentDomain.BaseDirectory; var dir = new DirectoryInfo(currentDir); @@ -58,7 +57,13 @@ private static string GetCurrentVersion() // Ignore errors } - return "0.1.0"; // fallback + // Fallback: read from assembly version (set at build time by Directory.Build.props) + var assembly = typeof(ComponentMetadata).Assembly; + var assemblyVersion = assembly.GetCustomAttribute(); + if (assemblyVersion != null) + return assemblyVersion.InformationalVersion.Split('+')[0]; // strip build metadata + + return "0.2.0"; } } diff --git a/src/ShellUI.Core/Models/ShellUIConfig.cs b/src/ShellUI.Core/Models/ShellUIConfig.cs index 4ea34de..3c5cfea 100644 --- a/src/ShellUI.Core/Models/ShellUIConfig.cs +++ b/src/ShellUI.Core/Models/ShellUIConfig.cs @@ -13,7 +13,7 @@ public class ShellUIConfig public class TailwindConfig { public bool Enabled { get; set; } = true; - public string Version { get; set; } = "4.1.17"; + public string Version { get; set; } = "4.1.18"; public string Method { get; set; } = "standalone"; // "standalone" or "npm" public string ConfigPath { get; set; } = "tailwind.config.js"; public string CssPath { get; set; } = "wwwroot/app.css"; diff --git a/src/ShellUI.Templates/ComponentRegistry.cs b/src/ShellUI.Templates/ComponentRegistry.cs index c299078..3a543d2 100644 --- a/src/ShellUI.Templates/ComponentRegistry.cs +++ b/src/ShellUI.Templates/ComponentRegistry.cs @@ -94,7 +94,15 @@ public static class ComponentRegistry { "carousel-previous", CarouselPreviousTemplate.Metadata }, { "carousel-next", CarouselNextTemplate.Metadata }, { "carousel-dots", CarouselDotsTemplate.Metadata }, - { "file-upload", FileUploadTemplate.Metadata } + { "file-upload", FileUploadTemplate.Metadata }, + { "chart-variants", ChartVariantsTemplate.Metadata }, + { "chart", ChartTemplate.Metadata }, + { "bar-chart", BarChartTemplate.Metadata }, + { "line-chart", LineChartTemplate.Metadata }, + { "pie-chart", PieChartTemplate.Metadata }, + { "area-chart", AreaChartTemplate.Metadata }, + { "multi-series-chart", MultiSeriesChartTemplate.Metadata }, + { "chart-series", ChartSeriesTemplate.Metadata } }; public static string? GetComponentContent(string componentName) @@ -189,6 +197,14 @@ public static class ComponentRegistry "carousel-next" => CarouselNextTemplate.Content, "carousel-dots" => CarouselDotsTemplate.Content, "file-upload" => FileUploadTemplate.Content, + "chart-variants" => ChartVariantsTemplate.Content, + "chart" => ChartTemplate.Content, + "bar-chart" => BarChartTemplate.Content, + "line-chart" => LineChartTemplate.Content, + "pie-chart" => PieChartTemplate.Content, + "area-chart" => AreaChartTemplate.Content, + "multi-series-chart" => MultiSeriesChartTemplate.Content, + "chart-series" => ChartSeriesTemplate.Content, _ => null }; } diff --git a/src/ShellUI.Templates/Templates/AreaChartTemplate.cs b/src/ShellUI.Templates/Templates/AreaChartTemplate.cs new file mode 100644 index 0000000..eb88e15 --- /dev/null +++ b/src/ShellUI.Templates/Templates/AreaChartTemplate.cs @@ -0,0 +1,46 @@ +using ShellUI.Core.Models; + +namespace ShellUI.Templates.Templates; + +public class AreaChartTemplate +{ + public static ComponentMetadata Metadata => new() + { + Name = "area-chart", + DisplayName = "Area Chart", + Description = "Area chart component using ApexCharts with ShellUI theming", + Category = ComponentCategory.DataDisplay, + FilePath = "AreaChart.razor", + Dependencies = new List { "chart" }, + Variants = new List { "default", "colorful", "monochrome" }, + Tags = new List { "chart", "area", "data", "visualization", "apexcharts" } + }; + + public static string Content => @"@namespace YourProjectNamespace.Components.UI +@using ApexCharts +@inherits Chart +@typeparam TItem where TItem : class + +
+ + + +
+ +@code { + [Parameter] public IEnumerable? Data { get; set; } + [Parameter] public string Name { get; set; } = ""Data""; + [Parameter] public Func? XValue { get; set; } + [Parameter] public Func? YValue { get; set; } +} +"; +} \ No newline at end of file diff --git a/src/ShellUI.Templates/Templates/BarChartTemplate.cs b/src/ShellUI.Templates/Templates/BarChartTemplate.cs new file mode 100644 index 0000000..2344541 --- /dev/null +++ b/src/ShellUI.Templates/Templates/BarChartTemplate.cs @@ -0,0 +1,46 @@ +using ShellUI.Core.Models; + +namespace ShellUI.Templates.Templates; + +public class BarChartTemplate +{ + public static ComponentMetadata Metadata => new() + { + Name = "bar-chart", + DisplayName = "Bar Chart", + Description = "Bar chart component using ApexCharts with ShellUI theming", + Category = ComponentCategory.DataDisplay, + FilePath = "BarChart.razor", + Dependencies = new List { "chart" }, + Variants = new List { "default", "colorful", "monochrome" }, + Tags = new List { "chart", "bar", "data", "visualization", "apexcharts" } + }; + + public static string Content => @"@namespace YourProjectNamespace.Components.UI +@using ApexCharts +@inherits Chart +@typeparam TItem where TItem : class + +
+ + + +
+ +@code { + [Parameter] public IEnumerable? Data { get; set; } + [Parameter] public string Name { get; set; } = ""Data""; + [Parameter] public Func? XValue { get; set; } + [Parameter] public Func? YValue { get; set; } +} +"; +} \ No newline at end of file diff --git a/src/ShellUI.Templates/Templates/ChartSeriesTemplate.cs b/src/ShellUI.Templates/Templates/ChartSeriesTemplate.cs new file mode 100644 index 0000000..674dbe8 --- /dev/null +++ b/src/ShellUI.Templates/Templates/ChartSeriesTemplate.cs @@ -0,0 +1,38 @@ +using ShellUI.Core.Models; + +namespace ShellUI.Templates.Templates; + +public class ChartSeriesTemplate +{ + public static ComponentMetadata Metadata => new() + { + Name = "chart-series", + DisplayName = "Chart Series", + Description = "Individual chart series component for use in multi-series charts", + Category = ComponentCategory.DataDisplay, + FilePath = "ChartSeries.razor", + Dependencies = new List(), + Variants = new List { "line", "bar", "area", "pie" }, + Tags = new List { "chart", "series", "data", "visualization", "apexcharts" } + }; + + public static string Content => @"@namespace YourProjectNamespace.Components.UI +@using ApexCharts +@typeparam TItem where TItem : class + + + +@code { + [Parameter] public IEnumerable? Data { get; set; } + [Parameter] public string Name { get; set; } = ""Series""; + [Parameter] public SeriesType SeriesType { get; set; } = SeriesType.Line; + [Parameter] public Func? XValue { get; set; } + [Parameter] public Func? YValue { get; set; } +} +"; +} \ No newline at end of file diff --git a/src/ShellUI.Templates/Templates/ChartTemplate.cs b/src/ShellUI.Templates/Templates/ChartTemplate.cs new file mode 100644 index 0000000..49c9751 --- /dev/null +++ b/src/ShellUI.Templates/Templates/ChartTemplate.cs @@ -0,0 +1,79 @@ +using ShellUI.Core.Models; + +namespace ShellUI.Templates.Templates; + +public class ChartTemplate +{ + public static ComponentMetadata Metadata => new() + { + Name = "chart", + DisplayName = "Chart", + Description = "Base chart component with ShellUI theming and ApexCharts integration", + Category = ComponentCategory.DataDisplay, + FilePath = "Chart.razor", + Dependencies = new List { "chart-variants" }, + Variants = new List { "default", "colorful", "monochrome" }, + Tags = new List { "chart", "data", "visualization", "apexcharts" } + }; + + public static string Content => @"@namespace YourProjectNamespace.Components.UI +@using ApexCharts +@using YourProjectNamespace.Components.UI.Variants +@typeparam TItem where TItem : class + +
+ + @ChildContent + +
+ +@code { + [Parameter] public ChartTheme Theme { get; set; } = ChartTheme.Default; + [Parameter] public string? Title { get; set; } + [Parameter] public string? Subtitle { get; set; } + [Parameter] public string? Class { get; set; } + [Parameter] public string? Height { get; set; } = ""400px""; + [Parameter] public string? Width { get; set; } = ""100%""; + [Parameter] public RenderFragment? ChildContent { get; set; } + + private ApexCharts.ApexChartOptions? _chartOptions; + + protected ApexCharts.ApexChartOptions ChartOptions + { + get + { + if (_chartOptions == null) + { + _chartOptions = ChartVariants.GetOptions(Theme); + + // Apply title if provided + if (!string.IsNullOrEmpty(Title)) + { + _chartOptions.Title = _chartOptions.Title ?? new ApexCharts.Title(); + _chartOptions.Title.Text = Title; + } + + // Apply subtitle if provided + if (!string.IsNullOrEmpty(Subtitle)) + { + _chartOptions.Subtitle = _chartOptions.Subtitle ?? new ApexCharts.Subtitle(); + _chartOptions.Subtitle.Text = Subtitle; + } + } + return _chartOptions; + } + set => _chartOptions = value; + } + + protected string ComputedClass => Shell.Cn( + ""border border-border bg-card text-card-foreground overflow-hidden"", + ""[border-radius:var(--radius)] [box-shadow:var(--shadow)]"", + Class + ); +} +"; +} \ No newline at end of file diff --git a/src/ShellUI.Templates/Templates/ChartVariantsTemplate.cs b/src/ShellUI.Templates/Templates/ChartVariantsTemplate.cs new file mode 100644 index 0000000..fcc9c3e --- /dev/null +++ b/src/ShellUI.Templates/Templates/ChartVariantsTemplate.cs @@ -0,0 +1,236 @@ +using ShellUI.Core.Models; + +namespace ShellUI.Templates.Templates; + +public class ChartVariantsTemplate +{ + public static ComponentMetadata Metadata => new() + { + Name = "chart-variants", + DisplayName = "Chart Variants", + Description = "Chart theming and styling utilities for ShellUI charts", + Category = ComponentCategory.DataDisplay, + FilePath = "ChartVariants.cs", + Dependencies = new List(), + Variants = new List { "default", "colorful", "monochrome" }, + Tags = new List { "chart", "theme", "styling", "data", "visualization" } + }; + + public static string Content => @"using System.Collections.Generic; + +namespace YourProjectNamespace.Components.UI.Variants; + +/* Chart color themes */ +public enum ChartTheme +{ + Default, // Balanced color palette suitable for light and dark modes + Colorful, // Vibrant colors for emphasis + Monochrome // Grayscale for minimal aesthetic +} + +/* ApexCharts configuration optimized for ShellUI theme system. + Note: ApexCharts doesn't support CSS variables in color arrays, so we use literal colors. + Text and borders use CSS variables (styled via CSS) for automatic dark mode support. */ +public static class ChartVariants +{ + // Creates chart options with theme-aware styling + public static ApexCharts.ApexChartOptions GetOptions(ChartTheme theme = ChartTheme.Default) where TItem : class + { + return new ApexCharts.ApexChartOptions + { + Chart = new ApexCharts.Chart + { + Background = ""transparent"", + FontFamily = ""inherit"", // Inherits from CSS + Toolbar = new ApexCharts.Toolbar + { + Show = true, + Tools = new ApexCharts.Tools + { + Download = true, + Selection = true, + Zoom = true, + Zoomin = true, + Zoomout = true, + Pan = true, + Reset = true + } + } + }, + + Theme = new ApexCharts.Theme(), + Colors = GetThemeColors(theme), + + Stroke = new ApexCharts.Stroke + { + Width = 2, + Curve = ApexCharts.Curve.Smooth, + LineCap = ApexCharts.LineCap.Round + }, + + Fill = new ApexCharts.Fill { Opacity = 0.85 }, + + DataLabels = new ApexCharts.DataLabels + { + Enabled = false + }, + + Grid = new ApexCharts.Grid + { + StrokeDashArray = 3, + Position = ApexCharts.GridPosition.Back + }, + + Xaxis = new ApexCharts.XAxis + { + Type = ApexCharts.XAxisType.Category, // Use category for proper label display + Labels = new ApexCharts.XAxisLabels + { + Style = new ApexCharts.AxisLabelStyle + { + FontSize = ""12px"" + } + }, + AxisBorder = new ApexCharts.AxisBorder + { + Show = true + }, + AxisTicks = new ApexCharts.AxisTicks + { + Show = true + } + }, + + Yaxis = new List + { + new ApexCharts.YAxis + { + Labels = new ApexCharts.YAxisLabels + { + Style = new ApexCharts.AxisLabelStyle + { + FontSize = ""12px"" + } + } + } + }, + + Legend = new ApexCharts.Legend + { + Show = true, + Position = ApexCharts.LegendPosition.Bottom, + FontSize = ""13px"", + Markers = new ApexCharts.LegendMarkers + { + Width = 10, + Height = 10, + Radius = 2 + } + }, + + Tooltip = new ApexCharts.Tooltip + { + Enabled = true, + Shared = true, + Intersect = false, + Style = new ApexCharts.TooltipStyle + { + FontSize = ""12px"" + }, + Custom = @""function({ series, seriesIndex, dataPointIndex, w }) { + const xLabel = w.globals.labels[dataPointIndex] || ''; + let html = '
'; + + if (xLabel) { + html += '
' + xLabel + '
'; + } + + w.globals.initialSeries.forEach((s, idx) => { + const color = w.config.colors[idx]; + const name = s.name || ''; + const value = series[idx] && series[idx][dataPointIndex] !== undefined + ? series[idx][dataPointIndex] + : '-'; + + html += '
' + + '' + + '' + name + ':' + + '' + value + '' + + '
'; + }); + + html += '
'; + return html; + }"" + }, + + Title = new ApexCharts.Title + { + Style = new ApexCharts.TitleStyle + { + FontSize = ""16px"", + FontWeight = ""600"" + } + }, + + Subtitle = new ApexCharts.Subtitle + { + Style = new ApexCharts.SubtitleStyle + { + FontSize = ""14px"" + } + }, + + PlotOptions = new ApexCharts.PlotOptions + { + Bar = new ApexCharts.PlotOptionsBar + { + BorderRadius = 4, + ColumnWidth = ""60%"" + }, + Pie = new ApexCharts.PlotOptionsPie + { + ExpandOnClick = true + } + } + }; + } + + /* Returns color palette based on selected theme. + Colors are literal values because ApexCharts doesn't support CSS variables in color arrays. */ + private static List GetThemeColors(ChartTheme theme) + { + return theme switch + { + ChartTheme.Default => new List + { + ""#2563eb"", // blue-600 + ""#dc2626"", // red-600 + ""#16a34a"", // green-600 + ""#ca8a04"", // yellow-600 + ""#9333ea"" // purple-600 + }, + ChartTheme.Colorful => new List + { + ""#3b82f6"", // blue-500 + ""#ef4444"", // red-500 + ""#10b981"", // green-500 + ""#f59e0b"", // yellow-500 + ""#8b5cf6"", // purple-500 + ""#ec4899"", // pink-500 + ""#06b6d4"" // cyan-500 + }, + ChartTheme.Monochrome => new List + { + ""#64748b"", // slate-500 - works in both light/dark + ""#94a3b8"", // slate-400 + ""#cbd5e1"", // slate-300 + ""#475569"", // slate-600 + ""#334155"" // slate-700 + }, + _ => GetThemeColors(ChartTheme.Default) + }; + } +} +"; +} diff --git a/src/ShellUI.Templates/Templates/LineChartTemplate.cs b/src/ShellUI.Templates/Templates/LineChartTemplate.cs new file mode 100644 index 0000000..01237ec --- /dev/null +++ b/src/ShellUI.Templates/Templates/LineChartTemplate.cs @@ -0,0 +1,46 @@ +using ShellUI.Core.Models; + +namespace ShellUI.Templates.Templates; + +public class LineChartTemplate +{ + public static ComponentMetadata Metadata => new() + { + Name = "line-chart", + DisplayName = "Line Chart", + Description = "Line chart component using ApexCharts with ShellUI theming", + Category = ComponentCategory.DataDisplay, + FilePath = "LineChart.razor", + Dependencies = new List { "chart" }, + Variants = new List { "default", "colorful", "monochrome" }, + Tags = new List { "chart", "line", "data", "visualization", "apexcharts" } + }; + + public static string Content => @"@namespace YourProjectNamespace.Components.UI +@using ApexCharts +@inherits Chart +@typeparam TItem where TItem : class + +
+ + + +
+ +@code { + [Parameter] public IEnumerable? Data { get; set; } + [Parameter] public string Name { get; set; } = ""Data""; + [Parameter] public Func? XValue { get; set; } + [Parameter] public Func? YValue { get; set; } +} +"; +} \ No newline at end of file diff --git a/src/ShellUI.Templates/Templates/MultiSeriesChartTemplate.cs b/src/ShellUI.Templates/Templates/MultiSeriesChartTemplate.cs new file mode 100644 index 0000000..9fb9ac0 --- /dev/null +++ b/src/ShellUI.Templates/Templates/MultiSeriesChartTemplate.cs @@ -0,0 +1,36 @@ +using ShellUI.Core.Models; + +namespace ShellUI.Templates.Templates; + +public class MultiSeriesChartTemplate +{ + public static ComponentMetadata Metadata => new() + { + Name = "multi-series-chart", + DisplayName = "Multi Series Chart", + Description = "Multi-series chart component for combining multiple data series", + Category = ComponentCategory.DataDisplay, + FilePath = "MultiSeriesChart.razor", + Dependencies = new List { "chart", "chart-series" }, + Variants = new List { "default", "colorful", "monochrome" }, + Tags = new List { "chart", "multi-series", "data", "visualization", "apexcharts" } + }; + + public static string Content => @"@namespace YourProjectNamespace.Components.UI +@using ApexCharts +@inherits Chart +@typeparam TItem where TItem : class + + + @ChildContent + + +@code { + // ChildContent is inherited from Chart +} +"; +} \ No newline at end of file diff --git a/src/ShellUI.Templates/Templates/PieChartTemplate.cs b/src/ShellUI.Templates/Templates/PieChartTemplate.cs new file mode 100644 index 0000000..ec6270a --- /dev/null +++ b/src/ShellUI.Templates/Templates/PieChartTemplate.cs @@ -0,0 +1,79 @@ +using ShellUI.Core.Models; + +namespace ShellUI.Templates.Templates; + +public class PieChartTemplate +{ + public static ComponentMetadata Metadata => new() + { + Name = "pie-chart", + DisplayName = "Pie Chart", + Description = "Pie chart component using ApexCharts with ShellUI theming", + Category = ComponentCategory.DataDisplay, + FilePath = "PieChart.razor", + Dependencies = new List { "chart" }, + Variants = new List { "default", "colorful", "monochrome" }, + Tags = new List { "chart", "pie", "data", "visualization", "apexcharts" } + }; + + public static string Content => @"@namespace YourProjectNamespace.Components.UI +@using ApexCharts +@inherits Chart +@typeparam TItem where TItem : class + +
+ + + +
+ +@code { + [Parameter] public IEnumerable? Data { get; set; } + [Parameter] public string Name { get; set; } = ""Data""; + [Parameter] public Func? XValue { get; set; } + [Parameter] public Func? YValue { get; set; } + + protected override void OnParametersSet() + { + base.OnParametersSet(); + + if (ChartOptions.Chart != null) + { + ChartOptions.Chart.Type = ApexCharts.ChartType.Pie; + } + + /* Custom tooltip for pie chart */ + ChartOptions.Tooltip = new ApexCharts.Tooltip + { + Enabled = true, + Style = new ApexCharts.TooltipStyle + { + FontSize = ""12px"" + }, + Custom = @""function({ series, seriesIndex, dataPointIndex, w }) { + const label = w.globals.labels[seriesIndex]; + const value = series[seriesIndex]; + const color = w.config.colors[seriesIndex]; + + return '
' + + '
' + + '' + + '' + label + ':' + + '' + value + '' + + '
' + + '
'; + }"" + }; + } +} +"; +} diff --git a/src/ShellUI.Templates/VersionHelper.cs b/src/ShellUI.Templates/VersionHelper.cs index d648f79..19a0e10 100644 --- a/src/ShellUI.Templates/VersionHelper.cs +++ b/src/ShellUI.Templates/VersionHelper.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Reflection; using System.Text.RegularExpressions; namespace ShellUI.Templates; @@ -17,35 +18,42 @@ public static string GetCurrentVersion() { // Look for Directory.Build.props in the solution root var solutionRoot = FindSolutionRoot(); - if (solutionRoot == null) - return "0.1.0"; // fallback - - var propsFile = Path.Combine(solutionRoot, "Directory.Build.props"); - if (!File.Exists(propsFile)) - return "0.1.0"; // fallback - - var content = File.ReadAllText(propsFile); - - // Extract version from tag - var match = Regex.Match(content, @"([^<]+)"); - if (match.Success) + if (solutionRoot != null) { - var version = match.Groups[1].Value.Trim(); - var suffixMatch = Regex.Match(content, @"([^<]*)"); - if (suffixMatch.Success && !string.IsNullOrEmpty(suffixMatch.Groups[1].Value.Trim())) + var propsFile = Path.Combine(solutionRoot, "Directory.Build.props"); + if (File.Exists(propsFile)) { - version += "-" + suffixMatch.Groups[1].Value.Trim(); + var content = File.ReadAllText(propsFile); + var match = Regex.Match(content, @"([^<]+)"); + if (match.Success) + { + var version = match.Groups[1].Value.Trim(); + var suffixMatch = Regex.Match(content, @"([^<]*)"); + if (suffixMatch.Success && !string.IsNullOrEmpty(suffixMatch.Groups[1].Value.Trim())) + { + version += "-" + suffixMatch.Groups[1].Value.Trim(); + } + _cachedVersion = version; + return version; + } } - _cachedVersion = version; - return version; } } catch { - // Ignore errors and return fallback + // Ignore errors + } + + // Fallback: read from assembly version (set at build time by Directory.Build.props) + var assembly = typeof(VersionHelper).Assembly; + var assemblyVersion = assembly.GetCustomAttribute(); + if (assemblyVersion != null) + { + _cachedVersion = assemblyVersion.InformationalVersion.Split('+')[0]; + return _cachedVersion; } - return "0.1.0"; // fallback version + return "0.2.0"; } private static string? FindSolutionRoot()