{
  "name": "Slider",
  "category": "Forms",
  "subcategory": "Specialized",
  "description": "Range slider with single and dual thumb modes.",
  "thumbnail": "/preview-cards/slider.png",
  "hasDocsPage": true,
  "nugetPackage": "Lumeo",
  "files": [
    "UI/Slider/Slider.razor"
  ],
  "dependencies": [],
  "packageDependencies": [],
  "cssVars": [
    "--color-destructive",
    "--color-muted",
    "--color-muted-foreground",
    "--color-popover",
    "--color-popover-foreground",
    "--color-primary",
    "--radius"
  ],
  "api": {
    "name": "Slider",
    "category": "Forms",
    "subcategory": "Specialized",
    "description": "Range slider with single and dual thumb modes.",
    "nugetPackage": "Lumeo",
    "files": [
      "UI/Slider/Slider.razor"
    ],
    "namespace": "Lumeo",
    "inheritsFrom": null,
    "implements": [],
    "parameters": [
      {
        "name": "FormField",
        "type": "FormField.FormFieldContext?",
        "default": null,
        "description": null,
        "isCascading": true,
        "captureUnmatched": false
      },
      {
        "name": "Required",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Invalid",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ErrorText",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "HelperText",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Label",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Name",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Value",
        "type": "double",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ValueChanged",
        "type": "EventCallback<double>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "IsRange",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ValueEnd",
        "type": "double",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ValueEndChanged",
        "type": "EventCallback<double>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Min",
        "type": "double",
        "default": "0",
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Max",
        "type": "double",
        "default": "100",
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Step",
        "type": "double",
        "default": "1",
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Disabled",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Orientation",
        "type": "string",
        "default": "\"Horizontal\"",
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "AriaLabel",
        "type": "string?",
        "default": null,
        "description": "Accessible name exposed via aria-label on the underlying range input. Required for WCAG compliance when the slider is not visually labelled by adjacent text connected via for/htmlFor. For range sliders (IsRange=true) the end handle's aria-label is automatically suffixed with \"(end)\".",
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ShowTooltip",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Marks",
        "type": "List<double>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ShowTicks",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "FormatTooltip",
        "type": "Func<double, string>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Culture",
        "type": "System.Globalization.CultureInfo?",
        "default": null,
        "description": "Culture used for the default tooltip/mark value formatting. When null (default) falls back to CurrentCulture. Ignored when FormatTooltip is supplied.",
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Class",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "AdditionalAttributes",
        "type": "Dictionary<string, object>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": true
      }
    ],
    "events": [
      {
        "name": "ValueChanged",
        "type": "EventCallback<double>",
        "description": null
      },
      {
        "name": "ValueEndChanged",
        "type": "EventCallback<double>",
        "description": null
      }
    ],
    "enums": [],
    "records": [],
    "cssVars": [
      "--color-destructive",
      "--color-muted",
      "--color-muted-foreground",
      "--color-popover",
      "--color-popover-foreground",
      "--color-primary",
      "--radius"
    ],
    "examples": [
      {
        "title": "Default",
        "code": "<Slider @bind-Value=\"_value\" />\n<Lumeo.Text Size=\"sm\" Color=\"muted\">Value: @_value</Lumeo.Text>\n\n@code {\n    private double _value = 50;\n}"
      },
      {
        "title": "Custom Range",
        "code": "<Slider @bind-Value=\"_value\" Min=\"0\" Max=\"1000\" Step=\"50\" />\n\n@code {\n    private double _value = 500;\n}"
      },
      {
        "title": "Step Values",
        "code": "<Slider @bind-Value=\"_value\" Min=\"0\" Max=\"10\" Step=\"1\" />\n\n@code {\n    private double _value = 5;\n}"
      },
      {
        "title": "Disabled",
        "code": "<Slider Value=\"40\" Disabled=\"true\" />"
      }
    ],
    "subComponents": {},
    "parseFailed": false,
    "parseError": null
  },
  "$schema": "https://lumeo.nativ.sh/registry-component-schema.json",
  "key": "slider",
  "docsUrl": "https://lumeo.nativ.sh/components/slider",
  "sourceUrl": [
    {
      "file": "UI/Slider/Slider.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Slider/Slider.razor"
    }
  ],
  "source": [
    {
      "path": "UI/Slider/Slider.razor",
      "content": "@namespace Lumeo\n@inject Lumeo.Services.Localization.ILumeoLocalizer L\n\n@if (Label is not null && !InsideFormField)\n{\n    <label class=\"text-sm font-medium leading-none\">\n        @Label\n        @if (Required)\n        {\n            <span class=\"text-destructive ml-0.5\">*</span>\n        }\n    </label>\n}\n\n@if (Orientation == \"Vertical\")\n{\n    <div class=\"@VerticalCssClass\" @attributes=\"AdditionalAttributes\">\n        <div class=\"relative flex h-full flex-col items-center\">\n            @if (Marks is not null || ShowTicks)\n            {\n                <div class=\"absolute left-6 top-0 flex h-full flex-col justify-between\">\n                    @foreach (var mark in GetTickValues())\n                    {\n                        var pos = GetPercent(mark);\n                        <span class=\"text-xs text-muted-foreground whitespace-nowrap\" style=\"position: absolute; top: @(pos)%; transform: translateY(-50%);\">@FormatValue(mark)</span>\n                    }\n                </div>\n            }\n            <div class=\"relative h-full w-2 rounded-[var(--radius)] @(EffectiveInvalid ? \"bg-destructive/20\" : \"bg-primary/20\")\">\n                <div class=\"absolute bottom-0 w-full rounded-[var(--radius)] @(EffectiveInvalid ? \"bg-destructive\" : \"bg-primary\")\" style=\"height: @(GetPercent(Value))%\"></div>\n                <input type=\"range\" min=\"@Min\" max=\"@Max\" step=\"@Step\" value=\"@Value\" disabled=\"@Disabled\"\n                       name=\"@Name\"\n                       aria-label=\"@AriaLabel\" aria-valuemin=\"@Min\" aria-valuemax=\"@Max\" aria-valuenow=\"@Value\"\n                       aria-required=\"@Required.ToString().ToLower()\"\n                       aria-invalid=\"@EffectiveInvalid.ToString().ToLower()\"\n                       class=\"@VerticalInputClasses\"\n                       @oninput=\"OnInput\"\n                       @onpointerenter=\"() => _showTooltipThumb = true\"\n                       @onpointerleave=\"() => _showTooltipThumb = false\" />\n            </div>\n            @if (ShowTooltip && _showTooltipThumb)\n            {\n                <div class=\"absolute -left-10 rounded-md bg-popover px-2 py-1 text-xs text-popover-foreground shadow-md\"\n                     style=\"top: @(100 - GetPercent(Value))%; transform: translateY(-50%);\">\n                    @FormatValue(Value)\n                </div>\n            }\n        </div>\n    </div>\n}\nelse if (IsRange)\n{\n    <div class=\"@CssClass\" @attributes=\"AdditionalAttributes\">\n        <div class=\"relative w-full\">\n            <div class=\"absolute inset-0 flex items-center\">\n                <div class=\"h-2 w-full rounded-[var(--radius)] @(EffectiveInvalid ? \"bg-destructive/20\" : \"bg-primary/20\")\"></div>\n            </div>\n            <div class=\"absolute inset-0 flex items-center\" style=\"pointer-events: none;\">\n                <div class=\"h-2 rounded-[var(--radius)] @(EffectiveInvalid ? \"bg-destructive\" : \"bg-primary\")\" style=\"margin-left: @(StartPercent)%; width: @(RangePercent)%;\"></div>\n            </div>\n            <div class=\"relative flex items-center\" style=\"pointer-events: none;\">\n                <input type=\"range\" min=\"@Min\" max=\"@Max\" step=\"@Step\" value=\"@Value\" disabled=\"@Disabled\"\n                       name=\"@Name\"\n                       aria-label=\"@AriaLabel\" aria-valuemin=\"@Min\" aria-valuemax=\"@Max\" aria-valuenow=\"@Value\"\n                       aria-required=\"@Required.ToString().ToLower()\"\n                       aria-invalid=\"@EffectiveInvalid.ToString().ToLower()\"\n                       class=\"@RangeInputClasses\"\n                       style=\"pointer-events: auto;\"\n                       @oninput=\"OnStartInput\" />\n                <input type=\"range\" min=\"@Min\" max=\"@Max\" step=\"@Step\" value=\"@ValueEnd\" disabled=\"@Disabled\"\n                       aria-label=\"@EndAriaLabel\" aria-valuemin=\"@Min\" aria-valuemax=\"@Max\" aria-valuenow=\"@ValueEnd\"\n                       aria-required=\"@Required.ToString().ToLower()\"\n                       aria-invalid=\"@EffectiveInvalid.ToString().ToLower()\"\n                       class=\"@RangeInputClasses\"\n                       style=\"pointer-events: auto; position: absolute; left: 0; width: 100%;\"\n                       @oninput=\"OnEndInput\" />\n            </div>\n            @if (Marks is not null || ShowTicks)\n            {\n                <div class=\"relative mt-2 w-full\">\n                    @foreach (var mark in GetTickValues())\n                    {\n                        var pos = GetPercent(mark);\n                        <div class=\"absolute flex flex-col items-center\" style=\"left: @(pos)%; transform: translateX(-50%);\">\n                            @if (ShowTicks)\n                            {\n                                <div class=\"h-1 w-px bg-muted-foreground/50\"></div>\n                            }\n                            <span class=\"mt-1 text-xs text-muted-foreground\">@FormatValue(mark)</span>\n                        </div>\n                    }\n                </div>\n            }\n        </div>\n    </div>\n}\nelse\n{\n    <div class=\"@CssClass\" @attributes=\"AdditionalAttributes\">\n        <div class=\"relative w-full\">\n            <div class=\"relative\">\n                <input type=\"range\" min=\"@Min\" max=\"@Max\" step=\"@Step\" value=\"@Value\" disabled=\"@Disabled\"\n                       name=\"@Name\"\n                       aria-label=\"@AriaLabel\" aria-valuemin=\"@Min\" aria-valuemax=\"@Max\" aria-valuenow=\"@Value\"\n                       aria-required=\"@Required.ToString().ToLower()\"\n                       aria-invalid=\"@EffectiveInvalid.ToString().ToLower()\"\n                       class=\"@InputClasses\"\n                       @oninput=\"OnInput\"\n                       @onpointerenter=\"() => _showTooltipThumb = true\"\n                       @onpointerleave=\"() => _showTooltipThumb = false\" />\n                @if (ShowTooltip && _showTooltipThumb)\n                {\n                    <div class=\"absolute -top-8 rounded-md bg-popover px-2 py-1 text-xs text-popover-foreground shadow-md\"\n                         style=\"left: @(GetPercent(Value))%; transform: translateX(-50%);\">\n                        @FormatValue(Value)\n                    </div>\n                }\n            </div>\n            @if (Marks is not null || ShowTicks)\n            {\n                <div class=\"relative mt-1 w-full\">\n                    @foreach (var mark in GetTickValues())\n                    {\n                        var pos = GetPercent(mark);\n                        <div class=\"absolute flex flex-col items-center\" style=\"left: @(pos)%; transform: translateX(-50%);\">\n                            @if (ShowTicks)\n                            {\n                                <div class=\"h-1 w-px bg-muted-foreground/50\"></div>\n                            }\n                            <span class=\"mt-1 text-xs text-muted-foreground\">@FormatValue(mark)</span>\n                        </div>\n                    }\n                </div>\n            }\n        </div>\n    </div>\n}\n\n@if (!InsideFormField)\n{\n    @if (EffectiveInvalid && ErrorText is not null)\n    {\n        <p class=\"text-xs text-destructive\">@ErrorText</p>\n    }\n    else if (HelperText is not null)\n    {\n        <p class=\"text-xs text-muted-foreground\">@HelperText</p>\n    }\n}\n\n@code {\n    [CascadingParameter] private FormField.FormFieldContext? FormField { get; set; }\n\n    [Parameter] public bool Required { get; set; }\n    [Parameter] public bool Invalid { get; set; }\n    [Parameter] public string? ErrorText { get; set; }\n    [Parameter] public string? HelperText { get; set; }\n    [Parameter] public string? Label { get; set; }\n    [Parameter] public string? Name { get; set; }\n\n    private bool EffectiveInvalid => Invalid || FormField?.HasError == true;\n    private bool InsideFormField => FormField is not null;\n\n    // Localized \"(end)\" suffix for the range slider's second thumb so screen\n    // readers in non-English locales announce a meaningful label.\n    private string? EndAriaLabel => string.IsNullOrEmpty(AriaLabel) ? null : $\"{AriaLabel} ({L[\"Slider.End\"]})\";\n\n    [Parameter] public double Value { get; set; }\n    [Parameter] public EventCallback<double> ValueChanged { get; set; }\n    [Parameter] public bool IsRange { get; set; }\n    [Parameter] public double ValueEnd { get; set; }\n    [Parameter] public EventCallback<double> ValueEndChanged { get; set; }\n    [Parameter] public double Min { get; set; } = 0;\n    [Parameter] public double Max { get; set; } = 100;\n    [Parameter] public double Step { get; set; } = 1;\n    [Parameter] public bool Disabled { get; set; }\n    [Parameter] public string Orientation { get; set; } = \"Horizontal\";\n    /// <summary>\n    /// Accessible name exposed via <c>aria-label</c> on the underlying range input.\n    /// Required for WCAG compliance when the slider is not visually labelled by\n    /// adjacent text connected via <c>for</c>/<c>htmlFor</c>. For range sliders\n    /// (<see cref=\"IsRange\"/>=true) the end handle's aria-label is automatically\n    /// suffixed with \"(end)\".\n    /// </summary>\n    [Parameter] public string? AriaLabel { get; set; }\n    [Parameter] public bool ShowTooltip { get; set; }\n    [Parameter] public List<double>? Marks { get; set; }\n    [Parameter] public bool ShowTicks { get; set; }\n    [Parameter] public Func<double, string>? FormatTooltip { get; set; }\n\n    /// <summary>\n    /// Culture used for the default tooltip/mark value formatting. When null (default)\n    /// falls back to <see cref=\"System.Globalization.CultureInfo.CurrentCulture\"/>. Ignored\n    /// when <see cref=\"FormatTooltip\"/> is supplied.\n    /// </summary>\n    [Parameter] public System.Globalization.CultureInfo? Culture { get; set; }\n\n    private System.Globalization.CultureInfo EffectiveCulture => Culture ?? System.Globalization.CultureInfo.CurrentCulture;\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private bool _showTooltipThumb;\n\n    private string CssClass => $\"relative flex w-full touch-none select-none items-center {Class}\".Trim();\n    private string VerticalCssClass => $\"relative flex h-full min-h-[200px] touch-none select-none justify-center {Class}\".Trim();\n\n    private string InputClasses => $\"w-full h-2 cursor-pointer appearance-none rounded-[var(--radius)] {(EffectiveInvalid ? \"bg-destructive/20 accent-destructive\" : \"bg-primary/20 accent-primary\")} disabled:cursor-not-allowed disabled:opacity-50\";\n\n    private string VerticalInputClasses => $\"absolute inset-0 h-full w-full cursor-pointer appearance-none bg-transparent {(EffectiveInvalid ? \"accent-destructive\" : \"accent-primary\")} disabled:cursor-not-allowed disabled:opacity-50 [writing-mode:vertical-lr] [direction:rtl]\";\n\n    private const string RangeInputClasses = \"w-full h-2 cursor-pointer appearance-none bg-transparent accent-primary disabled:cursor-not-allowed disabled:opacity-50\";\n\n    private double StartPercent => Max > Min ? ((Math.Min(Value, ValueEnd) - Min) / (Max - Min)) * 100 : 0;\n    private double RangePercent => Max > Min ? ((Math.Abs(ValueEnd - Value)) / (Max - Min)) * 100 : 0;\n\n    private double GetPercent(double value) => Max > Min ? ((value - Min) / (Max - Min)) * 100 : 0;\n\n    private string FormatValue(double value) => FormatTooltip is not null ? FormatTooltip(value) : value.ToString(\"G\", EffectiveCulture);\n\n    private List<double> GetTickValues()\n    {\n        if (Marks is not null) return Marks;\n        return new List<double> { Min, Max };\n    }\n\n    private async Task OnInput(ChangeEventArgs e)\n    {\n        if (double.TryParse(e.Value?.ToString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var newValue))\n        {\n            Value = newValue;\n            await ValueChanged.InvokeAsync(Value);\n        }\n    }\n\n    private async Task OnStartInput(ChangeEventArgs e)\n    {\n        if (double.TryParse(e.Value?.ToString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var newValue))\n        {\n            Value = newValue;\n            await ValueChanged.InvokeAsync(Value);\n        }\n    }\n\n    private async Task OnEndInput(ChangeEventArgs e)\n    {\n        if (double.TryParse(e.Value?.ToString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var newValue))\n        {\n            ValueEnd = newValue;\n            await ValueEndChanged.InvokeAsync(ValueEnd);\n        }\n    }\n}\n"
    }
  ],
  "slots": [],
  "serviceDependencies": [
    {
      "service": "ILumeoLocalizer",
      "namespace": "Lumeo.Services.Localization"
    }
  ],
  "cascadingDependencies": [
    {
      "name": "FormField",
      "type": "FormField.FormFieldContext?",
      "required": false
    }
  ],
  "relatedComponents": [],
  "keyboardInteractions": [],
  "tests": [
    "tests/Lumeo.Tests/Components/Slider/SliderTests.cs"
  ],
  "mdSummary": "# Slider\n\nRange slider with single and dual thumb modes.\n\n**Package:** `Lumeo` · **Category:** Forms/Specialized\n**Docs:** https://lumeo.nativ.sh/components/slider\n\n## Parameters\n| Owner | Name | Type | Default | Description |\n|---|---|---|---|---|\n| Slider | FormField | `FormField.FormFieldContext?` | `` |  |\n| Slider | Required | `bool` | `` |  |\n| Slider | Invalid | `bool` | `` |  |\n| Slider | ErrorText | `string?` | `` |  |\n| Slider | HelperText | `string?` | `` |  |\n| Slider | Label | `string?` | `` |  |\n| Slider | Name | `string?` | `` |  |\n| Slider | Value | `double` | `` |  |\n| Slider | ValueChanged | `EventCallback<double>` | `` |  |\n| Slider | IsRange | `bool` | `` |  |\n| Slider | ValueEnd | `double` | `` |  |\n| Slider | ValueEndChanged | `EventCallback<double>` | `` |  |\n| Slider | Min | `double` | `0` |  |\n| Slider | Max | `double` | `100` |  |\n| Slider | Step | `double` | `1` |  |\n| Slider | Disabled | `bool` | `` |  |\n| Slider | Orientation | `string` | `\"Horizontal\"` |  |\n| Slider | AriaLabel | `string?` | `` | Accessible name exposed via aria-label on the underlying range input. Required for WCAG compliance when the slider is not visually labelled by adjacent text connected via for/htmlFor. For range sliders (IsRange=true) the end handle's aria-label is automatically suffixed with \"(end)\". |\n| Slider | ShowTooltip | `bool` | `` |  |\n| Slider | Marks | `List<double>?` | `` |  |\n| Slider | ShowTicks | `bool` | `` |  |\n| Slider | FormatTooltip | `Func<double, string>?` | `` |  |\n| Slider | Culture | `System.Globalization.CultureInfo?` | `` | Culture used for the default tooltip/mark value formatting. When null (default) falls back to CurrentCulture. Ignored when FormatTooltip is supplied. |\n| Slider | Class | `string?` | `` |  |\n| Slider | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n\n## Slots\n- (none)\n\n## Events\n- `ValueChanged` (`EventCallback<double>`)\n- `ValueEndChanged` (`EventCallback<double>`)\n\n## Sub-components\n- (none)\n\n## Cascading values it expects\n- `FormField` (`FormField.FormFieldContext?`)\n\n## Services it injects\n- `ILumeoLocalizer` (Lumeo.Services.Localization)\n\n## Keyboard\n—\n\n## Example\n```razor\n<Slider />\n```\n\n## CSS variables\n`--color-destructive`, `--color-muted`, `--color-muted-foreground`, `--color-popover`, `--color-popover-foreground`, `--color-primary`, `--radius`\n\n## Files\n- `UI/Slider/Slider.razor`\n"
}