diff --git a/PrimeBlazor/src/Components/Knob/PrimeKnob.razor b/PrimeBlazor/src/Components/Knob/PrimeKnob.razor
new file mode 100644
index 0000000..b0aed90
--- /dev/null
+++ b/PrimeBlazor/src/Components/Knob/PrimeKnob.razor
@@ -0,0 +1,216 @@
+@namespace PrimeBlazor
+@using System.Globalization
+
+
+
+
+
+@code {
+
+ private double radius = 40;
+ private double midX = 50;
+ private double midY = 50;
+ private double minRadians = 4 * Math.PI / 3;
+ private double maxRadians = -Math.PI / 3;
+ private bool mouseMoveActive = false;
+ private bool touchMoveActive = false;
+
+ [Parameter]
+ public int Value { get; set; }
+
+ [Parameter]
+ public int Size { get; set; } = 100;
+
+ [Parameter]
+ public bool Disabled { get; set; } = false;
+
+ [Parameter]
+ public bool Readonly { get; set; } = false;
+
+ [Parameter]
+ public int Step { get; set; } = 1;
+
+ [Parameter]
+ public int Min { get; set; } = 0;
+
+ [Parameter]
+ public int Max { get; set; } = 100;
+
+ [Parameter]
+ public string ValueColor { get; set; } = "var(--primary-color, Black)";
+
+ [Parameter]
+ public string RangeColor { get; set; } = "var(--surface-d, LightGray)";
+
+ [Parameter]
+ public string TextColor { get; set; } = "var(--text-color-secondary, Black)";
+
+ [Parameter]
+ public int StrokeWidth { get; set; } = 14;
+
+ [Parameter]
+ public bool ShowValue { get; set; } = true;
+
+ [Parameter]
+ public string ValueTemplate { get; set; } = "{value}";
+
+ public string ContainerClass
+ {
+ get => "p-knob p-component" + (Disabled ? " p-disabled" : "");
+
+
+ }
+
+ public string RangePath
+ {
+ get => $"M {MinX.ToString(CultureInfo.InvariantCulture)} {MinY.ToString(CultureInfo.InvariantCulture)} A {radius.ToString(CultureInfo.InvariantCulture)} {radius.ToString(CultureInfo.InvariantCulture)} 0 1 1 {MaxX.ToString(CultureInfo.InvariantCulture)} {MaxY.ToString(CultureInfo.InvariantCulture)}";
+ }
+
+ public string ValuePath
+ {
+ get => $"M {ZeroX.ToString(CultureInfo.InvariantCulture)} {ZeroY.ToString(CultureInfo.InvariantCulture)} A {radius.ToString(CultureInfo.InvariantCulture)} {radius.ToString(CultureInfo.InvariantCulture)} 0 {LargeArc} {Sweep} {ValueX.ToString(CultureInfo.InvariantCulture)} {ValueY.ToString(CultureInfo.InvariantCulture)}";
+ }
+
+ public double ZeroRadians
+ {
+ get
+ {
+ if (Min > 0 && Max > 0)
+ return MapRange(Min, Min, Max, minRadians, maxRadians);
+ else
+ return MapRange(0, Min, Max, minRadians, maxRadians);
+ }
+ }
+ public double ValueRadians
+ {
+ get
+ {
+ return MapRange(Value, Min, Max, minRadians, maxRadians);
+ }
+ }
+ public double MinX
+ {
+ get => midX + Math.Cos(minRadians) * radius;
+ }
+ public double MinY
+ {
+ get => midY - Math.Sin(minRadians) * radius;
+ }
+
+ public double MaxX
+ {
+ get => midX + Math.Cos(maxRadians) * radius;
+ }
+ public double MaxY
+ {
+ get => midY - Math.Sin(maxRadians) * radius;
+ }
+
+ public double ZeroX
+ {
+ get => midX + Math.Cos(ZeroRadians) * radius;
+ }
+ public double ZeroY
+ {
+ get => midY - Math.Sin(ZeroRadians) * radius;
+ }
+ public double ValueX
+ {
+ get => midX + Math.Cos(ValueRadians) * radius;
+ }
+ public double ValueY
+ {
+ get => midY - Math.Sin(ValueRadians) * radius;
+ }
+ public int LargeArc
+ {
+ get => Math.Abs(ZeroRadians - ValueRadians) < Math.PI ? 0 : 1;
+ }
+ public int Sweep
+ {
+ get => ValueRadians > ZeroRadians ? 0 : 1;
+ }
+ public string ValueToDisplay
+ {
+ get => ValueTemplate.Replace("{value}", Value.ToString());
+ }
+ private void UpdateValue(double offsetX, double offsetY)
+ {
+ var dx = offsetX - Size / 2;
+ var dy = Size / 2 - offsetY;
+ var angle = Math.Atan2(dy, dx);
+ var start = -Math.PI / 2 - Math.PI / 6;
+
+ UpdateModel(angle, start);
+ }
+
+ private void UpdateModel(double angle, double start)
+ {
+ double mappedValue;
+ if (angle > maxRadians)
+ mappedValue = MapRange(angle, minRadians, maxRadians, Min, Max);
+ else if (angle < start)
+ mappedValue = MapRange(angle + 2 * Math.PI, minRadians, maxRadians, Min, Max);
+ else
+ return;
+ double newValue = Math.Round((mappedValue - Min) / Step) * Step + Min;
+
+ Value = (int)newValue;
+
+ }
+
+ private double MapRange(double x, double inMin, double inMax, double outMin, double outMax)
+ {
+ return (x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
+ }
+
+ private void OnClick(MouseEventArgs e)
+ {
+ if (!Disabled && !Readonly)
+ {
+ UpdateValue(e.OffsetX, e.OffsetY);
+ }
+
+ }
+
+ public void OnMouseDown(MouseEventArgs e)
+ {
+ if (!Disabled && !Readonly)
+ {
+
+ mouseMoveActive = true;
+ }
+ }
+
+ public void OnMouseUp(MouseEventArgs e)
+ {
+ if (!Disabled && !Readonly)
+ {
+
+ mouseMoveActive = false;
+ }
+ }
+
+
+ private void OnMouseMove(MouseEventArgs e)
+ {
+ if (!Disabled && !Readonly && mouseMoveActive)
+ {
+ UpdateValue(e.OffsetX, e.OffsetY);
+ }
+ }
+
+
+}
+
+
diff --git a/PrimeBlazor/src/Components/Knob/PrimeKnob.razor.css b/PrimeBlazor/src/Components/Knob/PrimeKnob.razor.css
new file mode 100644
index 0000000..8672e59
--- /dev/null
+++ b/PrimeBlazor/src/Components/Knob/PrimeKnob.razor.css
@@ -0,0 +1,19 @@
+
+@keyframes dash-frame {
+ 100% {
+ stroke-dashoffset: 0;
+ }
+}
+.p-knob-range {
+ fill: none;
+ transition: stroke .1s ease-in;
+}
+.p-knob-value {
+ animation-name: dash-frame;
+ animation-fill-mode: forwards;
+ fill: none;
+}
+.p-knob-text {
+ font-size: 1.3rem;
+ text-align: center;
+}