diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss b/src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss index bc4237dd4b..3212d75a40 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.scss @@ -1,3 +1,5 @@ +@import "../../../Bit.BlazorUI/Styles/functions.scss"; + /* ============================================================ BitDataGrid - styles Theming is driven entirely by the Bit BlazorUI theme system @@ -9,12 +11,12 @@ .bit-dtg { --bit-dtg-cell-pad: 8px 10px; - font-family: var(--bit-tpg-font-family); + font-family: $tg-font-family; font-size: 14px; line-height: 1.4; - color: var(--bit-clr-fg-pri); - background: var(--bit-clr-bg-pri); - border-radius: var(--bit-shp-brd-radius); + color: $clr-fg-pri; + background: $clr-bg-pri; + border-radius: $shp-border-radius; display: flex; flex-direction: column; position: relative; @@ -28,7 +30,7 @@ } .bit-dtg.bit-dtg-bordered { - border: 1px solid var(--bit-clr-brd-ter); + border: 1px solid $clr-brd-ter; } /* ---------------------------------------------------------- Toolbar */ @@ -38,7 +40,7 @@ align-items: center; gap: 8px; padding: 8px; - border-bottom: 1px solid var(--bit-clr-brd-ter); + border-bottom: 1px solid $clr-brd-ter; flex-wrap: wrap; } @@ -55,8 +57,8 @@ flex-wrap: wrap; gap: 12px; padding: 10px; - border-bottom: 1px solid var(--bit-clr-brd-ter); - background: var(--bit-clr-bg-sec); + border-bottom: 1px solid $clr-brd-ter; + background: $clr-bg-sec; } .bit-dtg-chooser-item { @@ -87,7 +89,7 @@ display: grid; grid-template-columns: var(--bit-dtg-template); align-items: stretch; - border-bottom: 1px solid var(--bit-clr-brd-ter); + border-bottom: 1px solid $clr-brd-ter; } .bit-dtg-header { @@ -98,11 +100,11 @@ .bit-dtg-header-row, .bit-dtg-filter-row { - background: var(--bit-clr-bg-sec); + background: $clr-bg-sec; } .bit-dtg-filter-row { - border-bottom: 1px solid var(--bit-clr-brd-ter); + border-bottom: 1px solid $clr-brd-ter; } .bit-dtg-hcell { @@ -112,7 +114,7 @@ padding: var(--bit-dtg-cell-pad); font-weight: 600; position: relative; - background: var(--bit-clr-bg-sec); + background: $clr-bg-sec; border-inline-end: 1px solid transparent; user-select: none; overflow: hidden; @@ -125,12 +127,12 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - background: var(--bit-clr-bg-pri); + background: $clr-bg-pri; } .bit-dtg-bordered .bit-dtg-cell, .bit-dtg-bordered .bit-dtg-hcell { - border-inline-end: 1px solid var(--bit-clr-brd-ter); + border-inline-end: 1px solid $clr-brd-ter; } .bit-dtg-center { @@ -145,26 +147,26 @@ /* Striping & hover */ .bit-dtg-striped .bit-dtg-body .bit-dtg-row:nth-child(even) .bit-dtg-cell { - background: var(--bit-clr-bg-sec); + background: $clr-bg-sec; } .bit-dtg-hoverable .bit-dtg-body .bit-dtg-row:hover .bit-dtg-cell { - background: var(--bit-clr-bg-pri-hover); + background: $clr-bg-pri-hover; } /* Selection & editing must win over both striping and hover (in every row position) */ .bit-dtg-row.bit-dtg-selected .bit-dtg-cell, .bit-dtg-striped .bit-dtg-body .bit-dtg-row.bit-dtg-selected:nth-child(even) .bit-dtg-cell, .bit-dtg-hoverable .bit-dtg-body .bit-dtg-row.bit-dtg-selected:hover .bit-dtg-cell { - color: var(--bit-clr-pri-text); - background: var(--bit-clr-pri); + color: $clr-pri-text; + background: $clr-pri; } .bit-dtg-row.bit-dtg-editing .bit-dtg-cell, .bit-dtg-striped .bit-dtg-body .bit-dtg-row.bit-dtg-editing:nth-child(even) .bit-dtg-cell, .bit-dtg-hoverable .bit-dtg-body .bit-dtg-row.bit-dtg-editing:hover .bit-dtg-cell { - color: var(--bit-clr-fg-pri); - background: var(--bit-clr-wrn-light); + color: $clr-fg-pri; + background: $clr-wrn-light; } /* Sticky / frozen columns */ @@ -219,13 +221,13 @@ button.bit-dtg-htext { .bit-dtg-sort-icon { font-size: 10px; - color: var(--bit-clr-pri); + color: $clr-pri; } .bit-dtg-sort-priority { font-size: 9px; - background: var(--bit-clr-pri); - color: var(--bit-clr-pri-text); + background: $clr-pri; + color: $clr-pri-text; border-radius: 8px; padding: 0 4px; line-height: 14px; @@ -248,7 +250,7 @@ button.bit-dtg-htext { } .bit-dtg-resizer:hover { - background: var(--bit-clr-pri); + background: $clr-pri; opacity: .5; } @@ -262,7 +264,7 @@ button.bit-dtg-htext { /* ---------------------------------------------------------- Group rows */ .bit-dtg-group-row { - border-bottom: 1px solid var(--bit-clr-brd-ter); + border-bottom: 1px solid $clr-brd-ter; } .bit-dtg-group-cell { @@ -271,34 +273,34 @@ button.bit-dtg-htext { align-items: center; gap: 10px; padding: var(--bit-dtg-cell-pad); - background: var(--bit-clr-bg-ter); + background: $clr-bg-ter; } .bit-dtg-group-count { - color: var(--bit-clr-fg-sec); + color: $clr-fg-sec; } .bit-dtg-group-agg { - color: var(--bit-clr-fg-sec); + color: $clr-fg-sec; font-size: 12px; } /* Nested groups: subtle indentation shading applied at every level so deeper groups keep the hierarchy cue (the level value drives indentation elsewhere). */ .bit-dtg-group-row .bit-dtg-group-cell { - border-inline-start: 3px solid var(--bit-clr-pri); + border-inline-start: 3px solid $clr-pri; } /* ----------------------------------------------- Grouped column headers */ .bit-dtg-group-header-row { - background: var(--bit-clr-bg-sec); - border-bottom: 1px solid var(--bit-clr-brd-ter); + background: $clr-bg-sec; + border-bottom: 1px solid $clr-brd-ter; } .bit-dtg-group-header { font-weight: 700; - background: var(--bit-clr-bg-sec); - border-inline-end: 1px solid var(--bit-clr-brd-ter); + background: $clr-bg-sec; + border-inline-end: 1px solid $clr-brd-ter; display: flex; align-items: center; justify-content: center; @@ -322,7 +324,7 @@ button.bit-dtg-htext { border: none; padding: 0; cursor: grab; - color: var(--bit-clr-fg-sec); + color: $clr-fg-sec; user-select: none; } @@ -336,12 +338,12 @@ button.bit-dtg-htext { /* ---------------------------------------------------------- Detail rows */ .bit-dtg-detail-row { - border-bottom: 1px solid var(--bit-clr-brd-ter); + border-bottom: 1px solid $clr-brd-ter; } .bit-dtg-detail-content { padding: 12px 16px; - background: var(--bit-clr-bg-sec); + background: $clr-bg-sec; } /* ---------------------------------------------------------- Tree view */ @@ -366,13 +368,13 @@ button.bit-dtg-htext { /* The focus ring is driven solely by real DOM :focus so exactly one cell can ever be outlined (state and DOM focus are kept in sync via @key + FocusAsync). */ .bit-dtg-cell[tabindex]:focus { - outline: 2px solid var(--bit-clr-pri); + outline: 2px solid $clr-pri; outline-offset: -2px; z-index: 1; } .bit-dtg-cell[tabindex]:focus-visible { - outline: 2px solid var(--bit-clr-pri); + outline: 2px solid $clr-pri; outline-offset: -2px; z-index: 1; } @@ -383,7 +385,7 @@ button.bit-dtg-htext { cell stays clearly visible even while selected. */ .bit-dtg-row.bit-dtg-selected .bit-dtg-cell[tabindex]:focus, .bit-dtg-row.bit-dtg-selected .bit-dtg-cell[tabindex]:focus-visible { - outline-color: var(--bit-clr-pri-text); + outline-color: $clr-pri-text; } /* ---------------------------------------------------------- Footer */ @@ -394,9 +396,9 @@ button.bit-dtg-htext { } .bit-dtg-footer-row .bit-dtg-cell { - background: var(--bit-clr-bg-sec); + background: $clr-bg-sec; font-weight: 600; - border-top: 2px solid var(--bit-clr-brd-ter); + border-top: 2px solid $clr-brd-ter; } /* ---------------------------------------------------------- Empty / loading */ @@ -404,14 +406,14 @@ button.bit-dtg-htext { .bit-dtg-loading { padding: 32px; text-align: center; - color: var(--bit-clr-fg-sec); + color: $clr-fg-sec; } .bit-dtg-spinner { display: inline-block; width: 14px; height: 14px; - border: 2px solid var(--bit-clr-fg-sec); + border: 2px solid $clr-fg-sec; border-top-color: transparent; border-radius: 50%; vertical-align: middle; @@ -441,9 +443,9 @@ button.bit-dtg-htext { height: 10px; border-radius: 6px; background: linear-gradient(90deg, - var(--bit-clr-brd-ter) 25%, - var(--bit-clr-bg-sec) 37%, - var(--bit-clr-brd-ter) 63%); + $clr-brd-ter 25%, + $clr-bg-sec 37%, + $clr-brd-ter 63%); background-size: 400% 100%; } @@ -466,7 +468,7 @@ button.bit-dtg-htext { .bit-dtg-infinite-end { padding: 14px; text-align: center; - color: var(--bit-clr-fg-sec); + color: $clr-fg-sec; font-size: 13px; } @@ -475,10 +477,10 @@ button.bit-dtg-htext { .bit-dtg-filter-input { width: 100%; padding: 4px 6px; - border: 1px solid var(--bit-clr-brd-ter); + border: 1px solid $clr-brd-ter; border-radius: 4px; - background: var(--bit-clr-bg-pri); - color: var(--bit-clr-fg-pri); + background: $clr-bg-pri; + color: $clr-fg-pri; font: inherit; } @@ -494,7 +496,7 @@ button.bit-dtg-htext { background: none; appearance: auto; -webkit-appearance: checkbox; - accent-color: var(--bit-clr-pri); + accent-color: $clr-pri; cursor: pointer; } @@ -512,7 +514,7 @@ button.bit-dtg-htext { /* Highlights the row/header cell a touch drag would drop onto. */ .bit-dtg-drop-target { - outline: 2px dashed var(--bit-clr-pri); + outline: 2px dashed $clr-pri; outline-offset: -2px; } @@ -531,8 +533,8 @@ button.bit-dtg-htext { /* Inline-edit validation: flag the invalid editor and show its message underneath. */ .bit-dtg-editor[aria-invalid="true"] { - border-color: var(--bit-clr-err); - outline-color: var(--bit-clr-err); + border-color: $clr-err; + outline-color: $clr-err; } .bit-dtg-editor-error { @@ -540,7 +542,7 @@ button.bit-dtg-htext { margin-top: 2px; font-size: 11px; line-height: 1.2; - color: var(--bit-clr-err); + color: $clr-err; } /* Lays out the operator dropdown and the filter input side by side. */ @@ -556,10 +558,10 @@ button.bit-dtg-htext { max-width: 40%; margin-inline-end: 4px; padding: 4px 2px; - border: 1px solid var(--bit-clr-brd-ter); + border: 1px solid $clr-brd-ter; border-radius: 4px; - background: var(--bit-clr-bg-pri); - color: var(--bit-clr-fg-pri); + background: $clr-bg-pri; + color: $clr-fg-pri; font-size: 11px; font-weight: 400; } @@ -570,10 +572,10 @@ button.bit-dtg-htext { align-items: center; gap: 4px; padding: 5px 10px; - border: 1px solid var(--bit-clr-brd-ter); + border: 1px solid $clr-brd-ter; border-radius: 6px; - background: var(--bit-clr-bg-pri); - color: var(--bit-clr-fg-pri); + background: $clr-bg-pri; + color: $clr-fg-pri; font: inherit; cursor: pointer; text-decoration: none; @@ -581,7 +583,7 @@ button.bit-dtg-htext { } .bit-dtg-btn:hover:not(:disabled) { - background: var(--bit-clr-bg-pri-hover); + background: $clr-bg-pri-hover; } .bit-dtg-btn:disabled { @@ -590,21 +592,21 @@ button.bit-dtg-htext { } .bit-dtg-btn-primary { - background: var(--bit-clr-pri); - border-color: var(--bit-clr-pri); - color: var(--bit-clr-pri-text); + background: $clr-pri; + border-color: $clr-pri; + color: $clr-pri-text; } /* Win over the generic .bit-dtg-btn:hover rule so primary buttons keep their primary treatment on hover */ .bit-dtg-btn-primary:hover:not(:disabled) { - background: var(--bit-clr-pri-hover); - border-color: var(--bit-clr-pri-hover); - color: var(--bit-clr-pri-text); + background: $clr-pri-hover; + border-color: $clr-pri-hover; + color: $clr-pri-text; } .bit-dtg-btn-danger { - color: var(--bit-clr-err); - border-color: var(--bit-clr-err); + color: $clr-err; + border-color: $clr-err; } .bit-dtg-cell-command { @@ -614,7 +616,7 @@ button.bit-dtg-htext { .bit-dtg-icon-btn { background: none; border: none; - color: var(--bit-clr-fg-pri); + color: $clr-fg-pri; cursor: pointer; font-size: 13px; padding: 2px 4px; @@ -622,11 +624,11 @@ button.bit-dtg-htext { } .bit-dtg-icon-btn:hover { - background: var(--bit-clr-bg-pri-hover); + background: $clr-bg-pri-hover; } .bit-dtg-group-btn.bit-dtg-active { - color: var(--bit-clr-pri); + color: $clr-pri; } /* ---------------------------------------------------------- Pager */ @@ -636,7 +638,7 @@ button.bit-dtg-htext { align-items: center; gap: 8px; padding: 8px; - border-top: 1px solid var(--bit-clr-brd-ter); + border-top: 1px solid $clr-brd-ter; flex-wrap: wrap; } @@ -644,7 +646,7 @@ button.bit-dtg-htext { display: flex; align-items: center; gap: 10px; - color: var(--bit-clr-fg-sec); + color: $clr-fg-sec; } .bit-dtg-pager-buttons { @@ -659,9 +661,9 @@ button.bit-dtg-htext { .bit-dtg-page-size { padding: 4px 6px; - border: 1px solid var(--bit-clr-brd-ter); + border: 1px solid $clr-brd-ter; border-radius: 6px; - background: var(--bit-clr-bg-pri); - color: var(--bit-clr-fg-pri); + background: $clr-bg-pri; + color: $clr-fg-pri; font: inherit; } \ No newline at end of file diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor deleted file mode 100644 index 3252ee7b13..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor +++ /dev/null @@ -1,152 +0,0 @@ -@namespace Bit.BlazorUI -@typeparam TGridItem - - - @{ - StartCollectingColumns(); - } - @(Columns ?? ChildContent) - - @{ - FinishCollectingColumns(); - } - - - - - - @_renderColumnHeaders - - - - @if (Virtualize) - { - if (IsLoading && LoadingTemplate is not null) - { - - - - } - else - { - - } - } - else - { - if (IsLoading && LoadingTemplate is not null) - { - - - - } - else - { - @_renderNonVirtualizedRows - } - } - -
- @LoadingTemplate -
- @LoadingTemplate -
-
-
- -@code { - private void RenderNonVirtualizedRows(RenderTreeBuilder __builder) - { - var initialRowIndex = 2; // aria-rowindex is 1-based, plus the first row is the header - var rowIndex = initialRowIndex; - foreach (var item in _currentNonVirtualizedViewItems) - { - RenderRow(__builder, rowIndex++, item); - } - - // When pagination is enabled, by default ensure we render the exact number of expected rows per page, - // even if there aren't enough data items. This avoids the layout jumping on the last page. - // Consider making this optional. - if (Pagination is not null) - { - while (rowIndex++ < initialRowIndex + Pagination.ItemsPerPage) - { - - - - } - } - } - - private void RenderRow(RenderTreeBuilder __builder, int rowIndex, TGridItem item) - { - if (RowTemplate is null) - { - RenderOriginalRow(__builder, rowIndex, item); - } - else - { - var args = new BitQuickGridRowTemplateArgs - { - RowIndex = rowIndex, - RowItem = item, - OriginalRow = (builder) => RenderOriginalRow(builder, rowIndex, item) - }; - __builder.AddContent(0, RowTemplate(args)); - } - } - - private void RenderOriginalRow(RenderTreeBuilder __builder, int rowIndex, TGridItem item) - { - - @foreach (var col in _columns) - { - - @{ - col.CellContent(__builder, item); - } - - } - - } - - private void RenderPlaceholderRow(RenderTreeBuilder __builder, PlaceholderContext placeholderContext) - { - - @foreach (var col in _columns) - { - - @{ - col.RenderPlaceholderContent(__builder, placeholderContext); - } - - } - - } - - private void RenderColumnHeaders(RenderTreeBuilder __builder) - { - foreach (var col in _columns) - { - -
@col.HeaderContent
- - @if (col == _displayOptionsForColumn) - { -
@col.ColumnOptions
- } - - @if (ResizableColumns) - { -
- } - - } - } -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs deleted file mode 100644 index faef31da68..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.razor.cs +++ /dev/null @@ -1,751 +0,0 @@ -// a fork from the Blazor QuickGrid at https://github.com/dotnet/aspnetcore/tree/main/src/Components/QuickGrid - -namespace Bit.BlazorUI; - -/// -/// BitQuickGrid is a robust way to display an information-rich collection of items, and allow people to sort, and filter the content. -/// -/// The type of data represented by each row in the grid. -[CascadingTypeParameter(nameof(TGridItem))] -public partial class BitQuickGrid : IAsyncDisposable -{ - private bool _disposed; - private int _ariaBodyRowCount; - private ElementReference _tableReference; - private Virtualize<(int, TGridItem)>? _virtualizeComponent; - private ICollection _currentNonVirtualizedViewItems = Array.Empty(); - - // IQueryable only exposes synchronous query APIs. IAsyncQueryExecutor is an adapter that lets us invoke any - // async query APIs that might be available. We have built-in support for using EF Core's async query APIs. - private IAsyncQueryExecutor? _asyncQueryExecutor; - - // We cascade the InternalGridContext to descendants, which in turn call it to add themselves to _columns - // This happens on every render so that the column list can be updated dynamically - private InternalGridContext _internalGridContext; - private List> _columns; - private bool _collectingColumns; // Columns might re-render themselves arbitrarily. We only want to capture them at a defined time. - - // Tracking state for options and sorting - private BitQuickGridColumnBase? _displayOptionsForColumn; - private BitQuickGridColumnBase? _sortByColumn; - private bool _sortByAscending; - private bool _checkColumnOptionsPosition; - // Set when column recollection drops the active sort column; triggers a data refresh after render - // so the grid query stays in sync with the (now changed) header sort state. - private bool _queueSortReconciliationRefresh; - // Tracks whether columns have been collected at least once, and the sort column captured at the - // start of a collection pass, so a *new* default sort applied during a later recollection can also - // queue a refresh (the very first collection already loads data via ColumnsFirstCollected). - private bool _columnsCollectedOnce; - private BitQuickGridColumnBase? _sortByColumnBeforeCollect; - // Captures the first default-sort column (and its direction) discovered during the current - // collection pass, so FinishCollectingColumns can adopt it when the previously active sort column - // is no longer present after recollection instead of clearing sorting outright. - private BitQuickGridColumnBase? _defaultSortColumnDuringCollect; - private BitQuickGridSortDirection? _defaultSortDirectionDuringCollect; - - // The associated ES6 module, which uses document-level event listeners - //private IJSObjectReference? _jsModule; - private IJSObjectReference? _jsEventDisposable; - - // Caches of method->delegate conversions - private readonly RenderFragment _renderColumnHeaders; - private readonly RenderFragment _renderNonVirtualizedRows; - - // We try to minimize the number of times we query the items provider, since queries may be expensive - // We only re-query when the developer calls RefreshDataAsync, or if we know something's changed, such - // as sort order, the pagination state, or the data source itself. These fields help us detect when - // things have changed, and to discard earlier load attempts that were superseded. - private int? _lastRefreshedPaginationStateHash; - private object? _lastAssignedItemsOrProvider; - // Tracks the Virtualize value the data was last refreshed under, so a flip between virtualized and - // non-virtualized rendering forces a re-query (otherwise the stale non-virtualized view would linger). - private bool? _lastRefreshedVirtualize; - private CancellationTokenSource? _pendingDataLoadCancellationTokenSource; - // Hash of the collected column set the resize handles were last bound against, so we only rebind - // when the columns actually change rather than on every render. - private int? _lastInitColumnsHash; - // Tracks the ResizableColumns value the resize handles were last bound against, so toggling the - // feature on/off (without otherwise changing the columns) still rebinds the new/removed handles. - private bool _lastResizableColumns; - - // If the PaginationState mutates, it raises this event. We use it to trigger a re-render. - private readonly EventCallbackSubscriber _currentPageItemsChanged; - - - - [Inject] private IJSRuntime _js { get; set; } = default!; - [Inject] private IServiceProvider _services { get; set; } = default!; - - - - /// - /// Constructs an instance of . - /// - public BitQuickGrid() - { - _columns = new(); - _internalGridContext = new(this); - _currentPageItemsChanged = new(EventCallback.Factory.Create(this, RefreshDataCoreAsync)); - _renderColumnHeaders = RenderColumnHeaders; - _renderNonVirtualizedRows = RenderNonVirtualizedRows; - - // As a special case, we don't issue the first data load request until we've collected the initial set of columns - // This is so we can apply default sort order (or any future per-column options) before loading data - // We use EventCallbackSubscriber to safely hook this async operation into the synchronous rendering flow - var columnsFirstCollectedSubscriber = new EventCallbackSubscriber( - EventCallback.Factory.Create(this, RefreshDataCoreAsync)); - columnsFirstCollectedSubscriber.SubscribeOrMove(_internalGridContext.ColumnsFirstCollected); - } - - private bool IsLoading => _pendingDataLoadCancellationTokenSource is not null; - - - - /// - /// Defines the child components of this instance. For example, you may define columns by adding - /// components derived from the base class. - /// - [Parameter] public RenderFragment? ChildContent { get; set; } - - /// - /// An optional CSS class name. If given, this will be included in the class attribute of the rendered table. - /// - [Parameter] public string? Class { get; set; } - - /// - /// Alias of the ChildContent parameter. - /// - [Parameter] public RenderFragment? Columns { get; set; } - - /// - /// Optionally defines a value for @key on each rendered row. Typically this should be used to specify a - /// unique identifier, such as a primary key value, for each data item. - /// - /// This allows the grid to preserve the association between row elements and data items based on their - /// unique identifiers, even when the TGridItem instances are replaced by new copies (for - /// example, after a new query against the underlying data store). - /// - /// If not set, the @key will be the TGridItem instance itself. - /// - [Parameter] public Func ItemKey { get; set; } = x => x!; - - /// - /// A queryable source of data for the grid. - /// - /// This could be in-memory data converted to queryable using the - /// extension method, - /// or an EntityFramework DataSet or an derived from it. - /// - /// You should supply either or , but not both. - /// - [Parameter] public IQueryable? Items { get; set; } - - /// - /// This is applicable only when using . It defines an expected height in pixels for - /// each row, allowing the virtualization mechanism to fetch the correct number of items to match the display - /// size and to ensure accurate scrolling. - /// - [Parameter] public float ItemSize { get; set; } = 50; - - /// - /// A callback that supplies data for the grid. - /// - /// You should supply either or , but not both. - /// - [Parameter] public BitQuickGridItemsProvider? ItemsProvider { get; set; } - - /// - /// The custom template to render while loading the new items. - /// It is honored in both the non-virtualized and the virtualized () paths: - /// while a data refresh is in flight the template is shown instead of the rows, and the - /// placeholder flow still covers rows streamed in during scrolling. - /// - [Parameter] public RenderFragment? LoadingTemplate { get; set; } - - /// - /// Optionally links this instance with a model, - /// causing the grid to fetch and render only the current page of data. - /// - /// This is normally used in conjunction with a component or some other UI logic - /// that displays and updates the supplied instance. - /// - [Parameter] public BitQuickGridPaginationState? Pagination { get; set; } - - /// - /// If true, renders draggable handles around the column headers, allowing the user to resize the columns - /// manually. Size changes are not persisted. - /// - [Parameter] public bool ResizableColumns { get; set; } - - /// - /// The CSS class of all rows of the data grid. - /// - [Parameter] public string? RowClass { get; set; } - - /// - /// The function to generate the CSS class of each row of the data grid. - /// - [Parameter] public Func? RowClassSelector { get; set; } - - /// - /// The CSS style of all row of the data grid. - /// - [Parameter] public string? RowStyle { get; set; } - - /// - /// The function to generate the CSS style of each row of the data grid. - /// - [Parameter] public Func? RowStyleSelector { get; set; } - - /// - /// Optional template to customize row rendering. Receives with - /// set to the default row content; call it to render the original cells or replace with custom content. - /// - [Parameter] public RenderFragment>? RowTemplate { get; set; } - - /// - /// A theme name, with default value "default". This affects which styling rules match the table. - /// - [Parameter] public string? Theme { get; set; } = "default"; - - /// - /// If true, the grid will be rendered with virtualization. This is normally used in conjunction with - /// scrolling and causes the grid to fetch and render only the data around the current scroll viewport. - /// This can greatly improve the performance when scrolling through large data sets. - /// - /// If you use , you should supply a value for and must - /// ensure that every row renders with the same constant height. - /// - /// Generally it's preferable not to use if the amount of data being rendered - /// is small or if you are using pagination. - /// - [Parameter] public bool Virtualize { get; set; } - - - - /// - /// Sets the grid's current sort column to the specified . - /// - /// The column that defines the new sort order. - /// The direction of sorting. If the value is , then it will toggle the direction on each call. - /// A representing the completion of the operation. - public Task SortByColumnAsync(BitQuickGridColumnBase column, BitQuickGridSortDirection direction = BitQuickGridSortDirection.Auto) - { - _sortByAscending = direction switch - { - BitQuickGridSortDirection.Ascending => true, - BitQuickGridSortDirection.Descending => false, - BitQuickGridSortDirection.Auto => _sortByColumn == column ? !_sortByAscending : true, - _ => throw new NotSupportedException($"Unknown sort direction {direction}"), - }; - - _sortByColumn = column; - - StateHasChanged(); // We want to see the updated sort order in the header, even before the data query is completed - return RefreshDataAsync(); - } - - /// - /// Displays the UI for the specified column, closing any other column - /// options UI that was previously displayed. - /// - /// The column whose options are to be displayed, if any are available. - public void ShowColumnOptions(BitQuickGridColumnBase column) - { - _displayOptionsForColumn = column; - _checkColumnOptionsPosition = true; // Triggers a call to JS to position the options element, apply autofocus, and any other setup - StateHasChanged(); - } - - /// - /// Instructs the grid to re-fetch and render the current data from the supplied data source - /// (either or ). - /// - /// A that represents the completion of the operation. - public async Task RefreshDataAsync() - { - try - { - await RefreshDataCoreAsync(); - } - finally - { - // Always rerender after the core refresh settles, even when it throws, so the grid - // doesn't get stuck showing the loading state if the caller handles the exception. - StateHasChanged(); - } - } - - - - // Invoked by descendant columns at a special time during rendering - internal void AddColumn(BitQuickGridColumnBase column, BitQuickGridSortDirection? isDefaultSortDirection) - { - if (_collectingColumns) - { - _columns.Add(column); - - if (_sortByColumn is null && isDefaultSortDirection.HasValue) - { - _sortByColumn = column; - _sortByAscending = isDefaultSortDirection.Value != BitQuickGridSortDirection.Descending; - } - - // Remember the first default-sort column collected in this pass even when a (possibly stale) - // _sortByColumn is still set. If that prior column turns out to have been dropped, this lets - // FinishCollectingColumns switch to the newly declared default instead of clearing sorting. - if (isDefaultSortDirection.HasValue && _defaultSortColumnDuringCollect is null) - { - _defaultSortColumnDuringCollect = column; - _defaultSortDirectionDuringCollect = isDefaultSortDirection.Value; - } - } - } - - - - /// - protected override Task OnParametersSetAsync() - { - // The associated pagination state may have been added/removed/replaced - _currentPageItemsChanged.SubscribeOrMove(Pagination?.CurrentPageItemsChanged); - - if (Items is not null && ItemsProvider is not null) - { - throw new InvalidOperationException($"BitQuickGrid requires one of {nameof(Items)} or {nameof(ItemsProvider)}, but both were specified."); - } - - // Perform a re-query only if the data source or something else has changed - var _newItemsOrItemsProvider = Items ?? (object?)ItemsProvider; - var dataSourceHasChanged = _newItemsOrItemsProvider != _lastAssignedItemsOrProvider; - if (dataSourceHasChanged) - { - _lastAssignedItemsOrProvider = _newItemsOrItemsProvider; - _asyncQueryExecutor = AsyncQueryExecutorSupplier.GetAsyncQueryExecutor(_services, Items); - } - - var mustRefreshData = dataSourceHasChanged - || (_lastRefreshedVirtualize != Virtualize) - || (ComputePaginationStateHash() != _lastRefreshedPaginationStateHash); - - // We don't want to trigger the first data load until we've collected the initial set of columns, - // because they might perform some action like setting the default sort order, so it would be wasteful - // to have to re-query immediately - if (_columns.Count > 0 && mustRefreshData) - { - return RefreshDataCoreAsync(); - } - - return Task.CompletedTask; - } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - _jsEventDisposable = await _js.BitQuickGridInit(_tableReference); - _lastInitColumnsHash = ComputeColumnsHash(); - _lastResizableColumns = ResizableColumns; - } - else if (ResizableColumns) - { - // The resize handles (.bit-qkg-drg) are bound per-element by init. When the column set - // changes, the header re-renders with fresh handles that have no listeners, so rebind them. - // The handles also appear/disappear when ResizableColumns itself is toggled, so rebind on - // that transition too. Re-running init re-adds the document-level listeners, so stop the - // previous registration first to avoid leaking duplicate handlers. Unchanged renders are skipped. - var hash = ComputeColumnsHash(); - if (hash != _lastInitColumnsHash || !_lastResizableColumns) - { - _lastInitColumnsHash = hash; - _lastResizableColumns = true; - await StopJsEventsAsync(); - _jsEventDisposable = await _js.BitQuickGridInit(_tableReference); - } - } - else if (_lastResizableColumns) - { - // ResizableColumns was just turned off; the drag handles are gone. Rebind so the - // document-level listeners are refreshed and the stale handle registration is dropped. - _lastResizableColumns = false; - await StopJsEventsAsync(); - _jsEventDisposable = await _js.BitQuickGridInit(_tableReference); - } - - if (_checkColumnOptionsPosition && _displayOptionsForColumn is not null) - { - _checkColumnOptionsPosition = false; - await _js.BitQuickGridCheckColumnOptionsPosition(_tableReference); - } - - if (_queueSortReconciliationRefresh) - { - // Column recollection dropped the active sort column; re-query so the grid data matches - // the header state that no longer shows that sort. - _queueSortReconciliationRefresh = false; - await RefreshDataAsync(); - } - } - - private int ComputeColumnsHash() - { - var hash = new HashCode(); - foreach (var col in _columns) hash.Add(col); - return hash.ToHashCode(); - } - - // Only the requested slice inputs (page index + page size) should trigger a re-query. The pagination - // state's own GetHashCode also folds in TotalItemCount, which the grid mutates after every successful - // load, so using it here would make a completed fetch look like a fresh pagination change and kick off - // an immediate redundant second query. - private int? ComputePaginationStateHash() - => Pagination is null ? null : HashCode.Combine(Pagination.CurrentPageIndex, Pagination.ItemsPerPage); - - private async Task StopJsEventsAsync() - { - try - { - if (_jsEventDisposable is not null) - { - await _jsEventDisposable.InvokeVoidAsync("stop"); - await _jsEventDisposable.DisposeAsync(); - _jsEventDisposable = null; - } - } - catch (JSDisconnectedException) { } - catch (JSException) { } - } - - - - private void StartCollectingColumns() - { - _sortByColumnBeforeCollect = _sortByColumn; - _defaultSortColumnDuringCollect = null; - _defaultSortDirectionDuringCollect = null; - _columns.Clear(); - _collectingColumns = true; - } - - private void FinishCollectingColumns() - { - _collectingColumns = false; - - // The column that drove the last data load may no longer be among the freshly collected - // columns (it was removed or replaced). Leaving _sortByColumn pointing at a dropped column - // desyncs the data query from the header, so reconcile it and queue a refresh so the grid - // re-queries. The refresh is run from OnAfterRenderAsync because this runs mid-render. - if (_sortByColumn is not null && _columns.Contains(_sortByColumn) is false) - { - if (_defaultSortColumnDuringCollect is not null) - { - // A newly collected column declares a default sort, so adopt it instead of clearing - // sorting; otherwise a dynamic column swap would drop the intended default order. - _sortByColumn = _defaultSortColumnDuringCollect; - _sortByAscending = _defaultSortDirectionDuringCollect!.Value != BitQuickGridSortDirection.Descending; - } - else - { - _sortByColumn = null; - _sortByAscending = false; - } - _queueSortReconciliationRefresh = true; - } - else if (_columnsCollectedOnce && _sortByColumnBeforeCollect is null && _sortByColumn is not null) - { - // A recollection assigned a brand-new default sort (none was active before). The initial - // collection already loads data via ColumnsFirstCollected, but later recollections do not, - // so queue a refresh to re-query in the newly defaulted sort order and keep header/data in sync. - _queueSortReconciliationRefresh = true; - } - - _columnsCollectedOnce = true; - } - - // Same as RefreshDataAsync, except without forcing a re-render. We use this from OnParametersSetAsync - // because in that case there's going to be a re-render anyway. - private async Task RefreshDataCoreAsync() - { - // Record the Virtualize mode this refresh runs under so every refresh path keeps the marker - // current: the initial column-driven load (via ColumnsFirstCollected), RefreshDataAsync, and the - // parameter-change trigger in OnParametersSetAsync all funnel through here. Updating it only in - // OnParametersSetAsync would leave it stale after those other paths, so a later parameter set - // could wrongly (or never) detect a virtualized/non-virtualized flip. - _lastRefreshedVirtualize = Virtualize; - - // Snapshot the requested pagination slice up front so both the virtualized and non-virtualized - // paths record it. Doing this only in the non-virtualized branch (and relying on - // ProvideVirtualizedItems otherwise) leaves the marker stale when Virtualize is on but its child - // hasn't requested items yet (e.g. first render / right after toggling virtualization). The next - // OnParametersSetAsync would then see ComputePaginationStateHash() != _lastRefreshedPaginationStateHash - // and fire a duplicate initial query for the same slice. - _lastRefreshedPaginationStateHash = ComputePaginationStateHash(); - - // Move into a "loading" state, cancelling any earlier-but-still-pending load. Do NOT dispose - // the previous source here: the load that owns it may still be in flight and holding its token - // (e.g. registered on it), so disposing now could surface an ObjectDisposedException instead of - // the expected OperationCanceledException. Each load disposes its own source in its finally block - // once it has finished using it (whether or not it is still the current one), so a superseded - // source is disposed by its owning load rather than leaked to the GC. - _pendingDataLoadCancellationTokenSource?.Cancel(); - var thisLoadCts = _pendingDataLoadCancellationTokenSource = new CancellationTokenSource(); - - // Render now so the loading state (IsLoading / LoadingTemplate) becomes visible as soon as the - // refresh starts, instead of only after the async load below completes. - StateHasChanged(); - - if (Virtualize) - { - // If we're using Virtualize, we have to go through its RefreshDataAsync API otherwise: - // (1) It won't know to update its own internal state if the provider output has changed - // (2) We won't know what slice of data to query for - // The reference can still be null before it's captured (first render) or right after toggling - // virtualization on; in that case Virtualize will request its own items once it renders, so we - // just reconcile the load-state here. The non-virtualized provider request must never run for a - // virtualized grid. - try - { - if (_virtualizeComponent is not null) - { - await _virtualizeComponent.RefreshDataAsync(); - } - } - finally - { - // Always reconcile the load-state, even if RefreshDataAsync threw, so we don't leak the - // CTS or leave _pendingDataLoadCancellationTokenSource pointing at a disposed instance. - // This load is done with its own source, so dispose it unconditionally; only clear the - // field when it still points at this source (a newer load may already own it). - thisLoadCts.Dispose(); - if (ReferenceEquals(_pendingDataLoadCancellationTokenSource, thisLoadCts)) - { - _pendingDataLoadCancellationTokenSource = null; - } - } - } - else - { - // If we're not using Virtualize, we build and execute a request against the items provider directly - var startIndex = Pagination is null ? 0 : (Pagination.CurrentPageIndex * Pagination.ItemsPerPage); - var request = new BitQuickGridItemsProviderRequest( - startIndex, Pagination?.ItemsPerPage, _sortByColumn, _sortByAscending, thisLoadCts.Token); - try - { - var result = await ResolveItemsRequestAsync(request); - if (!thisLoadCts.IsCancellationRequested) - { - _currentNonVirtualizedViewItems = result.Items; - _ariaBodyRowCount = _currentNonVirtualizedViewItems.Count; - await (Pagination?.SetTotalItemCountAsync(result.TotalItemCount) ?? Task.CompletedTask); - } - } - catch (OperationCanceledException) when (thisLoadCts.IsCancellationRequested) - { - // This load was superseded by a newer request (our own cancellation token fired); swallow - // the cancellation and fall through to the cleanup below so the load-state remains - // consistent. Cancellations from any other source (e.g. a provider-side timeout) propagate. - } - finally - { - // This load is done with its own source, so dispose it unconditionally to avoid leaking - // a superseded source; only clear the field when it still points at this source. - thisLoadCts.Dispose(); - if (ReferenceEquals(_pendingDataLoadCancellationTokenSource, thisLoadCts)) - { - _pendingDataLoadCancellationTokenSource = null; - } - } - } - } - - // Gets called both by RefreshDataCoreAsync and directly by the Virtualize child component during scrolling - private async ValueTask> ProvideVirtualizedItems(ItemsProviderRequest request) - { - _lastRefreshedPaginationStateHash = ComputePaginationStateHash(); - - // Debounce the requests. This eliminates a lot of redundant queries at the cost of slight lag after interactions. - // TODO: Consider making this configurable, or smarter (e.g., doesn't delay on first call in a batch, then the amount - // of delay increases if you rapidly issue repeated requests, such as when scrolling a long way) - try - { - await Task.Delay(100, request.CancellationToken); - } - catch (OperationCanceledException) - { - // The request was superseded/cancelled during the debounce window; abandon it early. - return default; - } - if (request.CancellationToken.IsCancellationRequested) - { - return default; - } - - // Combine the query parameters from Virtualize with the ones from PaginationState - var startIndex = request.StartIndex; - var count = request.Count; - if (Pagination is not null) - { - startIndex += Pagination.CurrentPageIndex * Pagination.ItemsPerPage; - count = Math.Max(0, Math.Min(request.Count, Pagination.ItemsPerPage - request.StartIndex)); - } - - var providerRequest = new BitQuickGridItemsProviderRequest( - startIndex, count, _sortByColumn, _sortByAscending, request.CancellationToken); - BitQuickGridItemsProviderResult providerResult; - try - { - providerResult = await ResolveItemsRequestAsync(providerRequest); - } - catch (OperationCanceledException) when (request.CancellationToken.IsCancellationRequested) - { - // The request was superseded by a newer one after the debounce window (our own cancellation - // token fired); the items provider observed the cancellation token and bailed out. Return an - // empty result the virtualization system can handle rather than letting the cancellation - // propagate out of here. Cancellations from any other source propagate as real errors. - return default; - } - - if (!request.CancellationToken.IsCancellationRequested) - { - // ARIA's rowcount is part of the UI, so it should reflect what the human user regards as the number of rows in the table, - // not the number of physical elements. For virtualization this means what's in the entire scrollable range, not just - // the current viewport. In the case where you're also paginating then it means what's conceptually on the current page. - // The last page can hold fewer than ItemsPerPage rows, so clamp the paginated count to the items remaining on the current - // page; otherwise assistive tech would announce non-existent trailing rows on a short final page. - _ariaBodyRowCount = Pagination is null - ? providerResult.TotalItemCount - : Math.Clamp(providerResult.TotalItemCount - Pagination.CurrentPageIndex * Pagination.ItemsPerPage, 0, Pagination.ItemsPerPage); - - await (Pagination?.SetTotalItemCountAsync(providerResult.TotalItemCount) ?? Task.CompletedTask); - - // We're supplying the row index along with each row's data because we need it for aria-rowindex, and we have to account for - // the virtualized start index. It might be more performant just to have some _latestQueryRowStartIndex field, but we'd have - // to make sure it doesn't get out of sync with the rows being rendered. - return new ItemsProviderResult<(int, TGridItem)>( - items: providerResult.Items.Select((x, i) => ValueTuple.Create(i + request.StartIndex + 2, x)), - totalItemCount: _ariaBodyRowCount); - } - - return default; - } - - // Normalizes all the different ways of configuring a data source so they have common GridItemsProvider-shaped API - private async ValueTask> ResolveItemsRequestAsync(BitQuickGridItemsProviderRequest request) - { - if (ItemsProvider is not null) - { - return await ItemsProvider(request); - } - else if (Items is not null) - { - var totalItemCount = _asyncQueryExecutor is null ? Items.Count() : await _asyncQueryExecutor.CountAsync(Items); - var result = request.ApplySorting(Items).Skip(request.StartIndex); - if (request.Count.HasValue) - { - result = result.Take(request.Count.Value); - } - var resultArray = _asyncQueryExecutor is null ? result.ToArray() : await _asyncQueryExecutor.ToArrayAsync(result); - return BitQuickGridItemsProviderResult.From(resultArray, totalItemCount); - } - else - { - return BitQuickGridItemsProviderResult.From(Array.Empty(), 0); - } - } - - private string AriaSortValue(BitQuickGridColumnBase column) - => _sortByColumn == column - ? (_sortByAscending ? "ascending" : "descending") - : "none"; - - private string? ColumnHeaderClass(BitQuickGridColumnBase column) - => _sortByColumn == column - ? $"{ColumnClass(column)} {(_sortByAscending ? "bit-qkg-csa" : "bit-qkg-csd")}" - : ColumnClass(column); - - private string GridClass() - => $"bit-qkg {Class} {((IsLoading && LoadingTemplate is null) ? "loading" : null)}".Trim(); - - private void CloseColumnOptions() - { - _displayOptionsForColumn = null; - } - - private string? GetRowClass(TGridItem item) - { - var selected = RowClassSelector?.Invoke(item); - - if (string.IsNullOrEmpty(RowClass)) return string.IsNullOrEmpty(selected) ? null : selected; - if (string.IsNullOrEmpty(selected)) return RowClass; - return $"{RowClass} {selected}"; - } - - private string? GetRowStyle(TGridItem item) - { - var selected = RowStyleSelector?.Invoke(item); - - if (string.IsNullOrEmpty(RowStyle)) return string.IsNullOrEmpty(selected) ? null : selected; - if (string.IsNullOrEmpty(selected)) return RowStyle; - return $"{RowStyle};{selected}"; - } - - - - private static string? ColumnClass(BitQuickGridColumnBase column) => column.Align switch - { - BitQuickGridAlign.Center => $"bit-qkg-cjc {column.Class}", - BitQuickGridAlign.Right => $"bit-qkg-cje {column.Class}", - _ => column.Class, - }; - - - - - /// - public async ValueTask DisposeAsync() - { - await DisposeAsync(true); - GC.SuppressFinalize(this); - } - - protected virtual async ValueTask DisposeAsync(bool disposing) - { - if (_disposed || disposing is false) return; - - // Cancel (but don't dispose) any in-flight load: the load that owns this source may still be - // holding its token, so disposing here could race into an ObjectDisposedException. The owning - // load disposes it in its finally block (it's still the current source during disposal), so we - // only signal cancellation here. - _pendingDataLoadCancellationTokenSource?.Cancel(); - - _currentPageItemsChanged.Dispose(); - - try - { - if (_jsEventDisposable is not null) - { - await _jsEventDisposable.InvokeVoidAsync("stop"); - await _jsEventDisposable.DisposeAsync(); - } - - //if (_jsModule is not null) - //{ - // await _jsModule.DisposeAsync(); - //} - } - catch (JSDisconnectedException) - { - // The JS side may routinely be gone already if the reason we're disposing is that - // the client disconnected. This is not an error. - } - catch (JSException ex) - { - // it seems it's safe to just ignore this exception here. - // otherwise it will blow up the MAUI app in a page refresh for example. - Console.WriteLine(ex.Message); - } - - _disposed = true; - } -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss deleted file mode 100644 index 1109021ff7..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.scss +++ /dev/null @@ -1,138 +0,0 @@ -.bit-qkg { - width: 100%; - --bit-qkg-col-gap: 1rem; - - th { - position: relative; - } - - > thead > tr > th { - font-weight: normal; - } - - &.loading > tbody { - opacity: 0.25; - transition: opacity linear 100ms 25ms; - } - - > tbody > tr > td { - padding: 0.1rem calc(0.4rem + var(--bit-qkg-col-gap)) 0.1rem 0.4rem; - } -} - - -.bit-qkg-hct { - display: flex; - position: relative; - align-items: center; - padding-inline-end: var(--bit-qkg-col-gap); -} - -.bit-qkg-cop { - z-index: 1; - padding: 1rem; - border: 1px solid; - position: absolute; - inset-inline-start: 0; - border-color: var(--bit-clr-brd-pri); - background-color: var(--bit-clr-bg-sec); -} - -.bit-qkg-cob { - width: 1.5rem; - background: unset; - - &::before { - content: "\E712"; - font-style: normal; - font-weight: normal; - display: inline-block; - font-family: 'Fabric MDL2 bit BlazorUI Extras'; - } -} - -.bit-qkg-drg { - width: 1rem; - cursor: ew-resize; - position: absolute; - inset-block-end: 0; - inset-block-start: 0; - inset-inline-end: calc(var(--bit-qkg-col-gap)/2 - 0.5rem); - - &::after { - content: ' '; - position: absolute; - inset-block-end: 5px; - inset-block-start: 5px; - inset-inline-start: 0.5rem; - border-color: var(--bit-clr-brd-pri); - border-inline-start: 1px solid var(--bit-clr-brd-pri); - } -} - -.bit-qkg-srt { - width: 1rem; - height: 1rem; - opacity: 0.5; - align-self: center; - text-align: center; -} - -.bit-qkg-csa .bit-qkg-srt::before, -.bit-qkg-csd .bit-qkg-srt::before { - content: "\E96F"; - font-style: normal; - font-weight: normal; - display: inline-block; - transform: rotate(90deg); - font-family: 'Fabric MDL2 bit BlazorUI Extras'; -} - -.bit-qkg-csd .bit-qkg-srt { - transform: scaleY(-1) translateY(-2px); -} - -.bit-qkg-ctl { - gap: 0.4rem; - flex-grow: 1; - display: flex; - min-width: 0px; - font-size: 1rem; - font-weight: bold; - padding: 0.1rem 0.4rem; -} - -button.bit-qkg-ctl { - border: none; - color: inherit; - cursor: pointer; - background: none; - position: relative; -} - -.bit-qkg-ctt { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -.bit-qkg-cjc { - text-align: center; - - .bit-qkg-ctl { - justify-content: center; - } -} - -.bit-qkg-cje { - text-align: end; - - .bit-qkg-ctl { - flex-direction: row-reverse; - } -} - -.bit-qkg-plh::after { - opacity: 0.75; - content: '\2026'; -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts deleted file mode 100644 index 03a2585c3a..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGrid.ts +++ /dev/null @@ -1,151 +0,0 @@ -namespace BitBlazorUI { - export class QuickGrid { - public static init(tableElement: any) { - // Tracks the drag handles this init() bound so stop() can remove their listeners too, - // preventing handlers from accumulating across repeated init()/stop() cycles. - const boundDragHandles: { handle: any, listener: any }[] = []; - // Holds the teardown for an in-progress column resize drag (the document-level move/up - // listeners installed by handleMouseDown) so stop() can detach them even if disposal - // happens mid-drag, before the pointer is released. - const dragState: { cleanup: (() => void) | null } = { cleanup: null }; - QuickGrid.enableColumnResizing(tableElement, boundDragHandles, dragState); - - const bodyClickHandler = (event: any) => { - const columnOptionsElement = tableElement.tHead.querySelector('.bit-qkg-cop'); - if (columnOptionsElement && event.composedPath().indexOf(columnOptionsElement) < 0) { - tableElement.dispatchEvent(new CustomEvent('closecolumnoptions', { bubbles: true })); - } - }; - const keyDownHandler = (event: any) => { - const columnOptionsElement = tableElement.tHead.querySelector('.bit-qkg-cop'); - if (columnOptionsElement && event.key === "Escape") { - tableElement.dispatchEvent(new CustomEvent('closecolumnoptions', { bubbles: true })); - } - }; - - document.body.addEventListener('click', bodyClickHandler); - document.body.addEventListener('mousedown', bodyClickHandler); // Otherwise it seems strange that it doesn't go away until you release the mouse button - document.body.addEventListener('keydown', keyDownHandler); - - return { - stop: () => { - document.body.removeEventListener('click', bodyClickHandler); - document.body.removeEventListener('mousedown', bodyClickHandler); - document.body.removeEventListener('keydown', keyDownHandler); - - // Detach any document-level listeners left by an in-progress resize drag and clear - // the active drag state, so disposal/re-init mid-drag can't keep mutating a stale th. - if (dragState.cleanup) { - dragState.cleanup(); - dragState.cleanup = null; - } - - // Remove the per-handle drag listeners and clear the bound marker so a later - // init() can rebind the same surviving elements without duplicating handlers. - boundDragHandles.forEach(({ handle, listener }) => { - handle.removeEventListener('mousedown', listener); - handle.removeEventListener('touchstart', listener); - delete handle.__bitQkgResizeBound; - }); - boundDragHandles.length = 0; - } - }; - } - - public static checkColumnOptionsPosition(tableElement: any) { - const colOptions = tableElement.tHead && tableElement.tHead.querySelector('.bit-qkg-cop'); // Only match within *our* thead, not nested tables - if (colOptions) { - // We want the options popup to be positioned over the grid, not overflowing on either side, because it's possible that - // beyond either side is off-screen or outside the scroll range of an ancestor - const gridRect = tableElement.getBoundingClientRect(); - const optionsRect = colOptions.getBoundingClientRect(); - const leftOverhang = Math.max(0, gridRect.left - optionsRect.left); - const rightOverhang = Math.max(0, optionsRect.right - gridRect.right); - if (leftOverhang || rightOverhang) { - // In the unlikely event that it overhangs both sides, we'll center it - const applyOffset = leftOverhang && rightOverhang ? (leftOverhang - rightOverhang) / 2 : (leftOverhang - rightOverhang); - colOptions.style.transform = `translateX(${applyOffset}px)`; - } else { - // Clear any offset left over from a previous opening so the popup isn't misaligned. - colOptions.style.transform = ''; - } - - if (typeof colOptions.scrollIntoViewIfNeeded === 'function') { - colOptions.scrollIntoViewIfNeeded(); - } else { - // Fall back to a nearest-edge scroll so browsers without scrollIntoViewIfNeeded - // don't scroll more aggressively than needed (the default scrollIntoView() can - // jump the popup fully into view and shift the grid). - colOptions.scrollIntoView({ block: 'nearest', inline: 'nearest' }); - } - - const autoFocusElem = colOptions.querySelector('[autofocus]'); - if (autoFocusElem) { - autoFocusElem.focus(); - } - } - } - - private static enableColumnResizing(tableElement: any, boundDragHandles: { handle: any, listener: any }[], dragState: { cleanup: (() => void) | null }) { - tableElement.tHead.querySelectorAll('.bit-qkg-drg').forEach((handle: any) => { - // Bind each handle only once. A surviving handle (reused by Blazor's diffing across - // re-renders) would otherwise accumulate a fresh listener on every init() call. - if (handle.__bitQkgResizeBound) return; - handle.__bitQkgResizeBound = true; - - handle.addEventListener('mousedown', handleMouseDown); - if ('ontouchstart' in window) { - handle.addEventListener('touchstart', handleMouseDown); - } - boundDragHandles.push({ handle, listener: handleMouseDown }); - - function handleMouseDown(evt: any) { - evt.preventDefault(); - evt.stopPropagation(); - - const th = handle.parentElement; - const startPageX = evt.touches ? evt.touches[0].pageX : evt.pageX; - const originalColumnWidth = th.offsetWidth; - const rtlMultiplier = window.getComputedStyle(th, null).getPropertyValue('direction') === 'rtl' ? -1 : 1; - let updatedColumnWidth = 0; - - function handleMouseMove(evt: any) { - evt.stopPropagation(); - const newPageX = evt.touches ? evt.touches[0].pageX : evt.pageX; - // Clamp to a minimum width so a column can't collapse to (or below) zero while dragging. - const minColumnWidth = 20; - const nextWidth = Math.max(minColumnWidth, originalColumnWidth + (newPageX - startPageX) * rtlMultiplier); - if (Math.abs(nextWidth - updatedColumnWidth) > 0) { - updatedColumnWidth = nextWidth; - th.style.width = `${updatedColumnWidth}px`; - } - } - - function handleMouseUp() { - document.body.removeEventListener('mousemove', handleMouseMove); - document.body.removeEventListener('mouseup', handleMouseUp); - document.body.removeEventListener('touchmove', handleMouseMove); - document.body.removeEventListener('touchend', handleMouseUp); - document.body.removeEventListener('touchcancel', handleMouseUp); - dragState.cleanup = null; - } - - if (window.TouchEvent && evt instanceof TouchEvent) { - document.body.addEventListener('touchmove', handleMouseMove, { passive: true }); - document.body.addEventListener('touchend', handleMouseUp, { passive: true }); - // A touch gesture can be interrupted (e.g. by the system) without firing touchend, - // which would leave the move/end listeners attached. Tear down on touchcancel too. - document.body.addEventListener('touchcancel', handleMouseUp, { passive: true }); - } else { - document.body.addEventListener('mousemove', handleMouseMove, { passive: true }); - document.body.addEventListener('mouseup', handleMouseUp, { passive: true }); - } - - // Expose this drag's teardown so stop() can detach the document-level listeners if - // the grid is disposed/re-initialized before the pointer is released. - dragState.cleanup = handleMouseUp; - } - }); - } - } -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridJsRuntimeExtensions.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridJsRuntimeExtensions.cs deleted file mode 100644 index 74e83b985a..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridJsRuntimeExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Bit.BlazorUI; - -internal static class BitQuickGridJsRuntimeExtensions -{ - public static async ValueTask BitQuickGridInit(this IJSRuntime jsRuntime, ElementReference tableElement) - { - return await jsRuntime.InvokeAsync("BitBlazorUI.QuickGrid.init", tableElement); - } - - public static async ValueTask BitQuickGridCheckColumnOptionsPosition(this IJSRuntime jsRuntime, ElementReference tableElement) - { - await jsRuntime.InvokeVoidAsync("BitBlazorUI.QuickGrid.checkColumnOptionsPosition", tableElement); - } -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridRowTemplateArgs.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridRowTemplateArgs.cs deleted file mode 100644 index e79ed3fb46..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridRowTemplateArgs.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace Bit.BlazorUI; - -/// -/// Arguments passed to the render fragment. -/// -/// The type of data represented by each row in the grid. -public class BitQuickGridRowTemplateArgs -{ - /// - /// A render fragment that produces the original row markup (the default <tr> with all column cells). - /// Render this in your custom template to include the default row, or omit it to replace entirely. - /// - public required RenderFragment OriginalRow { get; set; } - - /// - /// The 1-based row index used for accessibility (e.g. aria-rowindex). - /// - public int RowIndex { get; set; } - - /// - /// The data item for this row. - /// - public T RowItem { get; set; } = default!; -} - -/// -/// Backward-compatible alias for , kept so existing code that -/// stored, returned or accepted the old BitDataGridRowTemplateArgs<T> type keeps compiling. It -/// inherits the renamed type (so instances are accepted anywhere a -/// is expected) and preserves the same public API surface. -/// -/// The type of data represented by each row in the grid. -[Obsolete("BitDataGridRowTemplateArgs has been renamed to BitQuickGridRowTemplateArgs. Use BitQuickGridRowTemplateArgs instead.")] -public class BitDataGridRowTemplateArgs : BitQuickGridRowTemplateArgs -{ -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridSortDirection.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridSortDirection.cs deleted file mode 100644 index 0136b25a3b..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/BitQuickGridSortDirection.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Bit.BlazorUI; - -/// -/// Describes the direction in which a column is sorted. -/// -public enum BitQuickGridSortDirection -{ - /// - /// Ascending order. - /// - Ascending, - - /// - /// Descending order. - /// - Descending, - - /// - /// Automatic sort order. When used with , - /// the sort order will automatically toggle between and on successive calls, and - /// resets to whenever the specified column is changed. - /// - Auto, -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridAlign.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridAlign.cs deleted file mode 100644 index f71d0eb676..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridAlign.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Bit.BlazorUI; - -/// -/// Describes alignment for a column. -/// -public enum BitQuickGridAlign -{ - /// - /// Justifies the content against the start of the container. - /// - Left, - - /// - /// Justifies the content at the center of the container. - /// - Center, - - /// - /// Justifies the content at the end of the container. - /// - Right, -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor deleted file mode 100644 index cc030ea8e5..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor +++ /dev/null @@ -1,148 +0,0 @@ -@namespace Bit.BlazorUI -@typeparam TGridItem - -@{ - InternalGridContext.Grid.AddColumn(this, IsDefaultSort); -} - -@code -{ - [CascadingParameter] internal InternalGridContext InternalGridContext { get; set; } = default!; - - /// - /// Title text for the column. This is rendered automatically if is not used. - /// - [Parameter] public string? Title { get; set; } - - /// - /// An optional CSS class name. If specified, this is included in the class attribute of table header and body cells - /// for this column. - /// - [Parameter] public string? Class { get; set; } - - /// - /// If specified, controls the justification of table header and body cells for this column. - /// - [Parameter] public BitQuickGridAlign Align { get; set; } - - /// - /// An optional template for this column's header cell. If not specified, the default header template - /// includes the along with any applicable sort indicators and options buttons. - /// - [Parameter] public RenderFragment>? HeaderTemplate { get; set; } - - /// - /// If specified, indicates that this column has this associated options UI. A button to display this - /// UI will be included in the header cell by default. - /// - /// If is used, it is left up to that template to render any relevant - /// "show options" UI and invoke the grid's ). - /// - [Parameter] public RenderFragment? ColumnOptions { get; set; } - - /// - /// Indicates whether the data should be sortable by this column. - /// - /// The default value may vary according to the column type (for example, a - /// is sortable by default if any parameter is specified). - /// - [Parameter] public bool? Sortable { get; set; } - - /// - /// If specified and not null, indicates that this column represents the initial sort order - /// for the grid. The supplied value controls the default sort direction. - /// - [Parameter] public BitQuickGridSortDirection? IsDefaultSort { get; set; } - - /// - /// If specified, virtualized grids will use this template to render cells whose data has not yet been loaded. - /// - [Parameter] public RenderFragment? PlaceholderTemplate { get; set; } - - /// - /// Gets a reference to the enclosing . - /// - public BitQuickGrid Grid => InternalGridContext.Grid; - - /// - /// Overridden by derived components to provide rendering logic for the column's cells. - /// - /// The current . - /// The data for the row being rendered. - protected internal abstract void CellContent(RenderTreeBuilder builder, TGridItem item); - - /// - /// Gets or sets a that will be rendered for this column's header cell. - /// This allows derived components to change the header output. However, derived components are then - /// responsible for using within that new output if they want to continue - /// respecting that option. - /// - protected internal RenderFragment HeaderContent { get; protected set; } - - /// - /// Get a value indicating whether this column should act as sortable if no value was set for the - /// parameter. The default behavior is not to be - /// sortable unless is true. - /// - /// Derived components may override this to implement alternative default sortability rules. - /// - /// True if the column should be sortable by default, otherwise false. - protected virtual bool IsSortableByDefault() => false; - - /// - /// Constructs an instance of . - /// - public BitQuickGridColumnBase() - { - HeaderContent = RenderDefaultHeaderContent; - } - - private string SortButtonLabel() - => string.IsNullOrEmpty(Title) ? "Sort" : $"Sort by {Title}"; - - private string ColumnOptionsLabel() - => string.IsNullOrEmpty(Title) ? "Column options" : $"Column options for {Title}"; - - private void RenderDefaultHeaderContent(RenderTreeBuilder __builder) - { - @if (HeaderTemplate is not null) - { - @HeaderTemplate(this) - } - else - { - @if (ColumnOptions is not null && Align != BitQuickGridAlign.Right) - { - - } - - if (Sortable.HasValue ? Sortable.Value : IsSortableByDefault()) - { - - } - else - { -
-
@Title
-
- } - - @if (ColumnOptions is not null && Align == BitQuickGridAlign.Right) - { - - } - } - } - - internal void RenderPlaceholderContent(RenderTreeBuilder __builder, PlaceholderContext placeholderContext) - { - // Blank if no placeholder template was supplied, as it's enough to style with CSS by default - if (PlaceholderTemplate is not null) - { - @PlaceholderTemplate(placeholderContext) - } - } -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor.cs deleted file mode 100644 index d0dc409a5f..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridColumnBase.razor.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Bit.BlazorUI; - -/// -/// An abstract base class for columns in a . -/// -/// The type of data represented by each row in the grid. -public abstract partial class BitQuickGridColumnBase -{ -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs deleted file mode 100644 index 7687d475da..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridPropertyColumn.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System.Linq.Expressions; - -namespace Bit.BlazorUI; - -/// -/// Represents a column whose cells display a single value. -/// -/// The type of data represented by each row in the grid. -/// The type of the value being displayed in the column's cells. -public class BitQuickGridPropertyColumn : BitQuickGridColumnBase, IBitQuickGridSortBuilderColumn -{ - private Expression>? _lastAssignedProperty; - private string? _lastAssignedFormat; - private bool _titleWasExplicitlySet; - private Func? _cellTextFunc; - private BitQuickGridSort? _sortBuilder; - - /// - /// Defines the value to be displayed in this column's cells. - /// - [Parameter, EditorRequired] public Expression> Property { get; set; } = default!; - - /// - /// Optionally specifies a format string for the value. - /// - /// Using this requires the type to implement . - /// - [Parameter] public string? Format { get; set; } - - BitQuickGridSort? IBitQuickGridSortBuilderColumn.SortBuilder => _sortBuilder; - - - /// - public override Task SetParametersAsync(ParameterView parameters) - { - // Track whether Title was supplied explicitly by the consumer in *this* render's ParameterView - // rather than inferring intent from value equality (which can't tell an auto-derived header apart - // from an explicit one matching the member name). Recompute it every render instead of latching: - // if a consumer later removes Title, explicitness must drop back to false so the auto-generated - // header can return. - _titleWasExplicitlySet = parameters.TryGetValue(nameof(Title), out _); - - // When the consumer stops passing Title, the incoming ParameterView no longer contains it, so - // base.SetParametersAsync leaves the previously assigned (explicit) value in place. Clear it up - // front so a removed Title can't linger as a stale header; OnParametersSet then re-derives the - // auto-generated title from the Property when possible. - if (!_titleWasExplicitlySet) - { - Title = null; - } - - return base.SetParametersAsync(parameters); - } - - - /// - protected override void OnParametersSet() - { - // We have to do a bit of pre-processing on the lambda expression. Only do that if the Property - // or the Format has changed, so a Format-only change still rebuilds the cell formatter. - if (_lastAssignedProperty != Property || _lastAssignedFormat != Format) - { - var compiledPropertyExpression = Property.Compile(); - Func cellTextFunc; - - if (Format.HasValue()) - { - // For a nullable value type (e.g. int?, DateTime?) Nullable itself does not implement - // IFormattable, but its underlying type does and a boxed non-null value formats correctly. - // Check the underlying type so Format is allowed on nullable columns too. - var formattableType = Nullable.GetUnderlyingType(typeof(TProp)) ?? typeof(TProp); - if (typeof(IFormattable).IsAssignableFrom(formattableType)) - { - cellTextFunc = item => ((IFormattable?)compiledPropertyExpression!(item))?.ToString(Format, null); - } - else - { - throw new InvalidOperationException($"A '{nameof(Format)}' parameter was supplied, but the type '{typeof(TProp)}' does not implement '{typeof(IFormattable)}'."); - } - } - else - { - cellTextFunc = item => compiledPropertyExpression!(item)?.ToString(); - } - - _cellTextFunc = cellTextFunc; - _sortBuilder = BitQuickGridSort.ByAscending(Property); - - // Only record the assignments after the formatter has been built and validated, so a failed - // Format/TProp validation above doesn't suppress a retry on the next parameters set (which - // would leave _cellTextFunc in a stale or null state). - _lastAssignedProperty = Property; - _lastAssignedFormat = Format; - } - - if (_titleWasExplicitlySet) - { - // The consumer supplied Title this render; base.SetParametersAsync already applied it, so - // there is nothing more to do. - } - else if (Property.Body is MemberExpression memberExpression) - { - // No explicit Title this render, so derive the header from the member name. Recomputed every - // render, this also lets the auto-generated header follow Property changes and reappear after - // a previously explicit Title is removed. - Title = memberExpression.Member.Name; - } - // else: Property is a method/cast expression (no member name to derive a header from) and no - // explicit Title was supplied. SetParametersAsync already cleared any stale value, so the column - // simply has no header. - } - - /// - protected internal override void CellContent(RenderTreeBuilder builder, TGridItem item) - => builder.AddContent(0, _cellTextFunc!(item)); -} - -/// -/// Backward-compatible alias for , kept so existing -/// Razor markup that referenced the old BitDataGridPropertyColumn<TGridItem, TProp> component keeps -/// compiling. It inherits the renamed column, so it behaves identically while remaining discoverable -/// alongside during the migration window. -/// -/// The type of data represented by each row in the grid. -/// The type of the value being displayed in the column's cells. -[Obsolete("BitDataGridPropertyColumn has been renamed to BitQuickGridPropertyColumn. Use BitQuickGridPropertyColumn instead.")] -public class BitDataGridPropertyColumn : BitQuickGridPropertyColumn -{ -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs deleted file mode 100644 index 2bd193bf60..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridSort.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System.Linq.Expressions; - -namespace Bit.BlazorUI; - -/// -/// Represents a sort order specification used within . -/// -/// The type of data represented by each row in the grid. -public class BitQuickGridSort -{ - private const string ExpressionNotRepresentableMessage = "The supplied expression can't be represented as a property name for sorting. Only simple member expressions, such as @(x => x.SomeProperty), can be converted to property names."; - - private Func, bool, IOrderedQueryable> _first; - private List, bool, IOrderedQueryable>>? _then; - - private (LambdaExpression, bool) _firstExpression; - private List<(LambdaExpression, bool)>? _thenExpressions; - - private IReadOnlyCollection<(string PropertyName, BitQuickGridSortDirection Direction)>? _cachedPropertyListAscending; - private IReadOnlyCollection<(string PropertyName, BitQuickGridSortDirection Direction)>? _cachedPropertyListDescending; - - internal BitQuickGridSort(Func, bool, IOrderedQueryable> first, (LambdaExpression, bool) firstExpression) - { - _first = first; - _firstExpression = firstExpression; - _then = default; - _thenExpressions = default; - } - - /// - /// Produces a instance that sorts according to the specified , ascending. - /// - /// The type of the expression's value. - /// An expression defining how a set of instances are to be sorted. - /// A instance representing the specified sorting rule. - public static BitQuickGridSort ByAscending(Expression> expression) - => new BitQuickGridSort((queryable, asc) => asc ? queryable.OrderBy(expression) : queryable.OrderByDescending(expression), - (expression, true)); - - /// - /// Produces a instance that sorts according to the specified , descending. - /// - /// The type of the expression's value. - /// An expression defining how a set of instances are to be sorted. - /// A instance representing the specified sorting rule. - public static BitQuickGridSort ByDescending(Expression> expression) - => new BitQuickGridSort((queryable, asc) => asc ? queryable.OrderByDescending(expression) : queryable.OrderBy(expression), - (expression, false)); - - /// - /// Updates a instance by appending a further sorting rule. - /// - /// The type of the expression's value. - /// An expression defining how a set of instances are to be sorted. - /// A instance representing the specified sorting rule. - public BitQuickGridSort ThenAscending(Expression> expression) - { - _then ??= new(); - _thenExpressions ??= new(); - _then.Add((queryable, asc) => asc ? queryable.ThenBy(expression) : queryable.ThenByDescending(expression)); - _thenExpressions.Add((expression, true)); - _cachedPropertyListAscending = null; - _cachedPropertyListDescending = null; - return this; - } - - /// - /// Updates a instance by appending a further sorting rule. - /// - /// The type of the expression's value. - /// An expression defining how a set of instances are to be sorted. - /// A instance representing the specified sorting rule. - public BitQuickGridSort ThenDescending(Expression> expression) - { - _then ??= new(); - _thenExpressions ??= new(); - _then.Add((queryable, asc) => asc ? queryable.ThenByDescending(expression) : queryable.ThenBy(expression)); - _thenExpressions.Add((expression, false)); - _cachedPropertyListAscending = null; - _cachedPropertyListDescending = null; - return this; - } - - internal IOrderedQueryable Apply(IQueryable queryable, bool ascending) - { - var orderedQueryable = _first(queryable, ascending); - - if (_then is not null) - { - foreach (var clause in _then) - { - orderedQueryable = clause(orderedQueryable, ascending); - } - } - - return orderedQueryable; - } - - internal IReadOnlyCollection<(string PropertyName, BitQuickGridSortDirection Direction)> ToPropertyList(bool ascending) - { - if (ascending) - { - _cachedPropertyListAscending ??= BuildPropertyList(ascending: true); - return _cachedPropertyListAscending; - } - else - { - _cachedPropertyListDescending ??= BuildPropertyList(ascending: false); - return _cachedPropertyListDescending; - } - } - - private IReadOnlyCollection<(string PropertyName, BitQuickGridSortDirection Direction)> BuildPropertyList(bool ascending) - { - var result = new List<(string, BitQuickGridSortDirection)>(); - result.Add((ToPropertyName(_firstExpression.Item1), (_firstExpression.Item2 ^ ascending) ? BitQuickGridSortDirection.Descending : BitQuickGridSortDirection.Ascending)); - - if (_thenExpressions is not null) - { - foreach (var (thenLambda, thenAscending) in _thenExpressions) - { - result.Add((ToPropertyName(thenLambda), (thenAscending ^ ascending) ? BitQuickGridSortDirection.Descending : BitQuickGridSortDirection.Ascending)); - } - } - - return result; - } - - // Not sure we really want this level of complexity, but it converts expressions like @(c => c.Medals.Gold) to "Medals.Gold" - private static string ToPropertyName(LambdaExpression expression) - { - var body = expression.Body as MemberExpression; - if (body is null) - { - throw new ArgumentException(ExpressionNotRepresentableMessage); - } - - // Handles cases like @(x => x.Name) - if (body.Expression is ParameterExpression) - { - return body.Member.Name; - } - - // First work out the length of the string we'll need, so that we can use string.Create - var length = body.Member.Name.Length; - var node = body; - while (node.Expression is not null) - { - if (node.Expression is MemberExpression parentMember) - { - length += parentMember.Member.Name.Length + 1; - node = parentMember; - } - else if (node.Expression is ParameterExpression) - { - break; - } - else - { - throw new ArgumentException(ExpressionNotRepresentableMessage); - } - } - - // Now construct the string - return string.Create(length, body, (chars, body) => - { - var nextPos = chars.Length; - while (body is not null) - { - nextPos -= body.Member.Name.Length; - body.Member.Name.CopyTo(chars.Slice(nextPos)); - if (nextPos > 0) - { - chars[--nextPos] = '.'; - } - body = (body.Expression as MemberExpression)!; - } - }); - } -} - -/// -/// Backward-compatible alias for , kept so existing code that -/// stored, returned or accepted the old BitDataGridSort<TGridItem> type keeps compiling. It -/// inherits the renamed type (so instances are accepted anywhere a -/// is expected) and its factory/chaining helpers return the old type to preserve the original API shape. -/// -/// The type of data represented by each row in the grid. -[Obsolete("BitDataGridSort has been renamed to BitQuickGridSort. Use BitQuickGridSort instead.")] -public class BitDataGridSort : BitQuickGridSort -{ - internal BitDataGridSort(Func, bool, IOrderedQueryable> first, (LambdaExpression, bool) firstExpression) - : base(first, firstExpression) - { - } - - /// - public static new BitDataGridSort ByAscending(Expression> expression) - => new BitDataGridSort((queryable, asc) => asc ? queryable.OrderBy(expression) : queryable.OrderByDescending(expression), - (expression, true)); - - /// - public static new BitDataGridSort ByDescending(Expression> expression) - => new BitDataGridSort((queryable, asc) => asc ? queryable.OrderByDescending(expression) : queryable.OrderBy(expression), - (expression, false)); - - /// - public new BitDataGridSort ThenAscending(Expression> expression) - { - base.ThenAscending(expression); - return this; - } - - /// - public new BitDataGridSort ThenDescending(Expression> expression) - { - base.ThenDescending(expression); - return this; - } -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridTemplateColumn.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridTemplateColumn.cs deleted file mode 100644 index 1801d70687..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/BitQuickGridTemplateColumn.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace Bit.BlazorUI; - -/// -/// Represents a column whose cells render a supplied template. -/// -/// The type of data represented by each row in the grid. -public class BitQuickGridTemplateColumn : BitQuickGridColumnBase, IBitQuickGridSortBuilderColumn -{ - private readonly static RenderFragment EmptyChildContent = _ => builder => { }; - - /// - /// Specifies the content to be rendered for each row in the table. - /// - [Parameter] public RenderFragment ChildContent { get; set; } = EmptyChildContent; - - /// - /// Optionally specifies sorting rules for this column. - /// - [Parameter] public BitQuickGridSort? SortBy { get; set; } - - BitQuickGridSort? IBitQuickGridSortBuilderColumn.SortBuilder => SortBy; - - /// - protected internal override void CellContent(RenderTreeBuilder builder, TGridItem item) - => builder.AddContent(0, ChildContent(item)); - - /// - protected override bool IsSortableByDefault() - => SortBy is not null; -} - -/// -/// Backward-compatible alias for , kept so existing -/// Razor markup that referenced the old BitDataGridTemplateColumn<TGridItem> component keeps -/// compiling. It inherits the renamed column, so it behaves identically while remaining discoverable -/// alongside during the migration window. -/// -/// The type of data represented by each row in the grid. -[Obsolete("BitDataGridTemplateColumn has been renamed to BitQuickGridTemplateColumn. Use BitQuickGridTemplateColumn instead.")] -public class BitDataGridTemplateColumn : BitQuickGridTemplateColumn -{ -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/IBitQuickGridSortBuilderColumn.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/IBitQuickGridSortBuilderColumn.cs deleted file mode 100644 index fee3035a48..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Columns/IBitQuickGridSortBuilderColumn.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Bit.BlazorUI; - -/// -/// An interface that, if implemented by a subclass, allows a -/// to understand the sorting rules associated with that column. -/// -/// If a subclass does not implement this, that column can still be marked as sortable and can -/// be the current sort column, but its sorting logic cannot be applied to the data queries automatically. The developer would be -/// responsible for implementing that sorting logic separately inside their . -/// -/// The type of data represented by each row in the grid. -public interface IBitQuickGridSortBuilderColumn -{ - /// - /// Gets the sorting rules associated with the column. - /// - public BitQuickGridSort? SortBuilder { get; } -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs deleted file mode 100644 index 46afc5dfd1..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/AsyncQueryExecutorSupplier.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.Collections.Concurrent; -using System.Diagnostics.CodeAnalysis; -using Microsoft.Extensions.DependencyInjection; - -namespace Bit.BlazorUI; - -internal static class AsyncQueryExecutorSupplier -{ - // The primary goal with this is to ensure that: - // - If you're using EF Core, then we resolve queries efficiently using its ToXyzAsync async extensions and don't - // just fall back on the synchronous IQueryable ToXyz calls - // - ... but without BitQuickGrid referencing Microsoft.EntityFramework directly. That's because it would bring in - // heavy dependencies you may not be using (and relying on trimming isn't enough, as it's still desirable to have - // heavy unused dependencies for Blazor Server). - // - // As a side-effect, we have an abstraction IAsyncQueryExecutor that developers could use to plug in their own - // mechanism for resolving async queries from other data sources than EF. It's not really a major goal to make this - // adapter generally useful beyond EF, but fine if people do have their own uses for it. - - private static readonly ConcurrentDictionary IsEntityFrameworkProviderTypeCache = new(); - - [SuppressMessage("Trimming", "IL2111:Method with parameters or return value with `DynamicallyAccessedMembersAttribute` is accessed via reflection. Trimmer can't guarantee availability of the requirements of the method.", Justification = "")] - public static IAsyncQueryExecutor? GetAsyncQueryExecutor(IServiceProvider services, IQueryable? queryable) - { - if (queryable is not null) - { - // Inspect every registered executor, not just the first one resolved: a registered executor - // that does not support this queryable must not shadow another that does, nor suppress the - // EF misconfiguration warning below. Keep scanning and return the *last* supported executor - // so later, more specific registrations override earlier generic ones - mirroring how DI - // resolves a single service (last registration wins). - IAsyncQueryExecutor? selected = null; - foreach (var executor in services.GetServices()) - { - if (executor.IsSupported(queryable)) - { - selected = executor; - } - } - - if (selected is not null) - { - return selected; - } - - // No registered executor supports this queryable. It's useful to detect if the developer is - // unaware that they should register an IAsyncQueryExecutor, otherwise they will likely never - // notice and simply deploy an inefficient app that blocks threads on each query. - var providerType = queryable.Provider?.GetType(); - if (providerType is not null && IsEntityFrameworkProviderTypeCache.GetOrAdd(providerType, IsEntityFrameworkProviderType)) - { - throw new InvalidOperationException( - $"The supplied {nameof(IQueryable)} is provided by Entity Framework. To query it efficiently without blocking threads, " + - $"register an implementation of {nameof(IAsyncQueryExecutor)} in your service collection that wraps EF Core's async query APIs " + - $"(for example ToArrayAsync/CountAsync) and reports IsSupported(queryable) == true for EF queryables. " + - $"Alternatively, supply non-EF data via the Items or ItemsProvider parameters."); - } - } - - return null; - } - - // We have to do this via reflection because the whole point is to avoid any static dependency on EF unless you - // reference the adapter. Trimming won't cause us any problems because this is only a way of detecting misconfiguration - // so it's sufficient if it can detect the misconfiguration in development. - private static bool IsEntityFrameworkProviderType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type queryableProviderType) - => queryableProviderType.GetInterfaces().Any(x => string.Equals(x.FullName, "Microsoft.EntityFrameworkCore.Query.IAsyncQueryProvider")) == true; -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridColumnsCollectedNotifier.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridColumnsCollectedNotifier.cs deleted file mode 100644 index 8f31d94e1c..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridColumnsCollectedNotifier.cs +++ /dev/null @@ -1,66 +0,0 @@ -namespace Bit.BlazorUI; - -// One awkwardness of the way BitQuickGrid collects its list of child columns is that, during OnParametersSetAsync, -// it only knows about the set of columns that were present on the *previous* render. If it's going to trigger a -// data load during OnParametersSetAsync, that operation can't depend on the current set of columns as it might -// have changed, or might even still be empty (i.e., on the first render). -// -// Ways this could be resolved: -// -// - In the future, we could implement the long-wanted feature of being able to query the contents of a RenderFragment -// separately from rendering. Then the whole trick of collection-during-rendering would not be needed. -// - Or, we could factor out most of BitQuickGrid's internals into some new component BitQuickGridCore. The parent component, -// BitQuickGrid, would then only be responsible for collecting columns followed by rendering BitQuickGridCore. So each time -// BitQuickGridCore renders, we'd already have the latest set of columns -// - Drawback: since BitQuickGrid has public API, it's much messier to have to forward all of that to some new child type. -// - However, this is arguably the most correct solution in general (at least until option 1 above is implemented) -// - Or, we could decide it's enough to fix this on the first render (since that's the only time we're going to guarantee -// to apply a default sort order), and then as a special case put in some extra component in the render flow that raises -// an event once the columns are first collected. -// - This is relatively simple and non-disruptive, though it doesn't cover cases where queries need to be delayed until -// after a dynamically-added column is added -// -// The final option is what's implemented here. We send the notification via EventCallbackSubscribable so that the async -// operation and re-rendering follows normal semantics without us having to call StateHasChanged or think about exceptions. - -/// -/// For internal use only. Do not use. -/// -/// For internal use only. Do not use. -public class BitQuickGridColumnsCollectedNotifier : IComponent -{ - private bool _isFirstRender = true; - - [CascadingParameter] internal InternalGridContext InternalGridContext { get; set; } = default!; - - public void Attach(RenderHandle renderHandle) - { - // This component never renders, so we can ignore the renderHandle - } - - public Task SetParametersAsync(ParameterView parameters) - { - if (_isFirstRender) - { - _isFirstRender = false; - parameters.SetParameterProperties(this); - return InternalGridContext.ColumnsFirstCollected.InvokeCallbacksAsync(null); - } - else - { - return Task.CompletedTask; - } - } -} - -/// -/// For internal use only. Do not use. -/// Backward-compatible alias for , kept so -/// existing code referencing the old BitDataGridColumnsCollectedNotifier<TGridItem> type keeps -/// compiling. It inherits the renamed notifier, preserving the same behavior. -/// -/// For internal use only. Do not use. -[Obsolete("BitDataGridColumnsCollectedNotifier has been renamed to BitQuickGridColumnsCollectedNotifier. Use BitQuickGridColumnsCollectedNotifier instead.")] -public class BitDataGridColumnsCollectedNotifier : BitQuickGridColumnsCollectedNotifier -{ -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridDefer.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridDefer.cs deleted file mode 100644 index db5deafc63..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/BitQuickGridDefer.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Bit.BlazorUI; - -// This is used by BitQuickGrid to move its body rendering to the end of the render queue so we can collect -// the list of child columns first. It has to be public only because it's used from .razor logic. - -/// -/// For internal use only. Do not use. -/// -public class BitQuickGridDefer : ComponentBase -{ - /// - /// For internal use only. Do not use. - /// - [Parameter] public RenderFragment? ChildContent { get; set; } - - /// - /// For internal use only. Do not use. - /// - protected override void BuildRenderTree(RenderTreeBuilder builder) - { - builder.AddContent(0, ChildContent); - } -} - -/// -/// Backward-compatible alias for , kept so existing code that referenced -/// the old BitDataGridDefer type keeps compiling. It inherits the renamed type, so it behaves -/// identically during the migration window. -/// -[Obsolete("BitDataGridDefer has been renamed to BitQuickGridDefer. Use BitQuickGridDefer instead.")] -public class BitDataGridDefer : BitQuickGridDefer -{ -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscribable.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscribable.cs deleted file mode 100644 index 3e54e76c25..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscribable.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Bit.BlazorUI; - -/// -/// Represents an event that you may subscribe to. This differs from normal C# events in that the handlers -/// are EventCallback, and so may have async behaviors and cause component re-rendering -/// while retaining error flow. -/// -/// A type for the eventargs. -internal class EventCallbackSubscribable -{ - private readonly Dictionary, EventCallback> _callbacks = new(); - - /// - /// Invokes all the registered callbacks sequentially, in an undefined order. - /// - public async Task InvokeCallbacksAsync(T eventArg) - { - foreach (var callback in _callbacks.Values) - { - await callback.InvokeAsync(eventArg); - } - } - - // Don't call this directly - it gets called by EventCallbackSubscription - public void Subscribe(EventCallbackSubscriber owner, EventCallback callback) - => _callbacks.Add(owner, callback); - - // Don't call this directly - it gets called by EventCallbackSubscription - public void Unsubscribe(EventCallbackSubscriber owner) - => _callbacks.Remove(owner); -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscriber.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscriber.cs deleted file mode 100644 index 7dfe8827d1..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventCallbackSubscriber.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace Bit.BlazorUI; - -/// -/// Represents a subscriber that may be subscribe to an . -/// The subscription can move between instances over time, -/// and automatically unsubscribes from earlier instances -/// whenever it moves to a new one. -/// -internal class EventCallbackSubscriber : IDisposable -{ - private readonly EventCallback _handler; - private EventCallbackSubscribable? _existingSubscription; - - public EventCallbackSubscriber(EventCallback handler) - { - _handler = handler; - } - - /// - /// Creates a subscription on the , or moves any existing subscription to it - /// by first unsubscribing from the previous . - /// - /// If the supplied is null, no new subscription will be created, but any - /// existing one will still be unsubscribed. - /// - /// - public void SubscribeOrMove(EventCallbackSubscribable? subscribable) - { - if (subscribable != _existingSubscription) - { - _existingSubscription?.Unsubscribe(this); - subscribable?.Subscribe(this, _handler); - _existingSubscription = subscribable; - } - } - - public void Dispose() - { - _existingSubscription?.Unsubscribe(this); - } -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventHandlers.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventHandlers.cs deleted file mode 100644 index 22482feff7..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/EventHandlers.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Bit.BlazorUI; - -/// -/// Configures event handlers for . -/// -[EventHandler("onclosecolumnoptions", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: true)] -public static class EventHandlers -{ -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/IAsyncQueryExecutor.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/IAsyncQueryExecutor.cs deleted file mode 100644 index 62cc08de3e..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/IAsyncQueryExecutor.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Bit.BlazorUI; - -/// -/// Provides methods for asynchronous evaluation of queries against an . -/// -public interface IAsyncQueryExecutor -{ - /// - /// Determines whether the is supported by this type. - /// - /// The data type. - /// An instance. - /// True if this instance can perform asynchronous queries for the supplied , otherwise false. - bool IsSupported(IQueryable queryable); - - /// - /// Asynchronously counts the items in the , if supported. - /// - /// The data type. - /// An instance. - /// The number of items in .. - Task CountAsync(IQueryable queryable); - - /// - /// Asynchronously materializes the as an array, if supported. - /// - /// The data type. - /// An instance. - /// The items in the .. - Task ToArrayAsync(IQueryable queryable); -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/InternalGridContext.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/InternalGridContext.cs deleted file mode 100644 index 1c66112664..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Infrastructure/InternalGridContext.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Bit.BlazorUI; - -// The grid cascades this so that descendant columns can talk back to it. It's an internal type -// so that it doesn't show up by mistake in unrelated components. -internal class InternalGridContext -{ - public BitQuickGrid Grid { get; } - public EventCallbackSubscribable ColumnsFirstCollected { get; } = new(); - - public InternalGridContext(BitQuickGrid grid) - { - Grid = grid; - } -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProvider.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProvider.cs deleted file mode 100644 index 6985a54e8a..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Bit.BlazorUI; - -/// -/// A callback that provides data for a . -/// -/// The type of data represented by each row in the grid. -/// Parameters describing the data being requested. -/// A (specifically ValueTask<BitQuickGridItemsProviderResult<TGridItem>>) whose result is a that gives the data to be displayed. -public delegate ValueTask> BitQuickGridItemsProvider(BitQuickGridItemsProviderRequest request); diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderRequest.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderRequest.cs deleted file mode 100644 index 471b66baf5..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderRequest.cs +++ /dev/null @@ -1,91 +0,0 @@ -namespace Bit.BlazorUI; - -/// -/// Parameters for data to be supplied by a 's . -/// -/// The type of data represented by each row in the grid. -public struct BitQuickGridItemsProviderRequest -{ - /// - /// The zero-based index of the first item to be supplied. - /// - public int StartIndex { get; } - - /// - /// If set, the maximum number of items to be supplied. If not set, the maximum number is unlimited. - /// - public int? Count { get; } - - /// - /// Specifies which column represents the sort order. - /// - /// Rather than inferring the sort rules manually, you should normally call either - /// or , since they also account for and automatically. - /// - public BitQuickGridColumnBase? SortByColumn { get; } - - /// - /// Specifies the current sort direction. - /// - /// Rather than inferring the sort rules manually, you should normally call either - /// or , since they also account for and automatically. - /// - public bool SortByAscending { get; } - - /// - /// A token that indicates if the request should be cancelled. - /// - public CancellationToken CancellationToken { get; } - - internal BitQuickGridItemsProviderRequest( - int startIndex, int? count, BitQuickGridColumnBase? sortByColumn, bool sortByAscending, - CancellationToken cancellationToken) - { - StartIndex = startIndex; - Count = count; - SortByColumn = sortByColumn; - SortByAscending = sortByAscending; - CancellationToken = cancellationToken; - } - - /// - /// Applies the request's sorting rules to the supplied . - /// - /// Note that this only works if the current implements - /// and exposes a non-null sort builder. If the column does not implement that interface, or implements it - /// but its sort builder is null (as with ), it will throw. - /// - /// An . - /// A new representing the with sorting rules applied. - public IQueryable ApplySorting(IQueryable source) => SortByColumn switch - { - // A sort-builder column with a null SortBuilder cannot apply its sort; treat it as unsupported - // (like a non-sort-builder column) rather than silently returning the unsorted source, which - // would hide an active sort on e.g. BitQuickGridTemplateColumn. - IBitQuickGridSortBuilderColumn { SortBuilder: { } sortBuilder } => sortBuilder.Apply(source, SortByAscending), - null => source, - _ => throw new NotSupportedException(ColumnNotSortableMessage(SortByColumn)), - }; - - /// - /// Produces a collection of (property name, direction) pairs representing the sorting rules. - /// - /// Note that this only works if the current implements - /// and exposes a non-null sort builder. If the column does not implement that interface, or implements it - /// but its sort builder is null (as with ), it will throw. - /// - /// A collection of (property name, direction) pairs representing the sorting rules - public IReadOnlyCollection<(string PropertyName, BitQuickGridSortDirection Direction)> GetSortByProperties() => SortByColumn switch - { - // Mirror ApplySorting: a null SortBuilder on a sort-builder column is unsupported rather than - // an empty (no-op) sort, so the caller isn't misled into thinking there is no active sort. - IBitQuickGridSortBuilderColumn { SortBuilder: { } sortBuilder } => sortBuilder.ToPropertyList(SortByAscending), - null => Array.Empty<(string, BitQuickGridSortDirection)>(), - _ => throw new NotSupportedException(ColumnNotSortableMessage(SortByColumn)), - }; - - private static string ColumnNotSortableMessage(BitQuickGridColumnBase col) - => col is IBitQuickGridSortBuilderColumn - ? $"The current sort column '{col.GetType().FullName}' implements {nameof(IBitQuickGridSortBuilderColumn)} but its {nameof(IBitQuickGridSortBuilderColumn.SortBuilder)} is null, so its sorting rules cannot be applied automatically." - : $"The current sort column is of type '{col.GetType().FullName}', which does not implement {nameof(IBitQuickGridSortBuilderColumn)}, so its sorting rules cannot be applied automatically."; -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderResult.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderResult.cs deleted file mode 100644 index 1a778dce57..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/ItemsProvider/BitQuickGridItemsProviderResult.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace Bit.BlazorUI; - -/// -/// Holds data being supplied to a 's . -/// -/// The type of data represented by each row in the grid. -public struct BitQuickGridItemsProviderResult -{ - /// - /// The items being supplied. - /// - public ICollection Items { get; set; } - - /// - /// The total number of items that may be displayed in the grid. This normally means the total number of items in the - /// underlying data source after applying any filtering that is in effect. - /// - /// If the grid is paginated, this should include all pages. If the grid is virtualized, this should include the entire scroll range. - /// - public int TotalItemCount { get; set; } - - /// - /// Constructs an instance of . - /// - /// The items being supplied. - /// The total number of items that exist. See for details. - public BitQuickGridItemsProviderResult(ICollection items, int totalItemCount) - { - Items = items; - TotalItemCount = totalItemCount; - } -} - -/// -/// Provides convenience methods for constructing instances. -/// -public static class BitQuickGridItemsProviderResult -{ - // This is just to provide generic type inference, so you don't have to specify TGridItem yet again. - - /// - /// Supplies an instance of . - /// - /// The type of data represented by each row in the grid. - /// The items being supplied. - /// The total number of items that exist. See for details. - /// An instance of . - public static BitQuickGridItemsProviderResult From(ICollection items, int totalItemCount) => new(items, totalItemCount); -} - -/// -/// Backward-compatible alias for , kept so code -/// that constructed the old BitDataGridItemsProviderResult<TGridItem> keeps compiling. An -/// implicit conversion lets it be returned where a -/// is expected. -/// -/// The type of data represented by each row in the grid. -[Obsolete("BitDataGridItemsProviderResult has been renamed to BitQuickGridItemsProviderResult. Use BitQuickGridItemsProviderResult instead.")] -public struct BitDataGridItemsProviderResult -{ - /// The items being supplied. - public ICollection Items { get; set; } - - /// The total number of items that may be displayed in the grid. - public int TotalItemCount { get; set; } - - /// Constructs an instance of . - /// The items being supplied. - /// The total number of items that exist. - public BitDataGridItemsProviderResult(ICollection items, int totalItemCount) - { - Items = items; - TotalItemCount = totalItemCount; - } - - /// Implicitly converts to the current type. - public static implicit operator BitQuickGridItemsProviderResult(BitDataGridItemsProviderResult result) - => new(result.Items, result.TotalItemCount); -} - -/// -/// Backward-compatible alias for , kept so existing calls to -/// the old BitDataGridItemsProviderResult.From(...) helper keep compiling. The helper forwards to -/// , which the grid's ItemsProvider now expects. -/// -[Obsolete("BitDataGridItemsProviderResult has been renamed to BitQuickGridItemsProviderResult. Use BitQuickGridItemsProviderResult instead.")] -public static class BitDataGridItemsProviderResult -{ - /// - // Returns the old alias type so callers still assigning to BitDataGridItemsProviderResult - // keep compiling; the implicit conversion above bridges to BitQuickGridItemsProviderResult - // wherever the grid's ItemsProvider expects the renamed type. - public static BitDataGridItemsProviderResult From(ICollection items, int totalItemCount) => new(items, totalItemCount); -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginationState.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginationState.cs deleted file mode 100644 index 122ac7bb51..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginationState.cs +++ /dev/null @@ -1,87 +0,0 @@ -namespace Bit.BlazorUI; - -/// -/// Holds state to represent pagination in a . -/// -public class BitQuickGridPaginationState -{ - /// - /// Gets the current zero-based page index. To set it, call . - /// - public int CurrentPageIndex { get; private set; } - - /// - /// Gets or sets the number of items on each page. - /// - public int ItemsPerPage { get; set; } = 10; - - /// - /// Gets the zero-based index of the last page, if known. The value will be null until is known. - /// - public int? LastPageIndex => (TotalItemCount - 1) / ItemsPerPage; - - /// - /// Gets the total number of items across all pages, if known. The value will be null until an - /// associated assigns a value after loading data. - /// - public int? TotalItemCount { get; private set; } - - /// - /// An event that is raised when the total item count has changed. - /// - public event EventHandler? TotalItemCountChanged; - - internal EventCallbackSubscribable CurrentPageItemsChanged { get; } = new(); - internal EventCallbackSubscribable TotalItemCountChangedSubscribable { get; } = new(); - - /// - public override int GetHashCode() - => HashCode.Combine(ItemsPerPage, CurrentPageIndex, TotalItemCount); - - /// - /// Sets the current page index, and notifies any associated - /// to fetch and render updated data. - /// - /// The new, zero-based page index. - /// A representing the completion of the operation. - public Task SetCurrentPageIndexAsync(int pageIndex) - { - CurrentPageIndex = pageIndex; - return CurrentPageItemsChanged.InvokeCallbacksAsync(this); - } - - // Can be internal because this only needs to be called by BitQuickGrid itself, not any custom pagination UI components. - internal Task SetTotalItemCountAsync(int totalItemCount) - { - if (totalItemCount == TotalItemCount) - { - return Task.CompletedTask; - } - - TotalItemCount = totalItemCount; - - if (CurrentPageIndex > 0 && CurrentPageIndex > LastPageIndex) - { - // If the number of items has reduced such that the current page index is no longer valid, move - // automatically to the final valid page index and trigger a further data load. - return SetCurrentPageIndexAsync(LastPageIndex.Value); - } - else - { - // Under normal circumstances, we just want any associated pagination UI to update - TotalItemCountChanged?.Invoke(this, TotalItemCount); - return TotalItemCountChangedSubscribable.InvokeCallbacksAsync(this); - } - } -} - -/// -/// Backward-compatible alias for , kept so existing code that -/// constructed the old BitDataGridPaginationState type keeps compiling. It inherits the renamed -/// type, so it behaves identically and can still be assigned to a grid's pagination during the -/// migration window. -/// -[Obsolete("BitDataGridPaginationState has been renamed to BitQuickGridPaginationState. Use BitQuickGridPaginationState instead.")] -public class BitDataGridPaginationState : BitQuickGridPaginationState -{ -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor deleted file mode 100644 index 788a8e9f29..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor +++ /dev/null @@ -1,61 +0,0 @@ -@namespace Bit.BlazorUI - -
- @if (Value.TotalItemCount.HasValue) - { -
- @if (SummaryTemplate is not null) - { - @SummaryTemplate(Value) - } - else if (SummaryFormat is not null) - { - @SummaryFormat(Value) - } - else - { - @Value.TotalItemCount items - } -
- - } -
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs deleted file mode 100644 index ef8d07cb2c..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.razor.cs +++ /dev/null @@ -1,86 +0,0 @@ -namespace Bit.BlazorUI; - -/// -/// A component that provides a user interface for . -/// -public partial class BitQuickGridPaginator : IDisposable -{ - private readonly EventCallbackSubscriber _totalItemCountChanged; - - /// - /// The title of the go to first page button. - /// - [Parameter] public string GoToFirstButtonTitle { get; set; } = "Go to first page"; - - /// - /// The title of the go to previous page button. - /// - [Parameter] public string GoToPrevButtonTitle { get; set; } = "Go to previous page"; - - /// - /// The title of the go to next page button. - /// - [Parameter] public string GoToNextButtonTitle { get; set; } = "Go to next page"; - - /// - /// The title of the go to last page button. - /// - [Parameter] public string GoToLastButtonTitle { get; set; } = "Go to last page"; - - /// - /// Optionally supplies a format for rendering the page count summary. - /// - [Parameter] public Func? SummaryFormat { get; set; } - - /// - /// Optionally supplies a template for rendering the page count summary. - /// - [Parameter] public RenderFragment? SummaryTemplate { get; set; } - - /// - /// The optional custom format for the main text of the paginator in the middle of it. - /// - [Parameter] public Func? TextFormat { get; set; } - - /// - /// The optional custom template for the main text of the paginator in the middle of it. - /// - [Parameter] public RenderFragment? TextTemplate { get; set; } - - /// - /// Specifies the associated . This parameter is required. - /// - [Parameter, EditorRequired] public BitQuickGridPaginationState Value { get; set; } = default!; - - /// - /// Constructs an instance of . - /// - public BitQuickGridPaginator() - { - // The "total item count" handler doesn't need to do anything except cause this component to - // re-render. Invoking this EventCallback already routes through the paginator's - // IHandleEvent.HandleEventAsync (the receiver is `this`), which re-renders the component on its - // own, so the callback body is intentionally empty - calling StateHasChanged() here as well - // would queue a second, redundant render. - _totalItemCountChanged = new(EventCallback.Factory.Create(this, () => { })); - } - - private Task GoFirstAsync() => GoToPageAsync(0); - private Task GoPreviousAsync() => GoToPageAsync(Value.CurrentPageIndex - 1); - private Task GoNextAsync() => GoToPageAsync(Value.CurrentPageIndex + 1); - private Task GoLastAsync() => GoToPageAsync(Value.LastPageIndex.GetValueOrDefault(0)); - - private bool CanGoBack => Value.CurrentPageIndex > 0; - private bool CanGoForwards => Value.CurrentPageIndex < Value.LastPageIndex; - - private Task GoToPageAsync(int pageIndex) - => Value.SetCurrentPageIndexAsync(pageIndex); - - /// - protected override void OnParametersSet() - => _totalItemCountChanged.SubscribeOrMove(Value.TotalItemCountChangedSubscribable); - - /// - public void Dispose() - => _totalItemCountChanged.Dispose(); -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.scss b/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.scss deleted file mode 100644 index 5d72b85b01..0000000000 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/QuickGrid/Pagination/BitQuickGridPaginator.scss +++ /dev/null @@ -1,59 +0,0 @@ -.bit-dgp { - display: flex; - flex-wrap: wrap; - padding: 0.25rem; - align-items: center; -} - -.bit-dgp-txt { - margin: 0 0.5rem; - text-align: center; -} - -.bit-dgp-nav { - gap: 0.5rem; - display: flex; - align-items: center; - margin-inline-start: auto; - - button { - border: 0; - width: 2rem; - height: 2rem; - background: none center center / 1rem no-repeat; - - &[disabled] { - opacity: 0.4; - } - - &:not([disabled]):hover { - background-color: var(--bit-clr-bg-pri-hover); - } - - &:not([disabled]):active { - background-color: var(--bit-clr-bg-pri-active); - } - } -} - -.bit-dgp-fst::before, -.bit-dgp-lst::before { - content: "\F371"; - font-style: normal; - font-weight: normal; - display: inline-block; - font-family: 'Fabric MDL2 bit BlazorUI Extras'; -} - -.bit-dgp-prv::before, -.bit-dgp-nex::before { - content: "\E96F"; - font-style: normal; - font-weight: normal; - display: inline-block; - font-family: 'Fabric MDL2 bit BlazorUI Extras'; -} - -.bit-dgp-nex, .bit-dgp-lst { - transform: scaleX(-1); -} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss b/src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss index da4dc51496..564e1a6a38 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss @@ -5,8 +5,6 @@ @import "../Components/Flag/BitFlag.scss"; @import "../Components/FullCalendar/BitFullCalendar.scss"; @import "../Components/InfiniteScrolling/BitInfiniteScrolling.scss"; -@import "../Components/QuickGrid/BitQuickGrid.scss"; -@import "../Components/QuickGrid/Pagination/BitQuickGridPaginator.scss"; @import "../Components/Map/BitMap.scss"; @import "../Components/MarkdownEditor/BitMarkdownEditor.scss"; @import "../Components/MarkdownViewer/BitMarkdownViewer.scss"; diff --git a/src/BlazorUI/Bit.BlazorUI.Legacy/Components/DataGrid/BitDataGridLegacy.scss b/src/BlazorUI/Bit.BlazorUI.Legacy/Components/DataGrid/BitDataGridLegacy.scss index 1109021ff7..b1e5bdcc3b 100644 --- a/src/BlazorUI/Bit.BlazorUI.Legacy/Components/DataGrid/BitDataGridLegacy.scss +++ b/src/BlazorUI/Bit.BlazorUI.Legacy/Components/DataGrid/BitDataGridLegacy.scss @@ -1,3 +1,5 @@ +@import "../../../Bit.BlazorUI/Styles/functions.scss"; + .bit-qkg { width: 100%; --bit-qkg-col-gap: 1rem; @@ -34,8 +36,8 @@ border: 1px solid; position: absolute; inset-inline-start: 0; - border-color: var(--bit-clr-brd-pri); - background-color: var(--bit-clr-bg-sec); + border-color: $clr-brd-pri; + background-color: $clr-bg-sec; } .bit-qkg-cob { @@ -65,8 +67,8 @@ inset-block-end: 5px; inset-block-start: 5px; inset-inline-start: 0.5rem; - border-color: var(--bit-clr-brd-pri); - border-inline-start: 1px solid var(--bit-clr-brd-pri); + border-color: $clr-brd-pri; + border-inline-start: 1px solid $clr-brd-pri; } } diff --git a/src/BlazorUI/Bit.BlazorUI.Legacy/Components/DataGrid/Pagination/BitDataGridLegacyPaginator.scss b/src/BlazorUI/Bit.BlazorUI.Legacy/Components/DataGrid/Pagination/BitDataGridLegacyPaginator.scss index 5d72b85b01..9e9731ad8d 100644 --- a/src/BlazorUI/Bit.BlazorUI.Legacy/Components/DataGrid/Pagination/BitDataGridLegacyPaginator.scss +++ b/src/BlazorUI/Bit.BlazorUI.Legacy/Components/DataGrid/Pagination/BitDataGridLegacyPaginator.scss @@ -1,3 +1,5 @@ +@import "../../../../Bit.BlazorUI/Styles/functions.scss"; + .bit-dgp { display: flex; flex-wrap: wrap; @@ -27,11 +29,11 @@ } &:not([disabled]):hover { - background-color: var(--bit-clr-bg-pri-hover); + background-color: $clr-bg-pri-hover; } &:not([disabled]):active { - background-color: var(--bit-clr-bg-pri-active); + background-color: $clr-bg-pri-active; } } } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButton.scss b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButton.scss index 6230d430e6..a69720e736 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButton.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButton.scss @@ -27,16 +27,16 @@ } &:focus-visible { - outline: none; + @include focus-ring(var(--bit-acb-clr-focus)); + border-radius: $shp-border-radius; - box-shadow: 0 0 0 spacing(0.25) var(--bit-acb-clr-hover); } &.bit-dis { cursor: default; - color: $clr-fg-dis; pointer-events: none; - --bit-acb-clr-ico: #{$clr-fg-dis}; + color: var(--bit-acb-clr-dis-text); + --bit-acb-clr-ico: var(--bit-acb-clr-dis-text); } } @@ -137,48 +137,64 @@ --bit-acb-clr-ico: #{$clr-pri}; --bit-acb-clr-hover: #{$clr-pri-hover}; --bit-acb-clr-active: #{$clr-pri-active}; + --bit-acb-clr-focus: #{$clr-pri-focus}; + --bit-acb-clr-dis-text: #{$clr-pri-dis-text}; } .bit-acb-sec { --bit-acb-clr-ico: #{$clr-sec}; --bit-acb-clr-hover: #{$clr-sec-hover}; --bit-acb-clr-active: #{$clr-sec-active}; + --bit-acb-clr-focus: #{$clr-sec-focus}; + --bit-acb-clr-dis-text: #{$clr-sec-dis-text}; } .bit-acb-ter { --bit-acb-clr-ico: #{$clr-ter}; --bit-acb-clr-hover: #{$clr-ter-hover}; --bit-acb-clr-active: #{$clr-ter-active}; + --bit-acb-clr-focus: #{$clr-ter-focus}; + --bit-acb-clr-dis-text: #{$clr-ter-dis-text}; } .bit-acb-inf { --bit-acb-clr-ico: #{$clr-inf}; --bit-acb-clr-hover: #{$clr-inf-hover}; --bit-acb-clr-active: #{$clr-inf-active}; + --bit-acb-clr-focus: #{$clr-inf-focus}; + --bit-acb-clr-dis-text: #{$clr-inf-dis-text}; } .bit-acb-suc { --bit-acb-clr-ico: #{$clr-suc}; --bit-acb-clr-hover: #{$clr-suc-hover}; --bit-acb-clr-active: #{$clr-suc-active}; + --bit-acb-clr-focus: #{$clr-suc-focus}; + --bit-acb-clr-dis-text: #{$clr-suc-dis-text}; } .bit-acb-wrn { --bit-acb-clr-ico: #{$clr-wrn}; --bit-acb-clr-hover: #{$clr-wrn-hover}; --bit-acb-clr-active: #{$clr-wrn-active}; + --bit-acb-clr-focus: #{$clr-wrn-focus}; + --bit-acb-clr-dis-text: #{$clr-wrn-dis-text}; } .bit-acb-swr { --bit-acb-clr-ico: #{$clr-swr}; --bit-acb-clr-hover: #{$clr-swr-hover}; --bit-acb-clr-active: #{$clr-swr-active}; + --bit-acb-clr-focus: #{$clr-swr-focus}; + --bit-acb-clr-dis-text: #{$clr-swr-dis-text}; } .bit-acb-err { --bit-acb-clr-ico: #{$clr-err}; --bit-acb-clr-hover: #{$clr-err-hover}; --bit-acb-clr-active: #{$clr-err-active}; + --bit-acb-clr-focus: #{$clr-err-focus}; + --bit-acb-clr-dis-text: #{$clr-err-dis-text}; } @@ -186,54 +202,72 @@ --bit-acb-clr-ico: #{$clr-bg-pri}; --bit-acb-clr-hover: #{$clr-bg-pri-hover}; --bit-acb-clr-active: #{$clr-bg-pri-active}; + --bit-acb-clr-focus: #{$clr-bg-pri-focus}; + --bit-acb-clr-dis-text: #{$clr-bg-pri-dis-text}; } .bit-acb-sbg { --bit-acb-clr-ico: #{$clr-bg-sec}; --bit-acb-clr-hover: #{$clr-bg-sec-hover}; --bit-acb-clr-active: #{$clr-bg-sec-active}; + --bit-acb-clr-focus: #{$clr-bg-sec-focus}; + --bit-acb-clr-dis-text: #{$clr-bg-sec-dis-text}; } .bit-acb-tbg { --bit-acb-clr-ico: #{$clr-bg-ter}; --bit-acb-clr-hover: #{$clr-bg-ter-hover}; --bit-acb-clr-active: #{$clr-bg-ter-active}; + --bit-acb-clr-focus: #{$clr-bg-ter-focus}; + --bit-acb-clr-dis-text: #{$clr-bg-ter-dis-text}; } .bit-acb-pfg { --bit-acb-clr-ico: #{$clr-fg-pri}; --bit-acb-clr-hover: #{$clr-fg-pri-hover}; --bit-acb-clr-active: #{$clr-fg-pri-active}; + --bit-acb-clr-focus: #{$clr-fg-pri-focus}; + --bit-acb-clr-dis-text: #{$clr-fg-pri-dis-text}; } .bit-acb-sfg { --bit-acb-clr-ico: #{$clr-fg-sec}; --bit-acb-clr-hover: #{$clr-fg-sec-hover}; --bit-acb-clr-active: #{$clr-fg-sec-active}; + --bit-acb-clr-focus: #{$clr-fg-sec-focus}; + --bit-acb-clr-dis-text: #{$clr-fg-sec-dis-text}; } .bit-acb-tfg { --bit-acb-clr-ico: #{$clr-fg-ter}; --bit-acb-clr-hover: #{$clr-fg-ter-hover}; --bit-acb-clr-active: #{$clr-fg-ter-active}; + --bit-acb-clr-focus: #{$clr-fg-ter-focus}; + --bit-acb-clr-dis-text: #{$clr-fg-ter-dis-text}; } .bit-acb-pbr { --bit-acb-clr-ico: #{$clr-brd-pri}; --bit-acb-clr-hover: #{$clr-brd-pri-hover}; --bit-acb-clr-active: #{$clr-brd-pri-active}; + --bit-acb-clr-focus: #{$clr-brd-pri-focus}; + --bit-acb-clr-dis-text: #{$clr-brd-pri-dis-text}; } .bit-acb-sbr { --bit-acb-clr-ico: #{$clr-brd-sec}; --bit-acb-clr-hover: #{$clr-brd-sec-hover}; --bit-acb-clr-active: #{$clr-brd-sec-active}; + --bit-acb-clr-focus: #{$clr-brd-sec-focus}; + --bit-acb-clr-dis-text: #{$clr-brd-sec-dis-text}; } .bit-acb-tbr { --bit-acb-clr-ico: #{$clr-brd-ter}; --bit-acb-clr-hover: #{$clr-brd-ter-hover}; --bit-acb-clr-active: #{$clr-brd-ter-active}; + --bit-acb-clr-focus: #{$clr-brd-ter-focus}; + --bit-acb-clr-dis-text: #{$clr-brd-ter-dis-text}; } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/Button/BitButton.scss b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/Button/BitButton.scss index dec33a8317..ab0cdc1b65 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/Button/BitButton.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/Button/BitButton.scss @@ -17,18 +17,14 @@ border-width: $shp-border-width; border-style: $shp-border-style; border-radius: $shp-border-radius; - --bit-btn-clr-bg-dis: #{$clr-bg-dis}; - --bit-btn-clr-brd-dis: #{$clr-brd-dis}; --bit-btn-float-offset: #{spacing(2)}; &:focus-visible { - outline-offset: spacing(0.25); - outline: spacing(0.25) solid var(--bit-btn-clr); + @include focus-ring(var(--bit-btn-clr-focus)); } &.bit-dis { cursor: default; - color: $clr-fg-dis; pointer-events: none; } @@ -170,8 +166,9 @@ } &.bit-dis { - border-color: var(--bit-btn-clr-brd-dis); - background-color: var(--bit-btn-clr-bg-dis); + color: var(--bit-btn-clr-dis-text); + border-color: var(--bit-btn-clr-dis); + background-color: var(--bit-btn-clr-dis); } } @@ -193,7 +190,9 @@ } &.bit-dis { - border-color: var(--bit-btn-clr-brd-dis); + color: var(--bit-btn-clr-dis-text); + border-color: var(--bit-btn-clr-dis); + background-color: transparent; } } @@ -213,6 +212,12 @@ color: var(--bit-btn-clr-txt); background-color: var(--bit-btn-clr-active); } + + &.bit-dis { + color: var(--bit-btn-clr-dis-text); + border-color: transparent; + background-color: transparent; + } } @@ -221,7 +226,10 @@ --bit-btn-clr-txt: #{$clr-pri-text}; --bit-btn-clr-hover: #{$clr-pri-hover}; --bit-btn-clr-active: #{$clr-pri-active}; + --bit-btn-clr-focus: #{$clr-pri-focus}; --bit-btn-clr-spn: #{$clr-pri-dark}; + --bit-btn-clr-dis: #{$clr-pri-dis}; + --bit-btn-clr-dis-text: #{$clr-pri-dis-text}; } .bit-btn-sec { @@ -229,7 +237,10 @@ --bit-btn-clr-txt: #{$clr-sec-text}; --bit-btn-clr-hover: #{$clr-sec-hover}; --bit-btn-clr-active: #{$clr-sec-active}; + --bit-btn-clr-focus: #{$clr-sec-focus}; --bit-btn-clr-spn: #{$clr-sec-dark}; + --bit-btn-clr-dis: #{$clr-sec-dis}; + --bit-btn-clr-dis-text: #{$clr-sec-dis-text}; } .bit-btn-ter { @@ -237,7 +248,10 @@ --bit-btn-clr-txt: #{$clr-ter-text}; --bit-btn-clr-hover: #{$clr-ter-hover}; --bit-btn-clr-active: #{$clr-ter-active}; + --bit-btn-clr-focus: #{$clr-ter-focus}; --bit-btn-clr-spn: #{$clr-ter-dark}; + --bit-btn-clr-dis: #{$clr-ter-dis}; + --bit-btn-clr-dis-text: #{$clr-ter-dis-text}; } .bit-btn-inf { @@ -245,7 +259,10 @@ --bit-btn-clr-txt: #{$clr-inf-text}; --bit-btn-clr-hover: #{$clr-inf-hover}; --bit-btn-clr-active: #{$clr-inf-active}; + --bit-btn-clr-focus: #{$clr-inf-focus}; --bit-btn-clr-spn: #{$clr-inf-dark}; + --bit-btn-clr-dis: #{$clr-inf-dis}; + --bit-btn-clr-dis-text: #{$clr-inf-dis-text}; } .bit-btn-suc { @@ -253,7 +270,10 @@ --bit-btn-clr-txt: #{$clr-suc-text}; --bit-btn-clr-hover: #{$clr-suc-hover}; --bit-btn-clr-active: #{$clr-suc-active}; + --bit-btn-clr-focus: #{$clr-suc-focus}; --bit-btn-clr-spn: #{$clr-suc-dark}; + --bit-btn-clr-dis: #{$clr-suc-dis}; + --bit-btn-clr-dis-text: #{$clr-suc-dis-text}; } .bit-btn-wrn { @@ -261,7 +281,10 @@ --bit-btn-clr-txt: #{$clr-wrn-text}; --bit-btn-clr-hover: #{$clr-wrn-hover}; --bit-btn-clr-active: #{$clr-wrn-active}; + --bit-btn-clr-focus: #{$clr-wrn-focus}; --bit-btn-clr-spn: #{$clr-wrn-dark}; + --bit-btn-clr-dis: #{$clr-wrn-dis}; + --bit-btn-clr-dis-text: #{$clr-wrn-dis-text}; } .bit-btn-swr { @@ -269,7 +292,10 @@ --bit-btn-clr-txt: #{$clr-swr-text}; --bit-btn-clr-hover: #{$clr-swr-hover}; --bit-btn-clr-active: #{$clr-swr-active}; + --bit-btn-clr-focus: #{$clr-swr-focus}; --bit-btn-clr-spn: #{$clr-swr-dark}; + --bit-btn-clr-dis: #{$clr-swr-dis}; + --bit-btn-clr-dis-text: #{$clr-swr-dis-text}; } .bit-btn-err { @@ -277,7 +303,10 @@ --bit-btn-clr-txt: #{$clr-err-text}; --bit-btn-clr-hover: #{$clr-err-hover}; --bit-btn-clr-active: #{$clr-err-active}; + --bit-btn-clr-focus: #{$clr-err-focus}; --bit-btn-clr-spn: #{$clr-err-dark}; + --bit-btn-clr-dis: #{$clr-err-dis}; + --bit-btn-clr-dis-text: #{$clr-err-dis-text}; } .bit-btn-pbg { @@ -285,7 +314,10 @@ --bit-btn-clr-txt: #{$clr-fg-pri}; --bit-btn-clr-hover: #{$clr-bg-pri-hover}; --bit-btn-clr-active: #{$clr-bg-pri-active}; + --bit-btn-clr-focus: #{$clr-bg-pri-focus}; --bit-btn-clr-spn: #{$clr-fg-pri}; + --bit-btn-clr-dis: #{$clr-bg-pri-dis}; + --bit-btn-clr-dis-text: #{$clr-bg-pri-dis-text}; } .bit-btn-sbg { @@ -293,7 +325,10 @@ --bit-btn-clr-txt: #{$clr-fg-pri}; --bit-btn-clr-hover: #{$clr-bg-sec-hover}; --bit-btn-clr-active: #{$clr-bg-sec-active}; + --bit-btn-clr-focus: #{$clr-bg-sec-focus}; --bit-btn-clr-spn: #{$clr-fg-pri}; + --bit-btn-clr-dis: #{$clr-bg-sec-dis}; + --bit-btn-clr-dis-text: #{$clr-bg-sec-dis-text}; } .bit-btn-tbg { @@ -301,7 +336,10 @@ --bit-btn-clr-txt: #{$clr-fg-pri}; --bit-btn-clr-hover: #{$clr-bg-ter-hover}; --bit-btn-clr-active: #{$clr-bg-ter-active}; + --bit-btn-clr-focus: #{$clr-bg-ter-focus}; --bit-btn-clr-spn: #{$clr-fg-pri}; + --bit-btn-clr-dis: #{$clr-bg-ter-dis}; + --bit-btn-clr-dis-text: #{$clr-bg-ter-dis-text}; } .bit-btn-pfg { @@ -309,7 +347,10 @@ --bit-btn-clr-txt: #{$clr-bg-pri}; --bit-btn-clr-hover: #{$clr-fg-pri-hover}; --bit-btn-clr-active: #{$clr-fg-pri-active}; + --bit-btn-clr-focus: #{$clr-fg-pri-focus}; --bit-btn-clr-spn: #{$clr-bg-pri}; + --bit-btn-clr-dis: #{$clr-fg-pri-dis}; + --bit-btn-clr-dis-text: #{$clr-fg-pri-dis-text}; } .bit-btn-sfg { @@ -317,7 +358,10 @@ --bit-btn-clr-txt: #{$clr-bg-pri}; --bit-btn-clr-hover: #{$clr-fg-sec-hover}; --bit-btn-clr-active: #{$clr-fg-sec-active}; + --bit-btn-clr-focus: #{$clr-fg-sec-focus}; --bit-btn-clr-spn: #{$clr-bg-pri}; + --bit-btn-clr-dis: #{$clr-fg-sec-dis}; + --bit-btn-clr-dis-text: #{$clr-fg-sec-dis-text}; } .bit-btn-tfg { @@ -325,7 +369,10 @@ --bit-btn-clr-txt: #{$clr-bg-pri}; --bit-btn-clr-hover: #{$clr-fg-ter-hover}; --bit-btn-clr-active: #{$clr-fg-ter-active}; + --bit-btn-clr-focus: #{$clr-fg-ter-focus}; --bit-btn-clr-spn: #{$clr-bg-pri}; + --bit-btn-clr-dis: #{$clr-fg-ter-dis}; + --bit-btn-clr-dis-text: #{$clr-fg-ter-dis-text}; } .bit-btn-pbr { @@ -333,7 +380,10 @@ --bit-btn-clr-txt: #{$clr-fg-pri}; --bit-btn-clr-hover: #{$clr-brd-pri-hover}; --bit-btn-clr-active: #{$clr-brd-pri-active}; + --bit-btn-clr-focus: #{$clr-brd-pri-focus}; --bit-btn-clr-spn: #{$clr-fg-pri}; + --bit-btn-clr-dis: #{$clr-brd-pri-dis}; + --bit-btn-clr-dis-text: #{$clr-brd-pri-dis-text}; } .bit-btn-sbr { @@ -341,7 +391,10 @@ --bit-btn-clr-txt: #{$clr-fg-pri}; --bit-btn-clr-hover: #{$clr-brd-sec-hover}; --bit-btn-clr-active: #{$clr-brd-sec-active}; + --bit-btn-clr-focus: #{$clr-brd-sec-focus}; --bit-btn-clr-spn: #{$clr-fg-pri}; + --bit-btn-clr-dis: #{$clr-brd-sec-dis}; + --bit-btn-clr-dis-text: #{$clr-brd-sec-dis-text}; } .bit-btn-tbr { @@ -349,7 +402,10 @@ --bit-btn-clr-txt: #{$clr-fg-pri}; --bit-btn-clr-hover: #{$clr-brd-ter-hover}; --bit-btn-clr-active: #{$clr-brd-ter-active}; + --bit-btn-clr-focus: #{$clr-brd-ter-focus}; --bit-btn-clr-spn: #{$clr-fg-pri}; + --bit-btn-clr-dis: #{$clr-brd-ter-dis}; + --bit-btn-clr-dis-text: #{$clr-brd-ter-dis-text}; } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ButtonGroup/BitButtonGroup.scss b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ButtonGroup/BitButtonGroup.scss index d8123b6295..9d14ac2254 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ButtonGroup/BitButtonGroup.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ButtonGroup/BitButtonGroup.scss @@ -12,16 +12,26 @@ --bit-btg-itm-brd-wid: #{$shp-border-width}; --bit-btg-itm-brd-wid-ver: 0; + // The ring is rendered on the group container (like the MenuButton split-button) because the + // root's overflow:hidden would clip a box-shadow ring drawn on the focused item itself. + &:focus-visible, + &:has(:focus-visible) { + @include focus-ring(var(--bit-btg-clr-focus)); + } + &.bit-dis, &.bit-dis * { cursor: default; - color: $clr-fg-dis; pointer-events: none; } &.bit-dis { - border-color: $clr-brd-dis; - --bit-btg-itm-clr-bg: #{$clr-bg-dis}; - --bit-btg-itm-clr-brd: #{$clr-brd-dis}; + color: var(--bit-btg-clr-dis-text); + border-color: var(--bit-btg-clr-dis); + + // Resolve through the per-variant `*-dis` tokens so outline/text variants keep transparent + // backgrounds and only filled variants render the colored disabled tint. + --bit-btg-itm-clr-bg: var(--bit-btg-itm-clr-bg-dis); + --bit-btg-itm-clr-brd: var(--bit-btg-itm-clr-brd-dis, var(--bit-btg-clr-dis)); } } @@ -118,7 +128,8 @@ &[disabled], &[aria-disabled="true"] { cursor: default; - color: $clr-fg-dis; + color: var(--bit-btg-clr-dis-text); + border-color: var(--bit-btg-itm-clr-brd-dis, var(--bit-btg-clr-dis)); background-color: var(--bit-btg-itm-clr-bg-dis); } @@ -279,7 +290,7 @@ --bit-btg-itm-clr-hover: var(--bit-btg-clr-txt); --bit-btg-itm-clr-bg-hover: var(--bit-btg-clr-hover); --bit-btg-itm-clr-bg-active: var(--bit-btg-clr-active); - --bit-btg-itm-clr-bg-dis: #{$clr-bg-dis}; + --bit-btg-itm-clr-bg-dis: var(--bit-btg-clr-dis); --bit-btg-itm-clr-fcs: var(--bit-btg-clr-txt); @media (hover: hover) { @@ -313,6 +324,10 @@ &:active { border-color: var(--bit-btg-clr-active); } + + &.bit-dis { + background-color: transparent; + } } .bit-btg-txt { @@ -356,9 +371,12 @@ --bit-btg-clr-brd: #{$clr-pri-dark}; --bit-btg-clr-hover: #{$clr-pri-hover}; --bit-btg-clr-active: #{$clr-pri-active}; + --bit-btg-clr-focus: #{$clr-pri-focus}; --bit-btg-clr-dark: #{$clr-pri-dark}; --bit-btg-clr-dark-hover: #{$clr-pri-dark-hover}; --bit-btg-clr-dark-active: #{$clr-pri-dark-active}; + --bit-btg-clr-dis: #{$clr-pri-dis}; + --bit-btg-clr-dis-text: #{$clr-pri-dis-text}; } .bit-btg-sec { @@ -367,9 +385,12 @@ --bit-btg-clr-brd: #{$clr-sec-dark}; --bit-btg-clr-hover: #{$clr-sec-hover}; --bit-btg-clr-active: #{$clr-sec-active}; + --bit-btg-clr-focus: #{$clr-sec-focus}; --bit-btg-clr-dark: #{$clr-sec-dark}; --bit-btg-clr-dark-hover: #{$clr-sec-dark-hover}; --bit-btg-clr-dark-active: #{$clr-sec-dark-active}; + --bit-btg-clr-dis: #{$clr-sec-dis}; + --bit-btg-clr-dis-text: #{$clr-sec-dis-text}; } .bit-btg-ter { @@ -378,9 +399,12 @@ --bit-btg-clr-brd: #{$clr-ter-dark}; --bit-btg-clr-hover: #{$clr-ter-hover}; --bit-btg-clr-active: #{$clr-ter-active}; + --bit-btg-clr-focus: #{$clr-ter-focus}; --bit-btg-clr-dark: #{$clr-ter-dark}; --bit-btg-clr-dark-hover: #{$clr-ter-dark-hover}; --bit-btg-clr-dark-active: #{$clr-ter-dark-active}; + --bit-btg-clr-dis: #{$clr-ter-dis}; + --bit-btg-clr-dis-text: #{$clr-ter-dis-text}; } .bit-btg-inf { @@ -389,9 +413,12 @@ --bit-btg-clr-brd: #{$clr-inf-dark}; --bit-btg-clr-hover: #{$clr-inf-hover}; --bit-btg-clr-active: #{$clr-inf-active}; + --bit-btg-clr-focus: #{$clr-inf-focus}; --bit-btg-clr-dark: #{$clr-inf-dark}; --bit-btg-clr-dark-hover: #{$clr-inf-dark-hover}; --bit-btg-clr-dark-active: #{$clr-inf-dark-active}; + --bit-btg-clr-dis: #{$clr-inf-dis}; + --bit-btg-clr-dis-text: #{$clr-inf-dis-text}; } .bit-btg-suc { @@ -400,9 +427,12 @@ --bit-btg-clr-brd: #{$clr-suc-dark}; --bit-btg-clr-hover: #{$clr-suc-hover}; --bit-btg-clr-active: #{$clr-suc-active}; + --bit-btg-clr-focus: #{$clr-suc-focus}; --bit-btg-clr-dark: #{$clr-suc-dark}; --bit-btg-clr-dark-hover: #{$clr-suc-dark-hover}; --bit-btg-clr-dark-active: #{$clr-suc-dark-active}; + --bit-btg-clr-dis: #{$clr-suc-dis}; + --bit-btg-clr-dis-text: #{$clr-suc-dis-text}; } .bit-btg-wrn { @@ -411,9 +441,12 @@ --bit-btg-clr-brd: #{$clr-wrn-dark}; --bit-btg-clr-hover: #{$clr-wrn-hover}; --bit-btg-clr-active: #{$clr-wrn-active}; + --bit-btg-clr-focus: #{$clr-wrn-focus}; --bit-btg-clr-dark: #{$clr-wrn-dark}; --bit-btg-clr-dark-hover: #{$clr-wrn-dark-hover}; --bit-btg-clr-dark-active: #{$clr-wrn-dark-active}; + --bit-btg-clr-dis: #{$clr-wrn-dis}; + --bit-btg-clr-dis-text: #{$clr-wrn-dis-text}; } .bit-btg-swr { @@ -422,9 +455,12 @@ --bit-btg-clr-brd: #{$clr-swr-dark}; --bit-btg-clr-hover: #{$clr-swr-hover}; --bit-btg-clr-active: #{$clr-swr-active}; + --bit-btg-clr-focus: #{$clr-swr-focus}; --bit-btg-clr-dark: #{$clr-swr-dark}; --bit-btg-clr-dark-hover: #{$clr-swr-dark-hover}; --bit-btg-clr-dark-active: #{$clr-swr-dark-active}; + --bit-btg-clr-dis: #{$clr-swr-dis}; + --bit-btg-clr-dis-text: #{$clr-swr-dis-text}; } .bit-btg-err { @@ -433,9 +469,12 @@ --bit-btg-clr-brd: #{$clr-err-dark}; --bit-btg-clr-hover: #{$clr-err-hover}; --bit-btg-clr-active: #{$clr-err-active}; + --bit-btg-clr-focus: #{$clr-err-focus}; --bit-btg-clr-dark: #{$clr-err-dark}; --bit-btg-clr-dark-hover: #{$clr-err-dark-hover}; --bit-btg-clr-dark-active: #{$clr-err-dark-active}; + --bit-btg-clr-dis: #{$clr-err-dis}; + --bit-btg-clr-dis-text: #{$clr-err-dis-text}; } @@ -445,9 +484,12 @@ --bit-btg-clr-brd: #{$clr-bg-pri}; --bit-btg-clr-hover: #{$clr-bg-pri-hover}; --bit-btg-clr-active: #{$clr-bg-pri-active}; + --bit-btg-clr-focus: #{$clr-bg-pri-focus}; --bit-btg-clr-dark: #{$clr-bg-pri}; --bit-btg-clr-dark-hover: #{$clr-bg-pri-hover}; --bit-btg-clr-dark-active: #{$clr-bg-pri-active}; + --bit-btg-clr-dis: #{$clr-bg-pri-dis}; + --bit-btg-clr-dis-text: #{$clr-bg-pri-dis-text}; } .bit-btg-sbg { @@ -456,9 +498,12 @@ --bit-btg-clr-brd: #{$clr-bg-sec}; --bit-btg-clr-hover: #{$clr-bg-sec-hover}; --bit-btg-clr-active: #{$clr-bg-sec-active}; + --bit-btg-clr-focus: #{$clr-bg-sec-focus}; --bit-btg-clr-dark: #{$clr-bg-sec}; --bit-btg-clr-dark-hover: #{$clr-bg-sec-hover}; --bit-btg-clr-dark-active: #{$clr-bg-sec-active}; + --bit-btg-clr-dis: #{$clr-bg-sec-dis}; + --bit-btg-clr-dis-text: #{$clr-bg-sec-dis-text}; } .bit-btg-tbg { @@ -467,9 +512,12 @@ --bit-btg-clr-brd: #{$clr-bg-ter}; --bit-btg-clr-hover: #{$clr-bg-ter-hover}; --bit-btg-clr-active: #{$clr-bg-ter-active}; + --bit-btg-clr-focus: #{$clr-bg-ter-focus}; --bit-btg-clr-dark: #{$clr-bg-ter}; --bit-btg-clr-dark-hover: #{$clr-bg-ter-hover}; --bit-btg-clr-dark-active: #{$clr-bg-ter-active}; + --bit-btg-clr-dis: #{$clr-bg-ter-dis}; + --bit-btg-clr-dis-text: #{$clr-bg-ter-dis-text}; } .bit-btg-pfg { @@ -478,9 +526,12 @@ --bit-btg-clr-brd: #{$clr-fg-pri}; --bit-btg-clr-hover: #{$clr-fg-pri-hover}; --bit-btg-clr-active: #{$clr-fg-pri-active}; + --bit-btg-clr-focus: #{$clr-fg-pri-focus}; --bit-btg-clr-dark: #{$clr-fg-pri}; --bit-btg-clr-dark-hover: #{$clr-fg-pri-hover}; --bit-btg-clr-dark-active: #{$clr-fg-pri-active}; + --bit-btg-clr-dis: #{$clr-fg-pri-dis}; + --bit-btg-clr-dis-text: #{$clr-fg-pri-dis-text}; } .bit-btg-sfg { @@ -489,9 +540,12 @@ --bit-btg-clr-brd: #{$clr-fg-sec}; --bit-btg-clr-hover: #{$clr-fg-sec-hover}; --bit-btg-clr-active: #{$clr-fg-sec-active}; + --bit-btg-clr-focus: #{$clr-fg-sec-focus}; --bit-btg-clr-dark: #{$clr-fg-sec}; --bit-btg-clr-dark-hover: #{$clr-fg-sec-hover}; --bit-btg-clr-dark-active: #{$clr-fg-sec-active}; + --bit-btg-clr-dis: #{$clr-fg-sec-dis}; + --bit-btg-clr-dis-text: #{$clr-fg-sec-dis-text}; } .bit-btg-tfg { @@ -500,9 +554,12 @@ --bit-btg-clr-brd: #{$clr-fg-ter}; --bit-btg-clr-hover: #{$clr-fg-ter-hover}; --bit-btg-clr-active: #{$clr-fg-ter-active}; + --bit-btg-clr-focus: #{$clr-fg-ter-focus}; --bit-btg-clr-dark: #{$clr-fg-ter}; --bit-btg-clr-dark-hover: #{$clr-fg-ter-hover}; --bit-btg-clr-dark-active: #{$clr-fg-ter-active}; + --bit-btg-clr-dis: #{$clr-fg-ter-dis}; + --bit-btg-clr-dis-text: #{$clr-fg-ter-dis-text}; } .bit-btg-pbr { @@ -511,9 +568,12 @@ --bit-btg-clr-brd: #{$clr-brd-pri}; --bit-btg-clr-hover: #{$clr-brd-pri-hover}; --bit-btg-clr-active: #{$clr-brd-pri-active}; + --bit-btg-clr-focus: #{$clr-brd-pri-focus}; --bit-btg-clr-dark: #{$clr-brd-pri}; --bit-btg-clr-dark-hover: #{$clr-brd-pri-hover}; --bit-btg-clr-dark-active: #{$clr-brd-pri-active}; + --bit-btg-clr-dis: #{$clr-brd-pri-dis}; + --bit-btg-clr-dis-text: #{$clr-brd-pri-dis-text}; } .bit-btg-sbr { @@ -522,9 +582,12 @@ --bit-btg-clr-brd: #{$clr-brd-sec}; --bit-btg-clr-hover: #{$clr-brd-sec-hover}; --bit-btg-clr-active: #{$clr-brd-sec-active}; + --bit-btg-clr-focus: #{$clr-brd-sec-focus}; --bit-btg-clr-dark: #{$clr-brd-sec}; --bit-btg-clr-dark-hover: #{$clr-brd-sec-hover}; --bit-btg-clr-dark-active: #{$clr-brd-sec-active}; + --bit-btg-clr-dis: #{$clr-brd-sec-dis}; + --bit-btg-clr-dis-text: #{$clr-brd-sec-dis-text}; } .bit-btg-tbr { @@ -533,9 +596,12 @@ --bit-btg-clr-brd: #{$clr-brd-ter}; --bit-btg-clr-hover: #{$clr-brd-ter-hover}; --bit-btg-clr-active: #{$clr-brd-ter-active}; + --bit-btg-clr-focus: #{$clr-brd-ter-focus}; --bit-btg-clr-dark: #{$clr-brd-ter}; --bit-btg-clr-dark-hover: #{$clr-brd-ter-hover}; --bit-btg-clr-dark-active: #{$clr-brd-ter-active}; + --bit-btg-clr-dis: #{$clr-brd-ter-dis}; + --bit-btg-clr-dis-text: #{$clr-brd-ter-dis-text}; } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/MenuButton/BitMenuButton.scss b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/MenuButton/BitMenuButton.scss index 718e4299e3..28896ba626 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/MenuButton/BitMenuButton.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/MenuButton/BitMenuButton.scss @@ -13,6 +13,21 @@ border-style: $shp-border-style; border-radius: $shp-border-radius; + &:focus-visible, + &:has(:focus-visible) { + @include focus-ring(var(--bit-mnb-clr-focus)); + } + + &.bit-dis { + cursor: default; + pointer-events: none; + color: var(--bit-mnb-clr-dis-text); + --bit-mnb-clr-spb: var(--bit-mnb-clr-dis-text); + --bit-mnb-clr-txt: var(--bit-mnb-clr-dis-text); + --bit-mnb-clr-opb-txt: var(--bit-mnb-clr-dis-text); + --bit-mnb-clr-chb-txt: var(--bit-mnb-clr-dis-text); + } + @keyframes bit-mnb-spinner-animation { 0% { transform: rotate(0deg); @@ -22,13 +37,6 @@ transform: rotate(360deg); } } - - &.bit-dis { - cursor: default; - color: $clr-fg-dis; - pointer-events: none; - --bit-mnb-clr-spb: #{$clr-fg-dis}; - } } .bit-mnb-flw { @@ -187,7 +195,7 @@ &[disabled] { cursor: default; - color: $clr-fg-dis; + color: var(--bit-mnb-clr-dis-text); pointer-events: none; } @@ -225,8 +233,9 @@ } &.bit-dis { - border-color: $clr-brd-dis; - background-color: $clr-bg-dis; + color: var(--bit-mnb-clr-dis-text); + border-color: var(--bit-mnb-clr-dis); + background-color: var(--bit-mnb-clr-dis); } } @@ -247,7 +256,9 @@ } &.bit-dis { - border-color: $clr-brd-dis; + color: var(--bit-mnb-clr-dis-text); + border-color: var(--bit-mnb-clr-dis); + background-color: transparent; } } @@ -256,6 +267,12 @@ border-color: transparent; background-color: transparent; --bit-mnb-clr-spb: var(--bit-mnb-clr); + + &.bit-dis { + color: var(--bit-mnb-clr-dis-text); + border-color: transparent; + background-color: transparent; + } } .bit-mnb-pri { @@ -263,6 +280,9 @@ --bit-mnb-clr-txt: #{$clr-pri-text}; --bit-mnb-clr-hover: #{$clr-pri-hover}; --bit-mnb-clr-active: #{$clr-pri-active}; + --bit-mnb-clr-focus: #{$clr-pri-focus}; + --bit-mnb-clr-dis: #{$clr-pri-dis}; + --bit-mnb-clr-dis-text: #{$clr-pri-dis-text}; } .bit-mnb-sec { @@ -270,6 +290,9 @@ --bit-mnb-clr-txt: #{$clr-sec-text}; --bit-mnb-clr-hover: #{$clr-sec-hover}; --bit-mnb-clr-active: #{$clr-sec-active}; + --bit-mnb-clr-focus: #{$clr-sec-focus}; + --bit-mnb-clr-dis: #{$clr-sec-dis}; + --bit-mnb-clr-dis-text: #{$clr-sec-dis-text}; } .bit-mnb-ter { @@ -277,6 +300,9 @@ --bit-mnb-clr-txt: #{$clr-ter-text}; --bit-mnb-clr-hover: #{$clr-ter-hover}; --bit-mnb-clr-active: #{$clr-ter-active}; + --bit-mnb-clr-focus: #{$clr-ter-focus}; + --bit-mnb-clr-dis: #{$clr-ter-dis}; + --bit-mnb-clr-dis-text: #{$clr-ter-dis-text}; } .bit-mnb-inf { @@ -284,6 +310,9 @@ --bit-mnb-clr-txt: #{$clr-inf-text}; --bit-mnb-clr-hover: #{$clr-inf-hover}; --bit-mnb-clr-active: #{$clr-inf-active}; + --bit-mnb-clr-focus: #{$clr-inf-focus}; + --bit-mnb-clr-dis: #{$clr-inf-dis}; + --bit-mnb-clr-dis-text: #{$clr-inf-dis-text}; } .bit-mnb-suc { @@ -291,6 +320,9 @@ --bit-mnb-clr-txt: #{$clr-suc-text}; --bit-mnb-clr-hover: #{$clr-suc-hover}; --bit-mnb-clr-active: #{$clr-suc-active}; + --bit-mnb-clr-focus: #{$clr-suc-focus}; + --bit-mnb-clr-dis: #{$clr-suc-dis}; + --bit-mnb-clr-dis-text: #{$clr-suc-dis-text}; } .bit-mnb-wrn { @@ -298,6 +330,9 @@ --bit-mnb-clr-txt: #{$clr-wrn-text}; --bit-mnb-clr-hover: #{$clr-wrn-hover}; --bit-mnb-clr-active: #{$clr-wrn-active}; + --bit-mnb-clr-focus: #{$clr-wrn-focus}; + --bit-mnb-clr-dis: #{$clr-wrn-dis}; + --bit-mnb-clr-dis-text: #{$clr-wrn-dis-text}; } .bit-mnb-swr { @@ -305,6 +340,9 @@ --bit-mnb-clr-txt: #{$clr-swr-text}; --bit-mnb-clr-hover: #{$clr-swr-hover}; --bit-mnb-clr-active: #{$clr-swr-active}; + --bit-mnb-clr-focus: #{$clr-swr-focus}; + --bit-mnb-clr-dis: #{$clr-swr-dis}; + --bit-mnb-clr-dis-text: #{$clr-swr-dis-text}; } .bit-mnb-err { @@ -312,6 +350,9 @@ --bit-mnb-clr-txt: #{$clr-err-text}; --bit-mnb-clr-hover: #{$clr-err-hover}; --bit-mnb-clr-active: #{$clr-err-active}; + --bit-mnb-clr-focus: #{$clr-err-focus}; + --bit-mnb-clr-dis: #{$clr-err-dis}; + --bit-mnb-clr-dis-text: #{$clr-err-dis-text}; } .bit-mnb-pbg { @@ -319,6 +360,9 @@ --bit-mnb-clr-txt: #{$clr-fg-pri}; --bit-mnb-clr-hover: #{$clr-bg-pri-hover}; --bit-mnb-clr-active: #{$clr-bg-pri-active}; + --bit-mnb-clr-focus: #{$clr-bg-pri-focus}; + --bit-mnb-clr-dis: #{$clr-bg-pri-dis}; + --bit-mnb-clr-dis-text: #{$clr-bg-pri-dis-text}; } .bit-mnb-sbg { @@ -326,6 +370,9 @@ --bit-mnb-clr-txt: #{$clr-fg-pri}; --bit-mnb-clr-hover: #{$clr-bg-sec-hover}; --bit-mnb-clr-active: #{$clr-bg-sec-active}; + --bit-mnb-clr-focus: #{$clr-bg-sec-focus}; + --bit-mnb-clr-dis: #{$clr-bg-sec-dis}; + --bit-mnb-clr-dis-text: #{$clr-bg-sec-dis-text}; } .bit-mnb-tbg { @@ -333,6 +380,9 @@ --bit-mnb-clr-txt: #{$clr-fg-pri}; --bit-mnb-clr-hover: #{$clr-bg-ter-hover}; --bit-mnb-clr-active: #{$clr-bg-ter-active}; + --bit-mnb-clr-focus: #{$clr-bg-ter-focus}; + --bit-mnb-clr-dis: #{$clr-bg-ter-dis}; + --bit-mnb-clr-dis-text: #{$clr-bg-ter-dis-text}; } .bit-mnb-pfg { @@ -340,6 +390,9 @@ --bit-mnb-clr-txt: #{$clr-bg-pri}; --bit-mnb-clr-hover: #{$clr-fg-pri-hover}; --bit-mnb-clr-active: #{$clr-fg-pri-active}; + --bit-mnb-clr-focus: #{$clr-fg-pri-focus}; + --bit-mnb-clr-dis: #{$clr-fg-pri-dis}; + --bit-mnb-clr-dis-text: #{$clr-fg-pri-dis-text}; } .bit-mnb-sfg { @@ -347,6 +400,9 @@ --bit-mnb-clr-txt: #{$clr-bg-pri}; --bit-mnb-clr-hover: #{$clr-fg-sec-hover}; --bit-mnb-clr-active: #{$clr-fg-sec-active}; + --bit-mnb-clr-focus: #{$clr-fg-sec-focus}; + --bit-mnb-clr-dis: #{$clr-fg-sec-dis}; + --bit-mnb-clr-dis-text: #{$clr-fg-sec-dis-text}; } .bit-mnb-tfg { @@ -354,6 +410,9 @@ --bit-mnb-clr-txt: #{$clr-bg-pri}; --bit-mnb-clr-hover: #{$clr-fg-ter-hover}; --bit-mnb-clr-active: #{$clr-fg-ter-active}; + --bit-mnb-clr-focus: #{$clr-fg-ter-focus}; + --bit-mnb-clr-dis: #{$clr-fg-ter-dis}; + --bit-mnb-clr-dis-text: #{$clr-fg-ter-dis-text}; } .bit-mnb-pbr { @@ -361,6 +420,9 @@ --bit-mnb-clr-txt: #{$clr-fg-pri}; --bit-mnb-clr-hover: #{$clr-brd-pri-hover}; --bit-mnb-clr-active: #{$clr-brd-pri-active}; + --bit-mnb-clr-focus: #{$clr-brd-pri-focus}; + --bit-mnb-clr-dis: #{$clr-brd-pri-dis}; + --bit-mnb-clr-dis-text: #{$clr-brd-pri-dis-text}; } .bit-mnb-sbr { @@ -368,6 +430,9 @@ --bit-mnb-clr-txt: #{$clr-fg-pri}; --bit-mnb-clr-hover: #{$clr-brd-sec-hover}; --bit-mnb-clr-active: #{$clr-brd-sec-active}; + --bit-mnb-clr-focus: #{$clr-brd-sec-focus}; + --bit-mnb-clr-dis: #{$clr-brd-sec-dis}; + --bit-mnb-clr-dis-text: #{$clr-brd-sec-dis-text}; } .bit-mnb-tbr { @@ -375,6 +440,9 @@ --bit-mnb-clr-txt: #{$clr-fg-pri}; --bit-mnb-clr-hover: #{$clr-brd-ter-hover}; --bit-mnb-clr-active: #{$clr-brd-ter-active}; + --bit-mnb-clr-focus: #{$clr-brd-ter-focus}; + --bit-mnb-clr-dis: #{$clr-brd-ter-dis}; + --bit-mnb-clr-dis-text: #{$clr-brd-ter-dis-text}; } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ToggleButton/BitToggleButton.scss b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ToggleButton/BitToggleButton.scss index d5cd81dc39..a4bb362b24 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ToggleButton/BitToggleButton.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ToggleButton/BitToggleButton.scss @@ -21,13 +21,11 @@ --bit-tgb-icn-margin: #{spacing(0.5)}; &:focus-visible { - outline-offset: spacing(0.25); - outline: spacing(0.25) solid var(--bit-tgb-clr); + @include focus-ring(var(--bit-tgb-clr-focus)); } &.bit-dis { cursor: default; - color: $clr-fg-dis; pointer-events: none; } @@ -156,8 +154,9 @@ } &.bit-dis { - border-color: $clr-brd-dis; - background-color: $clr-bg-dis; + color: var(--bit-tgb-clr-dis-text); + border-color: var(--bit-tgb-clr-dis); + background-color: var(--bit-tgb-clr-dis); } } @@ -181,7 +180,9 @@ } &.bit-dis { - border-color: $clr-brd-dis; + color: var(--bit-tgb-clr-dis-text); + border-color: var(--bit-tgb-clr-dis); + background-color: transparent; } } @@ -203,6 +204,12 @@ border-color: var(--bit-tgb-clr-active); background-color: var(--bit-tgb-clr-active); } + + &.bit-dis { + color: var(--bit-tgb-clr-dis-text); + border-color: transparent; + background-color: transparent; + } } .bit-tgb-chk { @@ -228,9 +235,12 @@ --bit-tgb-clr: #{$clr-pri}; --bit-tgb-clr-hover: #{$clr-pri-hover}; --bit-tgb-clr-active: #{$clr-pri-active}; + --bit-tgb-clr-focus: #{$clr-pri-focus}; --bit-tgb-clr-dark: #{$clr-pri-dark}; --bit-tgb-clr-dark-hover: #{$clr-pri-dark-hover}; --bit-tgb-clr-dark-active: #{$clr-pri-dark-active}; + --bit-tgb-clr-dis: #{$clr-pri-dis}; + --bit-tgb-clr-dis-text: #{$clr-pri-dis-text}; } .bit-tgb-sec { @@ -238,9 +248,12 @@ --bit-tgb-clr: #{$clr-sec}; --bit-tgb-clr-hover: #{$clr-sec-hover}; --bit-tgb-clr-active: #{$clr-sec-active}; + --bit-tgb-clr-focus: #{$clr-sec-focus}; --bit-tgb-clr-dark: #{$clr-sec-dark}; --bit-tgb-clr-dark-hover: #{$clr-sec-dark-hover}; --bit-tgb-clr-dark-active: #{$clr-sec-dark-active}; + --bit-tgb-clr-dis: #{$clr-sec-dis}; + --bit-tgb-clr-dis-text: #{$clr-sec-dis-text}; } .bit-tgb-ter { @@ -248,9 +261,12 @@ --bit-tgb-clr: #{$clr-ter}; --bit-tgb-clr-hover: #{$clr-ter-hover}; --bit-tgb-clr-active: #{$clr-ter-active}; + --bit-tgb-clr-focus: #{$clr-ter-focus}; --bit-tgb-clr-dark: #{$clr-ter-dark}; --bit-tgb-clr-dark-hover: #{$clr-ter-dark-hover}; --bit-tgb-clr-dark-active: #{$clr-ter-dark-active}; + --bit-tgb-clr-dis: #{$clr-ter-dis}; + --bit-tgb-clr-dis-text: #{$clr-ter-dis-text}; } .bit-tgb-inf { @@ -258,9 +274,12 @@ --bit-tgb-clr: #{$clr-inf}; --bit-tgb-clr-hover: #{$clr-inf-hover}; --bit-tgb-clr-active: #{$clr-inf-active}; + --bit-tgb-clr-focus: #{$clr-inf-focus}; --bit-tgb-clr-dark: #{$clr-inf-dark}; --bit-tgb-clr-dark-hover: #{$clr-inf-dark-hover}; --bit-tgb-clr-dark-active: #{$clr-inf-dark-active}; + --bit-tgb-clr-dis: #{$clr-inf-dis}; + --bit-tgb-clr-dis-text: #{$clr-inf-dis-text}; } .bit-tgb-suc { @@ -268,9 +287,12 @@ --bit-tgb-clr: #{$clr-suc}; --bit-tgb-clr-hover: #{$clr-suc-hover}; --bit-tgb-clr-active: #{$clr-suc-active}; + --bit-tgb-clr-focus: #{$clr-suc-focus}; --bit-tgb-clr-dark: #{$clr-suc-dark}; --bit-tgb-clr-dark-hover: #{$clr-suc-dark-hover}; --bit-tgb-clr-dark-active: #{$clr-suc-dark-active}; + --bit-tgb-clr-dis: #{$clr-suc-dis}; + --bit-tgb-clr-dis-text: #{$clr-suc-dis-text}; } .bit-tgb-wrn { @@ -278,9 +300,12 @@ --bit-tgb-clr: #{$clr-wrn}; --bit-tgb-clr-hover: #{$clr-wrn-hover}; --bit-tgb-clr-active: #{$clr-wrn-active}; + --bit-tgb-clr-focus: #{$clr-wrn-focus}; --bit-tgb-clr-dark: #{$clr-wrn-dark}; --bit-tgb-clr-dark-hover: #{$clr-wrn-dark-hover}; --bit-tgb-clr-dark-active: #{$clr-wrn-dark-active}; + --bit-tgb-clr-dis: #{$clr-wrn-dis}; + --bit-tgb-clr-dis-text: #{$clr-wrn-dis-text}; } .bit-tgb-swr { @@ -288,9 +313,12 @@ --bit-tgb-clr: #{$clr-swr}; --bit-tgb-clr-hover: #{$clr-swr-hover}; --bit-tgb-clr-active: #{$clr-swr-active}; + --bit-tgb-clr-focus: #{$clr-swr-focus}; --bit-tgb-clr-dark: #{$clr-swr-dark}; --bit-tgb-clr-dark-hover: #{$clr-swr-dark-hover}; --bit-tgb-clr-dark-active: #{$clr-swr-dark-active}; + --bit-tgb-clr-dis: #{$clr-swr-dis}; + --bit-tgb-clr-dis-text: #{$clr-swr-dis-text}; } .bit-tgb-err { @@ -298,9 +326,12 @@ --bit-tgb-clr: #{$clr-err}; --bit-tgb-clr-hover: #{$clr-err-hover}; --bit-tgb-clr-active: #{$clr-err-active}; + --bit-tgb-clr-focus: #{$clr-err-focus}; --bit-tgb-clr-dark: #{$clr-err-dark}; --bit-tgb-clr-dark-hover: #{$clr-err-dark-hover}; --bit-tgb-clr-dark-active: #{$clr-err-dark-active}; + --bit-tgb-clr-dis: #{$clr-err-dis}; + --bit-tgb-clr-dis-text: #{$clr-err-dis-text}; } .bit-tgb-pbg { @@ -308,9 +339,12 @@ --bit-tgb-clr: #{$clr-bg-pri}; --bit-tgb-clr-hover: #{$clr-bg-pri-hover}; --bit-tgb-clr-active: #{$clr-bg-pri-active}; + --bit-tgb-clr-focus: #{$clr-bg-pri-focus}; --bit-tgb-clr-dark: #{$clr-bg-pri-dark}; --bit-tgb-clr-dark-hover: #{$clr-bg-pri-dark-hover}; --bit-tgb-clr-dark-active: #{$clr-bg-pri-dark-active}; + --bit-tgb-clr-dis: #{$clr-bg-pri-dis}; + --bit-tgb-clr-dis-text: #{$clr-bg-pri-dis-text}; } .bit-tgb-sbg { @@ -318,9 +352,12 @@ --bit-tgb-clr: #{$clr-bg-sec}; --bit-tgb-clr-hover: #{$clr-bg-sec-hover}; --bit-tgb-clr-active: #{$clr-bg-sec-active}; + --bit-tgb-clr-focus: #{$clr-bg-sec-focus}; --bit-tgb-clr-dark: #{$clr-bg-sec-dark}; --bit-tgb-clr-dark-hover: #{$clr-bg-sec-dark-hover}; --bit-tgb-clr-dark-active: #{$clr-bg-sec-dark-active}; + --bit-tgb-clr-dis: #{$clr-bg-sec-dis}; + --bit-tgb-clr-dis-text: #{$clr-bg-sec-dis-text}; } .bit-tgb-tbg { @@ -328,9 +365,12 @@ --bit-tgb-clr: #{$clr-bg-ter}; --bit-tgb-clr-hover: #{$clr-bg-ter-hover}; --bit-tgb-clr-active: #{$clr-bg-ter-active}; + --bit-tgb-clr-focus: #{$clr-bg-ter-focus}; --bit-tgb-clr-dark: #{$clr-bg-ter-dark}; --bit-tgb-clr-dark-hover: #{$clr-bg-ter-dark-hover}; --bit-tgb-clr-dark-active: #{$clr-bg-ter-dark-active}; + --bit-tgb-clr-dis: #{$clr-bg-ter-dis}; + --bit-tgb-clr-dis-text: #{$clr-bg-ter-dis-text}; } .bit-tgb-pfg { @@ -338,9 +378,12 @@ --bit-tgb-clr: #{$clr-fg-pri}; --bit-tgb-clr-hover: #{$clr-fg-pri-hover}; --bit-tgb-clr-active: #{$clr-fg-pri-active}; + --bit-tgb-clr-focus: #{$clr-fg-pri-focus}; --bit-tgb-clr-dark: #{$clr-fg-pri-dark}; --bit-tgb-clr-dark-hover: #{$clr-fg-pri-dark-hover}; --bit-tgb-clr-dark-active: #{$clr-fg-pri-dark-active}; + --bit-tgb-clr-dis: #{$clr-fg-pri-dis}; + --bit-tgb-clr-dis-text: #{$clr-fg-pri-dis-text}; } .bit-tgb-sfg { @@ -348,9 +391,12 @@ --bit-tgb-clr: #{$clr-fg-sec}; --bit-tgb-clr-hover: #{$clr-fg-sec-hover}; --bit-tgb-clr-active: #{$clr-fg-sec-active}; + --bit-tgb-clr-focus: #{$clr-fg-sec-focus}; --bit-tgb-clr-dark: #{$clr-fg-sec-dark}; --bit-tgb-clr-dark-hover: #{$clr-fg-sec-dark-hover}; --bit-tgb-clr-dark-active: #{$clr-fg-sec-dark-active}; + --bit-tgb-clr-dis: #{$clr-fg-sec-dis}; + --bit-tgb-clr-dis-text: #{$clr-fg-sec-dis-text}; } .bit-tgb-tfg { @@ -358,9 +404,12 @@ --bit-tgb-clr: #{$clr-fg-ter}; --bit-tgb-clr-hover: #{$clr-fg-ter-hover}; --bit-tgb-clr-active: #{$clr-fg-ter-active}; + --bit-tgb-clr-focus: #{$clr-fg-ter-focus}; --bit-tgb-clr-dark: #{$clr-fg-ter-dark}; --bit-tgb-clr-dark-hover: #{$clr-fg-ter-dark-hover}; --bit-tgb-clr-dark-active: #{$clr-fg-ter-dark-active}; + --bit-tgb-clr-dis: #{$clr-fg-ter-dis}; + --bit-tgb-clr-dis-text: #{$clr-fg-ter-dis-text}; } .bit-tgb-pbr { @@ -368,9 +417,12 @@ --bit-tgb-clr: #{$clr-brd-pri}; --bit-tgb-clr-hover: #{$clr-brd-pri-hover}; --bit-tgb-clr-active: #{$clr-brd-pri-active}; + --bit-tgb-clr-focus: #{$clr-brd-pri-focus}; --bit-tgb-clr-dark: #{$clr-brd-pri-dark}; --bit-tgb-clr-dark-hover: #{$clr-brd-pri-dark-hover}; --bit-tgb-clr-dark-active: #{$clr-brd-pri-dark-active}; + --bit-tgb-clr-dis: #{$clr-brd-pri-dis}; + --bit-tgb-clr-dis-text: #{$clr-brd-pri-dis-text}; } .bit-tgb-sbr { @@ -378,9 +430,12 @@ --bit-tgb-clr: #{$clr-brd-sec}; --bit-tgb-clr-hover: #{$clr-brd-sec-hover}; --bit-tgb-clr-active: #{$clr-brd-sec-active}; + --bit-tgb-clr-focus: #{$clr-brd-sec-focus}; --bit-tgb-clr-dark: #{$clr-brd-sec-dark}; --bit-tgb-clr-dark-hover: #{$clr-brd-sec-dark-hover}; --bit-tgb-clr-dark-active: #{$clr-brd-sec-dark-active}; + --bit-tgb-clr-dis: #{$clr-brd-sec-dis}; + --bit-tgb-clr-dis-text: #{$clr-brd-sec-dis-text}; } .bit-tgb-tbr { @@ -388,9 +443,12 @@ --bit-tgb-clr: #{$clr-brd-ter}; --bit-tgb-clr-hover: #{$clr-brd-ter-hover}; --bit-tgb-clr-active: #{$clr-brd-ter-active}; + --bit-tgb-clr-focus: #{$clr-brd-ter-focus}; --bit-tgb-clr-dark: #{$clr-brd-ter-dark}; --bit-tgb-clr-dark-hover: #{$clr-brd-ter-dark-hover}; --bit-tgb-clr-dark-active: #{$clr-brd-ter-dark-active}; + --bit-tgb-clr-dis: #{$clr-brd-ter-dis}; + --bit-tgb-clr-dis-text: #{$clr-brd-ter-dis-text}; } @@ -451,7 +509,7 @@ display: inline-grid; place-items: center; - > * { + >* { grid-area: 1 / 1; } } @@ -464,4 +522,4 @@ padding: var(--bit-tgb-ntx-pad); --bit-tgb-icn-margin: 0; --bit-tgb-fontsize: var(--bit-tgb-ntx-icn-size); -} +} \ No newline at end of file diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Calendar/BitCalendar.scss b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Calendar/BitCalendar.scss index 61f4d88753..171205b4d0 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Calendar/BitCalendar.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Calendar/BitCalendar.scss @@ -491,9 +491,8 @@ } &:focus { + @include focus-underline-ring; border: none; - outline: none; - border-bottom: $shp-border-width $shp-border-style $clr-brd-pri-active; } } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Checkbox/BitCheckbox.scss b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Checkbox/BitCheckbox.scss index 1dd926cc60..5e687606d9 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Checkbox/BitCheckbox.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Checkbox/BitCheckbox.scss @@ -3,11 +3,16 @@ .bit-chb { cursor: pointer; width: fit-content; + border-radius: $shp-border-radius; font-family: $tg-font-family; --bit-chb-ico-opa: 0; --bit-chb-box-clr-brd: #{$clr-brd-pri}; --bit-chb-ico-clr: var(--bit-chb-clr-txt-sec); + &:focus-visible { + @include focus-ring(var(--bit-chb-clr-focus)); + } + @media (hover: hover) { &:hover { --bit-chb-ico-opa: 1; @@ -19,6 +24,10 @@ &.bit-inv { --bit-chb-box-clr-brd: #{$clr-err}; + &:focus-visible { + @include focus-ring($clr-err-focus); + } + &.bit-chb-ckd { --bit-chb-box-clr-bg: #{$clr-err}; --bit-chb-box-clr-brd: #{$clr-err}; @@ -32,20 +41,20 @@ &.bit-dis { cursor: default; - color: $clr-fg-dis; + color: var(--bit-chb-clr-dis-text); pointer-events: none; - --bit-chb-ico-clr: #{$clr-fg-dis}; - --bit-chb-ico-clr-bg: #{$clr-bg-dis}; - --bit-chb-box-clr-brd: #{$clr-brd-dis}; + --bit-chb-ico-clr: var(--bit-chb-clr-dis-text); + --bit-chb-ico-clr-bg: var(--bit-chb-clr-dis); + --bit-chb-box-clr-brd: var(--bit-chb-clr-dis); &.bit-chb-ckd { - --bit-chb-ico-clr: #{$clr-fg-dis}; - --bit-chb-box-clr-bg: #{$clr-bg-dis}; - --bit-chb-box-clr-brd: #{$clr-bg-dis}; + --bit-chb-ico-clr: var(--bit-chb-clr-dis-text); + --bit-chb-box-clr-bg: var(--bit-chb-clr-dis); + --bit-chb-box-clr-brd: var(--bit-chb-clr-dis); } &.bit-chb-ind { - --bit-chb-ico-clr-bg: #{$clr-bg-dis}; + --bit-chb-ico-clr-bg: var(--bit-chb-clr-dis); } } } @@ -248,6 +257,9 @@ --bit-chb-clr-txt: #{$clr-pri-text}; --bit-chb-clr-txt-sec: #{$clr-fg-sec}; --bit-chb-clr-txt-hover: #{$clr-pri-text}; + --bit-chb-clr-dis: #{$clr-pri-dis}; + --bit-chb-clr-dis-text: #{$clr-pri-dis-text}; + --bit-chb-clr-focus: #{$clr-pri-focus}; } .bit-chb-sec { @@ -256,6 +268,9 @@ --bit-chb-clr-txt: #{$clr-sec-text}; --bit-chb-clr-txt-sec: #{$clr-fg-sec}; --bit-chb-clr-txt-hover: #{$clr-sec-text}; + --bit-chb-clr-dis: #{$clr-sec-dis}; + --bit-chb-clr-dis-text: #{$clr-sec-dis-text}; + --bit-chb-clr-focus: #{$clr-sec-focus}; } .bit-chb-ter { @@ -264,6 +279,9 @@ --bit-chb-clr-txt: #{$clr-ter-text}; --bit-chb-clr-txt-sec: #{$clr-fg-sec}; --bit-chb-clr-txt-hover: #{$clr-ter-text}; + --bit-chb-clr-dis: #{$clr-ter-dis}; + --bit-chb-clr-dis-text: #{$clr-ter-dis-text}; + --bit-chb-clr-focus: #{$clr-ter-focus}; } .bit-chb-inf { @@ -272,6 +290,9 @@ --bit-chb-clr-txt: #{$clr-inf-text}; --bit-chb-clr-txt-sec: #{$clr-fg-sec}; --bit-chb-clr-txt-hover: #{$clr-inf-text}; + --bit-chb-clr-dis: #{$clr-inf-dis}; + --bit-chb-clr-dis-text: #{$clr-inf-dis-text}; + --bit-chb-clr-focus: #{$clr-inf-focus}; } .bit-chb-suc { @@ -280,6 +301,9 @@ --bit-chb-clr-txt: #{$clr-suc-text}; --bit-chb-clr-txt-sec: #{$clr-fg-sec}; --bit-chb-clr-txt-hover: #{$clr-suc-text}; + --bit-chb-clr-dis: #{$clr-suc-dis}; + --bit-chb-clr-dis-text: #{$clr-suc-dis-text}; + --bit-chb-clr-focus: #{$clr-suc-focus}; } .bit-chb-wrn { @@ -288,6 +312,9 @@ --bit-chb-clr-txt: #{$clr-wrn-text}; --bit-chb-clr-txt-sec: #{$clr-fg-sec}; --bit-chb-clr-txt-hover: #{$clr-wrn-text}; + --bit-chb-clr-dis: #{$clr-wrn-dis}; + --bit-chb-clr-dis-text: #{$clr-wrn-dis-text}; + --bit-chb-clr-focus: #{$clr-wrn-focus}; } .bit-chb-swr { @@ -296,6 +323,9 @@ --bit-chb-clr-txt: #{$clr-swr-text}; --bit-chb-clr-txt-sec: #{$clr-fg-sec}; --bit-chb-clr-txt-hover: #{$clr-swr-text}; + --bit-chb-clr-dis: #{$clr-swr-dis}; + --bit-chb-clr-dis-text: #{$clr-swr-dis-text}; + --bit-chb-clr-focus: #{$clr-swr-focus}; } .bit-chb-err { @@ -304,6 +334,9 @@ --bit-chb-clr-txt: #{$clr-err-text}; --bit-chb-clr-txt-sec: #{$clr-fg-sec}; --bit-chb-clr-txt-hover: #{$clr-err-text}; + --bit-chb-clr-dis: #{$clr-err-dis}; + --bit-chb-clr-dis-text: #{$clr-err-dis-text}; + --bit-chb-clr-focus: #{$clr-err-focus}; } .bit-chb-pbg { @@ -312,6 +345,9 @@ --bit-chb-clr-txt: #{$clr-fg-pri}; --bit-chb-clr-txt-sec: #{$clr-bg-sec}; --bit-chb-clr-txt-hover: #{$clr-fg-pri-hover}; + --bit-chb-clr-dis: #{$clr-bg-pri-dis}; + --bit-chb-clr-dis-text: #{$clr-bg-pri-dis-text}; + --bit-chb-clr-focus: #{$clr-bg-pri-focus}; } .bit-chb-sbg { @@ -320,6 +356,9 @@ --bit-chb-clr-txt: #{$clr-fg-sec}; --bit-chb-clr-txt-sec: #{$clr-bg-sec}; --bit-chb-clr-txt-hover: #{$clr-fg-sec-hover}; + --bit-chb-clr-dis: #{$clr-bg-sec-dis}; + --bit-chb-clr-dis-text: #{$clr-bg-sec-dis-text}; + --bit-chb-clr-focus: #{$clr-bg-sec-focus}; } .bit-chb-tbg { @@ -328,6 +367,9 @@ --bit-chb-clr-txt: #{$clr-fg-ter}; --bit-chb-clr-txt-sec: #{$clr-bg-sec}; --bit-chb-clr-txt-hover: #{$clr-fg-ter-hover}; + --bit-chb-clr-dis: #{$clr-bg-ter-dis}; + --bit-chb-clr-dis-text: #{$clr-bg-ter-dis-text}; + --bit-chb-clr-focus: #{$clr-bg-ter-focus}; } .bit-chb-pfg { @@ -336,6 +378,9 @@ --bit-chb-clr-txt: #{$clr-bg-pri}; --bit-chb-clr-txt-sec: #{$clr-fg-sec}; --bit-chb-clr-txt-hover: #{$clr-bg-pri-hover}; + --bit-chb-clr-dis: #{$clr-fg-pri-dis}; + --bit-chb-clr-dis-text: #{$clr-fg-pri-dis-text}; + --bit-chb-clr-focus: #{$clr-fg-pri-focus}; } .bit-chb-sfg { @@ -344,6 +389,9 @@ --bit-chb-clr-txt: #{$clr-bg-sec}; --bit-chb-clr-txt-sec: #{$clr-fg-sec}; --bit-chb-clr-txt-hover: #{$clr-bg-sec-hover}; + --bit-chb-clr-dis: #{$clr-fg-sec-dis}; + --bit-chb-clr-dis-text: #{$clr-fg-sec-dis-text}; + --bit-chb-clr-focus: #{$clr-fg-sec-focus}; } .bit-chb-tfg { @@ -352,6 +400,9 @@ --bit-chb-clr-txt: #{$clr-bg-ter}; --bit-chb-clr-txt-sec: #{$clr-fg-sec}; --bit-chb-clr-txt-hover: #{$clr-bg-ter-hover}; + --bit-chb-clr-dis: #{$clr-fg-ter-dis}; + --bit-chb-clr-dis-text: #{$clr-fg-ter-dis-text}; + --bit-chb-clr-focus: #{$clr-fg-ter-focus}; } .bit-chb-pbr { @@ -360,6 +411,9 @@ --bit-chb-clr-txt: #{$clr-fg-pri}; --bit-chb-clr-txt-sec: #{$clr-fg-sec}; --bit-chb-clr-txt-hover: #{$clr-fg-pri-hover}; + --bit-chb-clr-dis: #{$clr-brd-pri-dis}; + --bit-chb-clr-dis-text: #{$clr-brd-pri-dis-text}; + --bit-chb-clr-focus: #{$clr-brd-pri-focus}; } .bit-chb-sbr { @@ -368,6 +422,9 @@ --bit-chb-clr-txt: #{$clr-fg-sec}; --bit-chb-clr-txt-sec: #{$clr-fg-sec}; --bit-chb-clr-txt-hover: #{$clr-fg-sec-hover}; + --bit-chb-clr-dis: #{$clr-brd-sec-dis}; + --bit-chb-clr-dis-text: #{$clr-brd-sec-dis-text}; + --bit-chb-clr-focus: #{$clr-brd-sec-focus}; } .bit-chb-tbr { @@ -376,6 +433,9 @@ --bit-chb-clr-txt: #{$clr-fg-ter}; --bit-chb-clr-txt-sec: #{$clr-fg-sec}; --bit-chb-clr-txt-hover: #{$clr-fg-ter-hover}; + --bit-chb-clr-dis: #{$clr-brd-ter-dis}; + --bit-chb-clr-dis-text: #{$clr-brd-ter-dis-text}; + --bit-chb-clr-focus: #{$clr-brd-ter-focus}; } .bit-chb-sm { diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/ChoiceGroup/BitChoiceGroup.scss b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/ChoiceGroup/BitChoiceGroup.scss index e0e852a80e..adc374bbeb 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/ChoiceGroup/BitChoiceGroup.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/ChoiceGroup/BitChoiceGroup.scss @@ -24,6 +24,11 @@ } &.bit-inv { + .bit-chg-ili:focus-visible, + input:focus-visible + .bit-chg-ili { + @include focus-ring($clr-err-focus); + } + .bit-chg-rad { color: $clr-err; border-color: $clr-err; @@ -154,9 +159,15 @@ justify-content: center; min-height: spacing(2.5); transition: all 0.2s ease; + border-radius: $shp-border-radius; padding-block-start: spacing(2.75); border: $shp-border-width $shp-border-style transparent; + &:focus-visible, + input:focus-visible + & { + @include focus-ring(var(--bit-chg-clr-focus)); + } + .bit-chg-rad { position: absolute; top: calc(var(--bit-chg-circle-size) / 4); @@ -309,21 +320,21 @@ .bit-chg-rad { cursor: default; pointer-events: none; - color: $clr-fg-dis; + color: var(--bit-chg-clr-dis-text); &::before { - border-color: $clr-brd-dis; + border-color: var(--bit-chg-clr-dis); } &::after { - background-color: $clr-bg-dis; + background-color: var(--bit-chg-clr-dis); } } .bit-chg-ili { cursor: default; border-color: transparent; - color: $clr-fg-dis; + color: var(--bit-chg-clr-dis-text); @media (hover: hover) { &:hover { @@ -333,11 +344,11 @@ } .bit-chg-icw { - color: $clr-fg-dis; + color: var(--bit-chg-clr-dis-text); } .bit-chg-ico { - color: $clr-fg-dis; + color: var(--bit-chg-clr-dis-text); } } @@ -346,6 +357,9 @@ --bit-chg-clr-bg: #{$clr-bg-sec}; --bit-chg-clr-brd: #{$clr-brd-pri}; --bit-chg-clr-hover: #{$clr-pri-hover}; + --bit-chg-clr-dis: #{$clr-pri-dis}; + --bit-chg-clr-dis-text: #{$clr-pri-dis-text}; + --bit-chg-clr-focus: #{$clr-pri-focus}; } .bit-chg-sec { @@ -353,6 +367,9 @@ --bit-chg-clr-bg: #{$clr-bg-sec}; --bit-chg-clr-brd: #{$clr-brd-sec}; --bit-chg-clr-hover: #{$clr-sec-hover}; + --bit-chg-clr-dis: #{$clr-sec-dis}; + --bit-chg-clr-dis-text: #{$clr-sec-dis-text}; + --bit-chg-clr-focus: #{$clr-sec-focus}; } .bit-chg-ter { @@ -360,6 +377,9 @@ --bit-chg-clr-bg: #{$clr-bg-sec}; --bit-chg-clr-brd: #{$clr-brd-ter}; --bit-chg-clr-hover: #{$clr-ter-hover}; + --bit-chg-clr-dis: #{$clr-ter-dis}; + --bit-chg-clr-dis-text: #{$clr-ter-dis-text}; + --bit-chg-clr-focus: #{$clr-ter-focus}; } .bit-chg-inf { @@ -367,6 +387,9 @@ --bit-chg-clr-bg: #{$clr-bg-sec}; --bit-chg-clr-brd: #{$clr-inf}; --bit-chg-clr-hover: #{$clr-inf-hover}; + --bit-chg-clr-dis: #{$clr-inf-dis}; + --bit-chg-clr-dis-text: #{$clr-inf-dis-text}; + --bit-chg-clr-focus: #{$clr-inf-focus}; } .bit-chg-suc { @@ -374,6 +397,9 @@ --bit-chg-clr-bg: #{$clr-bg-sec}; --bit-chg-clr-brd: #{$clr-suc}; --bit-chg-clr-hover: #{$clr-suc-hover}; + --bit-chg-clr-dis: #{$clr-suc-dis}; + --bit-chg-clr-dis-text: #{$clr-suc-dis-text}; + --bit-chg-clr-focus: #{$clr-suc-focus}; } .bit-chg-wrn { @@ -381,6 +407,9 @@ --bit-chg-clr-brd: #{$clr-wrn}; --bit-chg-clr-hover: #{$clr-wrn-hover}; --bit-chg-clr-bg: #{$clr-bg-sec}; + --bit-chg-clr-dis: #{$clr-wrn-dis}; + --bit-chg-clr-dis-text: #{$clr-wrn-dis-text}; + --bit-chg-clr-focus: #{$clr-wrn-focus}; } .bit-chg-swr { @@ -388,6 +417,9 @@ --bit-chg-clr-bg: #{$clr-bg-sec}; --bit-chg-clr-brd: #{$clr-swr}; --bit-chg-clr-hover: #{$clr-swr-hover}; + --bit-chg-clr-dis: #{$clr-swr-dis}; + --bit-chg-clr-dis-text: #{$clr-swr-dis-text}; + --bit-chg-clr-focus: #{$clr-swr-focus}; } .bit-chg-err { @@ -395,6 +427,9 @@ --bit-chg-clr-brd: #{$clr-err}; --bit-chg-clr-hover: #{$clr-err-hover}; --bit-chg-clr-bg: #{$clr-bg-sec}; + --bit-chg-clr-dis: #{$clr-err-dis}; + --bit-chg-clr-dis-text: #{$clr-err-dis-text}; + --bit-chg-clr-focus: #{$clr-err-focus}; } .bit-chg-pbg { @@ -402,6 +437,9 @@ --bit-chg-clr-bg: #{$clr-fg-pri}; --bit-chg-clr-brd: #{$clr-bg-pri}; --bit-chg-clr-hover: #{$clr-bg-pri-hover}; + --bit-chg-clr-dis: #{$clr-bg-pri-dis}; + --bit-chg-clr-dis-text: #{$clr-bg-pri-dis-text}; + --bit-chg-clr-focus: #{$clr-bg-pri-focus}; } .bit-chg-sbg { @@ -409,6 +447,9 @@ --bit-chg-clr-bg: #{$clr-fg-pri}; --bit-chg-clr-brd: #{$clr-bg-sec}; --bit-chg-clr-hover: #{$clr-bg-sec-hover}; + --bit-chg-clr-dis: #{$clr-bg-sec-dis}; + --bit-chg-clr-dis-text: #{$clr-bg-sec-dis-text}; + --bit-chg-clr-focus: #{$clr-bg-sec-focus}; } .bit-chg-tbg { @@ -416,6 +457,9 @@ --bit-chg-clr-brd: #{$clr-bg-ter}; --bit-chg-clr-hover: #{$clr-bg-ter-hover}; --bit-chg-clr-bg: #{$clr-fg-pri}; + --bit-chg-clr-dis: #{$clr-bg-ter-dis}; + --bit-chg-clr-dis-text: #{$clr-bg-ter-dis-text}; + --bit-chg-clr-focus: #{$clr-bg-ter-focus}; } .bit-chg-pfg { @@ -423,6 +467,9 @@ --bit-chg-clr-bg: #{$clr-bg-sec}; --bit-chg-clr-brd: #{$clr-fg-pri}; --bit-chg-clr-hover: #{$clr-fg-pri-hover}; + --bit-chg-clr-dis: #{$clr-fg-pri-dis}; + --bit-chg-clr-dis-text: #{$clr-fg-pri-dis-text}; + --bit-chg-clr-focus: #{$clr-fg-pri-focus}; } .bit-chg-sfg { @@ -430,6 +477,9 @@ --bit-chg-clr-brd: #{$clr-fg-sec}; --bit-chg-clr-hover: #{$clr-fg-sec-hover}; --bit-chg-clr-bg: #{$clr-bg-sec}; + --bit-chg-clr-dis: #{$clr-fg-sec-dis}; + --bit-chg-clr-dis-text: #{$clr-fg-sec-dis-text}; + --bit-chg-clr-focus: #{$clr-fg-sec-focus}; } .bit-chg-tfg { @@ -437,6 +487,9 @@ --bit-chg-clr-bg: #{$clr-bg-sec}; --bit-chg-clr-brd: #{$clr-fg-ter}; --bit-chg-clr-hover: #{$clr-fg-ter-hover}; + --bit-chg-clr-dis: #{$clr-fg-ter-dis}; + --bit-chg-clr-dis-text: #{$clr-fg-ter-dis-text}; + --bit-chg-clr-focus: #{$clr-fg-ter-focus}; } .bit-chg-pbr { @@ -444,6 +497,9 @@ --bit-chg-clr-brd: #{$clr-brd-pri}; --bit-chg-clr-hover: #{$clr-brd-pri-hover}; --bit-chg-clr-bg: #{$clr-bg-sec}; + --bit-chg-clr-dis: #{$clr-brd-pri-dis}; + --bit-chg-clr-dis-text: #{$clr-brd-pri-dis-text}; + --bit-chg-clr-focus: #{$clr-brd-pri-focus}; } .bit-chg-sbr { @@ -451,6 +507,9 @@ --bit-chg-clr-bg: #{$clr-bg-sec}; --bit-chg-clr-brd: #{$clr-brd-sec}; --bit-chg-clr-hover: #{$clr-brd-sec-hover}; + --bit-chg-clr-dis: #{$clr-brd-sec-dis}; + --bit-chg-clr-dis-text: #{$clr-brd-sec-dis-text}; + --bit-chg-clr-focus: #{$clr-brd-sec-focus}; } .bit-chg-tbr { @@ -458,6 +517,9 @@ --bit-chg-clr-bg: #{$clr-bg-sec}; --bit-chg-clr-brd: #{$clr-brd-ter}; --bit-chg-clr-hover: #{$clr-brd-ter-hover}; + --bit-chg-clr-dis: #{$clr-brd-ter-dis}; + --bit-chg-clr-dis-text: #{$clr-brd-ter-dis-text}; + --bit-chg-clr-focus: #{$clr-brd-ter-focus}; } .bit-chg-sm { diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/CircularTimePicker/BitCircularTimePicker.scss b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/CircularTimePicker/BitCircularTimePicker.scss index 969a882103..18b7f5c122 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/CircularTimePicker/BitCircularTimePicker.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/CircularTimePicker/BitCircularTimePicker.scss @@ -44,6 +44,20 @@ .bit-ctp-icn { border-color: $clr-err; } + + &.bit-ctp-und .bit-ctp-wrp { + // Underlined variant drops the icon border, so surface the error cue on the + // underline itself to keep the invalid state visible before focus. + border-bottom-color: $clr-err; + } + + &.bit-ctp-foc:not(.bit-ctp-und) .bit-ctp-icn { + @include focus-ring($clr-err-focus); + } + + &.bit-ctp-und.bit-ctp-foc .bit-ctp-icn { + @include focus-underline-ring($clr-err-focus); + } } } @@ -121,14 +135,9 @@ } } -.bit-ctp-foc { - .bit-ctp-icn::after { - content: ""; - position: absolute; - pointer-events: none; - inset: spacing(-0.125); - border-radius: $shp-border-radius; - border: spacing(0.25) $shp-border-style $clr-pri; +.bit-ctp-foc:not(.bit-ctp-und) { + .bit-ctp-icn { + @include focus-ring; } } @@ -152,10 +161,8 @@ } &.bit-ctp-foc { - .bit-ctp-icn::after { - border: none; - border-radius: 0; - border-bottom: spacing(0.25) $shp-border-style $clr-brd-pri; + .bit-ctp-icn { + @include focus-underline-ring; } } } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/DatePicker/BitDatePicker.scss b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/DatePicker/BitDatePicker.scss index 6d8fc51efa..a45adb4f16 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/DatePicker/BitDatePicker.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/DatePicker/BitDatePicker.scss @@ -50,6 +50,20 @@ .bit-dtp-icn { border-color: $clr-err; } + + &.bit-dtp-und .bit-dtp-wrp { + // Underlined variant drops the icon border, so surface the error cue on the + // underline itself to keep the invalid state visible before focus. + border-bottom-color: $clr-err; + } + + &.bit-dtp-foc:not(.bit-dtp-und) .bit-dtp-icn { + @include focus-ring($clr-err-focus); + } + + &.bit-dtp-und.bit-dtp-foc .bit-dtp-icn { + @include focus-underline-ring($clr-err-focus); + } } &.bit-rtl { @@ -162,14 +176,9 @@ } } -.bit-dtp-foc { - .bit-dtp-icn::after { - content: ""; - position: absolute; - pointer-events: none; - inset: spacing(-0.125); - border-radius: $shp-border-radius; - border: spacing(0.25) $shp-border-style $clr-pri; +.bit-dtp-foc:not(.bit-dtp-und) { + .bit-dtp-icn { + @include focus-ring; } } @@ -655,9 +664,8 @@ } &:focus { + @include focus-underline-ring; border: none; - border-bottom: $shp-border-width $shp-border-style $clr-brd-pri-active; - outline: none; } &:disabled { @@ -722,10 +730,8 @@ } &.bit-dtp-foc { - .bit-dtp-icn::after { - border: none; - border-radius: 0; - border-bottom: spacing(0.25) $shp-border-style $clr-brd-pri; + .bit-dtp-icn { + @include focus-underline-ring; } } } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/DateRangePicker/BitDateRangePicker.scss b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/DateRangePicker/BitDateRangePicker.scss index 0add6f91e9..3f1124ff79 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/DateRangePicker/BitDateRangePicker.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/DateRangePicker/BitDateRangePicker.scss @@ -47,9 +47,23 @@ } &.bit-inv { - .bit-dtrp-icp { + .bit-dtrp-icn { border-color: $clr-err; } + + &.bit-dtrp-und .bit-dtrp-wrp { + // Underlined variant drops the icon border, so surface the error cue on the + // underline itself to keep the invalid state visible before focus. + border-bottom-color: $clr-err; + } + + &.bit-dtrp-foc:not(.bit-dtrp-und) .bit-dtrp-icn { + @include focus-ring($clr-err-focus); + } + + &.bit-dtrp-und.bit-dtrp-foc .bit-dtrp-icn { + @include focus-underline-ring($clr-err-focus); + } } &.bit-rtl { @@ -162,14 +176,9 @@ } } -.bit-dtrp-foc { - .bit-dtrp-icn::after { - content: ""; - position: absolute; - pointer-events: none; - inset: spacing(-0.125); - border-radius: $shp-border-radius; - border: spacing(0.25) $shp-border-style $clr-pri; +.bit-dtrp-foc:not(.bit-dtrp-und) { + .bit-dtrp-icn { + @include focus-ring; } } @@ -688,9 +697,8 @@ } &:focus { + @include focus-underline-ring; border: none; - outline: none; - border-bottom: $shp-border-width $shp-border-style $clr-brd-pri-active; } &:disabled { @@ -766,10 +774,8 @@ } &.bit-dtrp-foc { - .bit-dtrp-icn::after { - border: none; - border-radius: 0; - border-bottom: spacing(0.25) $shp-border-style $clr-brd-pri; + .bit-dtrp-icn { + @include focus-underline-ring; } } } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.scss b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.scss index c1dddd43b9..424eb27b87 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.scss @@ -31,8 +31,19 @@ } &:focus { - &::after { - border-color: $clr-err; + @include focus-ring($clr-err-focus); + } + } + + &.bit-drp-nbd { + .bit-drp-wrp { + &:focus { + outline: none; + box-shadow: none; + } + + &:focus-visible { + @include focus-underline-ring($clr-err-focus); } } } @@ -54,12 +65,9 @@ &:focus { color: $clr-fg-dis; border-color: $clr-brd-dis; - - &::after { - width: 0; - height: 0; - border: none; - } + // Suppress the focus ring while disabled, including the forced-colors outline. + outline: none; + box-shadow: none; } } @@ -105,9 +113,16 @@ } &:focus { - &::after { - border: none; - } + // Suppress the global focus ring on the no-border variant; the underline-ring + // below still applies for keyboard focus via :focus-visible. + outline: none; + box-shadow: none; + } + + &:focus-visible { + // No-border variant: keep the no-border look but use an inset underline-style + // ring as a keyboard-only, high-contrast focus cue (WCAG 2.4.7 / 2.4.13). + @include focus-underline-ring(var(--bit-drp-clr-focus)); } } } @@ -170,14 +185,7 @@ color: $clr-fg-pri; } - &::after { - content: ""; - position: absolute; - pointer-events: none; - inset: spacing(-0.125); - border-radius: $shp-border-radius; - border: spacing(0.25) $shp-border-style var(--bit-drp-clr); - } + @include focus-ring(var(--bit-drp-clr-focus)); } } @@ -805,100 +813,117 @@ --bit-drp-clr: #{$clr-pri}; --bit-drp-clr-hover: #{$clr-pri-hover}; --bit-drp-clr-text: #{$clr-pri-text}; + --bit-drp-clr-focus: #{$clr-pri-focus}; } .bit-drp-sec { --bit-drp-clr: #{$clr-sec}; --bit-drp-clr-hover: #{$clr-sec-hover}; --bit-drp-clr-text: #{$clr-sec-text}; + --bit-drp-clr-focus: #{$clr-sec-focus}; } .bit-drp-ter { --bit-drp-clr: #{$clr-ter}; --bit-drp-clr-hover: #{$clr-ter-hover}; --bit-drp-clr-text: #{$clr-ter-text}; + --bit-drp-clr-focus: #{$clr-ter-focus}; } .bit-drp-inf { --bit-drp-clr: #{$clr-inf}; --bit-drp-clr-hover: #{$clr-inf-hover}; --bit-drp-clr-text: #{$clr-inf-text}; + --bit-drp-clr-focus: #{$clr-inf-focus}; } .bit-drp-suc { --bit-drp-clr: #{$clr-suc}; --bit-drp-clr-hover: #{$clr-suc-hover}; --bit-drp-clr-text: #{$clr-suc-text}; + --bit-drp-clr-focus: #{$clr-suc-focus}; } .bit-drp-wrn { --bit-drp-clr: #{$clr-wrn}; --bit-drp-clr-hover: #{$clr-wrn-hover}; --bit-drp-clr-text: #{$clr-wrn-text}; + --bit-drp-clr-focus: #{$clr-wrn-focus}; } .bit-drp-swr { --bit-drp-clr: #{$clr-swr}; --bit-drp-clr-hover: #{$clr-swr-hover}; --bit-drp-clr-text: #{$clr-swr-text}; + --bit-drp-clr-focus: #{$clr-swr-focus}; } .bit-drp-err { --bit-drp-clr: #{$clr-err}; --bit-drp-clr-hover: #{$clr-err-hover}; --bit-drp-clr-text: #{$clr-err-text}; + --bit-drp-clr-focus: #{$clr-err-focus}; } .bit-drp-pbg { --bit-drp-clr: #{$clr-bg-pri}; --bit-drp-clr-hover: #{$clr-bg-pri-hover}; --bit-drp-clr-text: #{$clr-fg-pri}; + --bit-drp-clr-focus: #{$clr-bg-pri-focus}; } .bit-drp-sbg { --bit-drp-clr: #{$clr-bg-sec}; --bit-drp-clr-hover: #{$clr-bg-sec-hover}; --bit-drp-clr-text: #{$clr-fg-sec}; + --bit-drp-clr-focus: #{$clr-bg-sec-focus}; } .bit-drp-tbg { --bit-drp-clr: #{$clr-bg-ter}; --bit-drp-clr-hover: #{$clr-bg-ter-hover}; --bit-drp-clr-text: #{$clr-fg-ter}; + --bit-drp-clr-focus: #{$clr-bg-ter-focus}; } .bit-drp-pfg { --bit-drp-clr: #{$clr-fg-pri}; --bit-drp-clr-hover: #{$clr-fg-pri-hover}; --bit-drp-clr-text: #{$clr-bg-pri}; + --bit-drp-clr-focus: #{$clr-fg-pri-focus}; } .bit-drp-sfg { --bit-drp-clr: #{$clr-fg-sec}; --bit-drp-clr-hover: #{$clr-fg-sec-hover}; --bit-drp-clr-text: #{$clr-bg-sec}; + --bit-drp-clr-focus: #{$clr-fg-sec-focus}; } .bit-drp-tfg { --bit-drp-clr: #{$clr-fg-ter}; --bit-drp-clr-hover: #{$clr-fg-ter-hover}; --bit-drp-clr-text: #{$clr-bg-ter}; + --bit-drp-clr-focus: #{$clr-fg-ter-focus}; } .bit-drp-pbr { --bit-drp-clr: #{$clr-brd-pri}; --bit-drp-clr-hover: #{$clr-brd-pri-hover}; --bit-drp-clr-text: #{$clr-fg-pri}; + --bit-drp-clr-focus: #{$clr-brd-pri-focus}; } .bit-drp-sbr { --bit-drp-clr: #{$clr-brd-sec}; --bit-drp-clr-hover: #{$clr-brd-sec-hover}; --bit-drp-clr-text: #{$clr-fg-sec}; + --bit-drp-clr-focus: #{$clr-brd-sec-focus}; } .bit-drp-tbr { --bit-drp-clr: #{$clr-brd-ter}; --bit-drp-clr-hover: #{$clr-brd-ter-hover}; --bit-drp-clr-text: #{$clr-fg-ter}; + --bit-drp-clr-focus: #{$clr-brd-ter-focus}; } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/NumberField/BitNumberField.scss b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/NumberField/BitNumberField.scss index 19c1e37c05..63aa870255 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/NumberField/BitNumberField.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/NumberField/BitNumberField.scss @@ -52,10 +52,8 @@ border-color: $clr-err; } - &.bit-nfl-fcs { - .bit-nfl-cnt::after { - border-color: $clr-err; - } + &.bit-nfl-fcs .bit-nfl-cnt { + @include focus-ring($clr-err-focus); } } @@ -259,13 +257,8 @@ } .bit-nfl-fcs { - .bit-nfl-cnt::after { - content: ""; - position: absolute; - pointer-events: none; - inset: spacing(-0.125); - border-radius: $shp-border-radius; - border: spacing(0.25) $shp-border-style $clr-pri; + .bit-nfl-cnt { + @include focus-ring; } } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/OtpInput/BitOtpInput.scss b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/OtpInput/BitOtpInput.scss index 120f6352fd..ebbdfdea11 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/OtpInput/BitOtpInput.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/OtpInput/BitOtpInput.scss @@ -23,6 +23,10 @@ .bit-otp-inp { outline: none; border-color: $clr-err; + + &:focus-visible { + @include focus-ring($clr-err-focus); + } } } } @@ -65,9 +69,8 @@ -webkit-appearance: none } - &:focus { - outline: none; - border: spacing(0.25) $shp-border-style $clr-pri; + &:focus-visible { + @include focus-ring; } } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.scss b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.scss index d218bd57eb..64b7e8a17e 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.scss @@ -12,42 +12,42 @@ pointer-events: none; .bit-srb-cnt { - color: $clr-fg-dis; - border-color: $clr-brd-dis; - background-color: $clr-bg-dis; + color: var(--bit-srb-clr-dis-text); + border-color: var(--bit-srb-clr-dis); + background-color: var(--bit-srb-clr-dis); } .bit-srb-iwp { i { - color: $clr-fg-dis; + color: var(--bit-srb-clr-dis-text); } } .bit-srb-pre, .bit-srb-suf { - color: $clr-fg-dis; + color: var(--bit-srb-clr-dis-text); } @media (hover: hover) { &:hover { .bit-srb-cnt { - border-color: $clr-brd-dis; + border-color: var(--bit-srb-clr-dis); } .bit-srb-iwp { i { - color: $clr-fg-dis; + color: var(--bit-srb-clr-dis-text); } } } } .bit-srb-sbn { - border-color: $clr-brd-dis; - background-color: $clr-bg-dis; + border-color: var(--bit-srb-clr-dis); + background-color: var(--bit-srb-clr-dis); i { - color: $clr-fg-dis; + color: var(--bit-srb-clr-dis-text); } } @@ -84,13 +84,14 @@ } &.bit-srb-foc { - .bit-srb-cnt::after { - border-color: $clr-err; + &:not(.bit-srb-und) .bit-srb-cnt { + @include focus-ring($clr-err-focus); } &.bit-srb-und { - .bit-srb-cnt::after { - border-color: $clr-err; + .bit-srb-cnt, + .bit-srb-sbn { + @include focus-underline-ring($clr-err-focus); } } } @@ -111,7 +112,7 @@ &.bit-srb-foc { .bit-srb-sbn { - border-color: var(--bit-srb-clr); + border-color: var(--bit-srb-clr-focus); } } @@ -343,35 +344,27 @@ } .bit-srb-sbn { - border-color: var(--bit-srb-clr); + border-color: var(--bit-srb-clr-focus); } - .bit-srb-cnt::after { - content: ""; - position: absolute; - pointer-events: none; - inset: spacing(-0.125); - border-radius: $shp-border-radius; - border: spacing(0.25) $shp-border-style var(--bit-srb-clr); + &:not(.bit-srb-und) .bit-srb-cnt { + @include focus-ring(var(--bit-srb-clr-focus)); } &.bit-srb-und { - .bit-srb-cnt::after, - .bit-srb-sbn::after { - content: ""; - border: none; - position: absolute; - pointer-events: none; - inset: spacing(-0.125); - border-bottom: spacing(0.25) $shp-border-style var(--bit-srb-clr); + .bit-srb-cnt, + .bit-srb-sbn { + @include focus-underline-ring(var(--bit-srb-clr-focus)); } } } .bit-srb-nbr { - .bit-srb-cnt, - .bit-srb-cnt::after { + .bit-srb-cnt { border: none; + + // Keep box-shadow unset here so the focus-ring mixin applied via .bit-srb-foc + // (and any :focus / :focus-visible states) can still render the focus indicator. } .bit-srb-sbn { @@ -554,6 +547,9 @@ --bit-srb-clr-txt: #{$clr-pri-text}; --bit-srb-clr-hover: #{$clr-pri-hover}; --bit-srb-clr-active: #{$clr-pri-active}; + --bit-srb-clr-focus: #{$clr-pri-focus}; + --bit-srb-clr-dis: #{$clr-pri-dis}; + --bit-srb-clr-dis-text: #{$clr-pri-dis-text}; } .bit-srb-sec { @@ -561,6 +557,9 @@ --bit-srb-clr-txt: #{$clr-sec-text}; --bit-srb-clr-hover: #{$clr-sec-hover}; --bit-srb-clr-active: #{$clr-sec-active}; + --bit-srb-clr-focus: #{$clr-sec-focus}; + --bit-srb-clr-dis: #{$clr-sec-dis}; + --bit-srb-clr-dis-text: #{$clr-sec-dis-text}; } .bit-srb-ter { @@ -568,6 +567,9 @@ --bit-srb-clr-txt: #{$clr-ter-text}; --bit-srb-clr-hover: #{$clr-ter-hover}; --bit-srb-clr-active: #{$clr-ter-active}; + --bit-srb-clr-focus: #{$clr-ter-focus}; + --bit-srb-clr-dis: #{$clr-ter-dis}; + --bit-srb-clr-dis-text: #{$clr-ter-dis-text}; } .bit-srb-inf { @@ -575,6 +577,9 @@ --bit-srb-clr-txt: #{$clr-inf-text}; --bit-srb-clr-hover: #{$clr-inf-hover}; --bit-srb-clr-active: #{$clr-inf-active}; + --bit-srb-clr-focus: #{$clr-inf-focus}; + --bit-srb-clr-dis: #{$clr-inf-dis}; + --bit-srb-clr-dis-text: #{$clr-inf-dis-text}; } .bit-srb-suc { @@ -582,6 +587,9 @@ --bit-srb-clr-txt: #{$clr-suc-text}; --bit-srb-clr-hover: #{$clr-suc-hover}; --bit-srb-clr-active: #{$clr-suc-active}; + --bit-srb-clr-focus: #{$clr-suc-focus}; + --bit-srb-clr-dis: #{$clr-suc-dis}; + --bit-srb-clr-dis-text: #{$clr-suc-dis-text}; } .bit-srb-wrn { @@ -589,6 +597,9 @@ --bit-srb-clr-txt: #{$clr-wrn-text}; --bit-srb-clr-hover: #{$clr-wrn-hover}; --bit-srb-clr-active: #{$clr-wrn-active}; + --bit-srb-clr-focus: #{$clr-wrn-focus}; + --bit-srb-clr-dis: #{$clr-wrn-dis}; + --bit-srb-clr-dis-text: #{$clr-wrn-dis-text}; } .bit-srb-swr { @@ -596,6 +607,9 @@ --bit-srb-clr-txt: #{$clr-swr-text}; --bit-srb-clr-hover: #{$clr-swr-hover}; --bit-srb-clr-active: #{$clr-swr-active}; + --bit-srb-clr-focus: #{$clr-swr-focus}; + --bit-srb-clr-dis: #{$clr-swr-dis}; + --bit-srb-clr-dis-text: #{$clr-swr-dis-text}; } .bit-srb-err { @@ -603,6 +617,9 @@ --bit-srb-clr-txt: #{$clr-err-text}; --bit-srb-clr-hover: #{$clr-err-hover}; --bit-srb-clr-active: #{$clr-err-active}; + --bit-srb-clr-focus: #{$clr-err-focus}; + --bit-srb-clr-dis: #{$clr-err-dis}; + --bit-srb-clr-dis-text: #{$clr-err-dis-text}; } .bit-srb-pbg { @@ -610,6 +627,9 @@ --bit-srb-clr-txt: #{$clr-fg-pri}; --bit-srb-clr-hover: #{$clr-bg-pri-hover}; --bit-srb-clr-active: #{$clr-bg-pri-active}; + --bit-srb-clr-focus: #{$clr-bg-pri-focus}; + --bit-srb-clr-dis: #{$clr-bg-pri-dis}; + --bit-srb-clr-dis-text: #{$clr-bg-pri-dis-text}; } .bit-srb-sbg { @@ -617,6 +637,9 @@ --bit-srb-clr-txt: #{$clr-fg-pri}; --bit-srb-clr-hover: #{$clr-bg-sec-hover}; --bit-srb-clr-active: #{$clr-bg-sec-active}; + --bit-srb-clr-focus: #{$clr-bg-sec-focus}; + --bit-srb-clr-dis: #{$clr-bg-sec-dis}; + --bit-srb-clr-dis-text: #{$clr-bg-sec-dis-text}; } .bit-srb-tbg { @@ -624,6 +647,9 @@ --bit-srb-clr-txt: #{$clr-fg-pri}; --bit-srb-clr-hover: #{$clr-bg-ter-hover}; --bit-srb-clr-active: #{$clr-bg-ter-active}; + --bit-srb-clr-focus: #{$clr-bg-ter-focus}; + --bit-srb-clr-dis: #{$clr-bg-ter-dis}; + --bit-srb-clr-dis-text: #{$clr-bg-ter-dis-text}; } .bit-srb-pfg { @@ -631,6 +657,9 @@ --bit-srb-clr-txt: #{$clr-bg-pri}; --bit-srb-clr-hover: #{$clr-fg-pri-hover}; --bit-srb-clr-active: #{$clr-fg-pri-active}; + --bit-srb-clr-focus: #{$clr-fg-pri-focus}; + --bit-srb-clr-dis: #{$clr-fg-pri-dis}; + --bit-srb-clr-dis-text: #{$clr-fg-pri-dis-text}; } .bit-srb-sfg { @@ -638,6 +667,9 @@ --bit-srb-clr-txt: #{$clr-bg-pri}; --bit-srb-clr-hover: #{$clr-fg-sec-hover}; --bit-srb-clr-active: #{$clr-fg-sec-active}; + --bit-srb-clr-focus: #{$clr-fg-sec-focus}; + --bit-srb-clr-dis: #{$clr-fg-sec-dis}; + --bit-srb-clr-dis-text: #{$clr-fg-sec-dis-text}; } .bit-srb-tfg { @@ -645,6 +677,9 @@ --bit-srb-clr-txt: #{$clr-bg-pri}; --bit-srb-clr-hover: #{$clr-fg-ter-hover}; --bit-srb-clr-active: #{$clr-fg-ter-active}; + --bit-srb-clr-focus: #{$clr-fg-ter-focus}; + --bit-srb-clr-dis: #{$clr-fg-ter-dis}; + --bit-srb-clr-dis-text: #{$clr-fg-ter-dis-text}; } .bit-srb-pbr { @@ -652,6 +687,9 @@ --bit-srb-clr-txt: #{$clr-fg-pri}; --bit-srb-clr-hover: #{$clr-brd-pri-hover}; --bit-srb-clr-active: #{$clr-brd-pri-active}; + --bit-srb-clr-focus: #{$clr-brd-pri-focus}; + --bit-srb-clr-dis: #{$clr-brd-pri-dis}; + --bit-srb-clr-dis-text: #{$clr-brd-pri-dis-text}; } .bit-srb-sbr { @@ -659,6 +697,9 @@ --bit-srb-clr-txt: #{$clr-fg-pri}; --bit-srb-clr-hover: #{$clr-brd-sec-hover}; --bit-srb-clr-active: #{$clr-brd-sec-active}; + --bit-srb-clr-focus: #{$clr-brd-sec-focus}; + --bit-srb-clr-dis: #{$clr-brd-sec-dis}; + --bit-srb-clr-dis-text: #{$clr-brd-sec-dis-text}; } .bit-srb-tbr { @@ -666,4 +707,7 @@ --bit-srb-clr-txt: #{$clr-fg-pri}; --bit-srb-clr-hover: #{$clr-brd-ter-hover}; --bit-srb-clr-active: #{$clr-brd-ter-active}; + --bit-srb-clr-focus: #{$clr-brd-ter-focus}; + --bit-srb-clr-dis: #{$clr-brd-ter-dis}; + --bit-srb-clr-dis-text: #{$clr-brd-ter-dis-text}; } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Slider/BitSlider.scss b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Slider/BitSlider.scss index 1e82079532..c4b35b0db6 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Slider/BitSlider.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Slider/BitSlider.scss @@ -15,11 +15,40 @@ $BgHover: $clr-pri-light; background: none; &:focus { - outline: none; + outline: none; // The visible focus indicator lives on the thumb pseudo-element below. } } - /**/ - /*webkit*/ + /* webkit focus styles */ + input:focus-visible::-webkit-slider-thumb, + input:focus::-webkit-slider-thumb { + border-color: $clr-pri-focus; + + // Reference the global focus-ring shadow token so forced-colors and theme-level + // overrides (see forced-colors.fluent.scss) propagate to the slider thumb. + box-shadow: var(--bit-shd-focus-ring, + 0 0 0 #{$shp-focus-ring-offset} #{$clr-bg-pri-focus}, + 0 0 0 calc(#{$shp-focus-ring-offset} + #{$shp-focus-ring-width}) #{$clr-pri-focus}); + + // In Windows High Contrast / forced-colors mode box-shadows are stripped, which would leave + // the focused thumb with no visible indicator. Restore a real outline (matching focus-ring). + @media (forced-colors: active) { + outline: #{$shp-focus-ring-width} solid Highlight; + } + } + + input:focus-visible::-moz-range-thumb, + input:focus::-moz-range-thumb { + border-color: $clr-pri-focus; + box-shadow: var(--bit-shd-focus-ring, + 0 0 0 #{$shp-focus-ring-offset} #{$clr-bg-pri-focus}, + 0 0 0 calc(#{$shp-focus-ring-offset} + #{$shp-focus-ring-width}) #{$clr-pri-focus}); + + @media (forced-colors: active) { + outline: #{$shp-focus-ring-width} solid Highlight; + } + } + + /* webkit thumb */ input::-webkit-slider-thumb { width: spacing(2); height: spacing(2); @@ -43,8 +72,7 @@ $BgHover: $clr-pri-light; input::-webkit-slider-runnable-track { background: linear-gradient($BgTrack, $BgTrack) 0/var(--sx) 100% no-repeat, $BgDefult; } - /**/ - /*mozilla*/ + /* mozilla */ input::-moz-range-thumb { width: spacing(2); height: spacing(2); @@ -67,8 +95,7 @@ $BgHover: $clr-pri-light; input::-moz-range-track { background: linear-gradient($BgTrack, $BgTrack) 0/var(--sx) 100% no-repeat, $BgDefult; } - /**/ - /*ms*/ + /* ms */ input::-ms-fill-upper { background: transparent; border-color: transparent; diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/TagsInput/BitTagsInput.scss b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/TagsInput/BitTagsInput.scss index 4f9dcf4c00..d7aebfb981 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/TagsInput/BitTagsInput.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/TagsInput/BitTagsInput.scss @@ -23,6 +23,12 @@ .bit-tgi-cnt { border-color: $clr-err; } + + &.bit-tgi-fcs .bit-tgi-cnt { + @include focus-ring($clr-err-focus); + + border-color: $clr-err-focus; + } } @media (hover: hover) { @@ -42,19 +48,9 @@ .bit-tgi-fcs { .bit-tgi-cnt { - border-color: $clr-pri; - outline: none; - - &::after { - content: ""; - height: 2px; - left: -1px; - right: -1px; - bottom: -1px; - position: absolute; - background-color: $clr-pri; - border-radius: 0 0 $shp-border-radius $shp-border-radius; - } + @include focus-ring; + + border-color: $clr-pri-focus; } } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/TextField/BitTextField.scss b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/TextField/BitTextField.scss index 0075196c42..4f8d713a31 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/TextField/BitTextField.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/TextField/BitTextField.scss @@ -69,13 +69,13 @@ } &.bit-tfl-fcs { - .bit-tfl-fgp::after { - border-color: $clr-err; + &:not(.bit-tfl-und) .bit-tfl-fgp { + @include focus-ring($clr-err-focus); } &.bit-tfl-und { - .bit-tfl-wrp::after { - border-color: $clr-err; + .bit-tfl-wrp { + @include focus-underline-ring($clr-err-focus); } } } @@ -196,7 +196,7 @@ font-family: $tg-font-family; background-color: transparent; border-radius: 0 spacing(0.125) spacing(0.125) 0; - min-height: calc(var(--bit-spa-scaling-factor) * 3.75); + min-height: spacing(3.75); i { width: unset; @@ -313,26 +313,15 @@ color: var(--bit-tfl-clr); } - .bit-tfl-fgp::after { - content: ""; - position: absolute; - pointer-events: none; - inset: spacing(-0.125); - border-radius: $shp-border-radius; - border: spacing(0.25) $shp-border-style var(--bit-tfl-clr); + // The underlined variant is excluded so it gets only the bottom-rule treatment below - applying + // the ring mixin and negating its box-shadow would still leave its forced-colors outline behind. + &:not(.bit-tfl-und) .bit-tfl-fgp { + @include focus-ring(var(--bit-tfl-clr-focus)); } &.bit-tfl-und { - .bit-tfl-fgp::after { - border: none; - } - - .bit-tfl-wrp::after { - content: ""; - position: absolute; - pointer-events: none; - inset: spacing(-0.125); - border-bottom: spacing(0.25) $shp-border-style var(--bit-tfl-clr); + .bit-tfl-wrp { + @include focus-underline-ring(var(--bit-tfl-clr-focus)); } } } @@ -427,70 +416,87 @@ .bit-tfl-pri { --bit-tfl-clr: #{$clr-pri}; + --bit-tfl-clr-focus: #{$clr-pri-focus}; } .bit-tfl-sec { --bit-tfl-clr: #{$clr-sec}; + --bit-tfl-clr-focus: #{$clr-sec-focus}; } .bit-tfl-ter { --bit-tfl-clr: #{$clr-ter}; + --bit-tfl-clr-focus: #{$clr-ter-focus}; } .bit-tfl-inf { --bit-tfl-clr: #{$clr-inf}; + --bit-tfl-clr-focus: #{$clr-inf-focus}; } .bit-tfl-suc { --bit-tfl-clr: #{$clr-suc}; + --bit-tfl-clr-focus: #{$clr-suc-focus}; } .bit-tfl-wrn { --bit-tfl-clr: #{$clr-wrn}; + --bit-tfl-clr-focus: #{$clr-wrn-focus}; } .bit-tfl-swr { --bit-tfl-clr: #{$clr-swr}; + --bit-tfl-clr-focus: #{$clr-swr-focus}; } .bit-tfl-err { --bit-tfl-clr: #{$clr-err}; + --bit-tfl-clr-focus: #{$clr-err-focus}; } .bit-tfl-pbg { --bit-tfl-clr: #{$clr-bg-pri}; + --bit-tfl-clr-focus: #{$clr-bg-pri-focus}; } .bit-tfl-sbg { --bit-tfl-clr: #{$clr-bg-sec}; + --bit-tfl-clr-focus: #{$clr-bg-sec-focus}; } .bit-tfl-tbg { --bit-tfl-clr: #{$clr-bg-ter}; + --bit-tfl-clr-focus: #{$clr-bg-ter-focus}; } .bit-tfl-pfg { --bit-tfl-clr: #{$clr-fg-pri}; + --bit-tfl-clr-focus: #{$clr-fg-pri-focus}; } .bit-tfl-sfg { --bit-tfl-clr: #{$clr-fg-sec}; + --bit-tfl-clr-focus: #{$clr-fg-sec-focus}; } .bit-tfl-tfg { --bit-tfl-clr: #{$clr-fg-ter}; + --bit-tfl-clr-focus: #{$clr-fg-ter-focus}; } .bit-tfl-pbr { --bit-tfl-clr: #{$clr-brd-pri}; + --bit-tfl-clr-focus: #{$clr-brd-pri-focus}; } .bit-tfl-sbr { --bit-tfl-clr: #{$clr-brd-sec}; + --bit-tfl-clr-focus: #{$clr-brd-sec-focus}; } .bit-tfl-tbr { --bit-tfl-clr: #{$clr-brd-ter}; + --bit-tfl-clr-focus: #{$clr-brd-ter-focus}; } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/TimePicker/BitTimePicker.scss b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/TimePicker/BitTimePicker.scss index 881531c550..ab3d952d13 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/TimePicker/BitTimePicker.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/TimePicker/BitTimePicker.scss @@ -41,6 +41,20 @@ .bit-tpc-icn { border-color: $clr-err; } + + &.bit-tpc-und .bit-tpc-wrp { + // Underlined variant drops the icon border, so surface the error cue on the + // underline itself to keep the invalid state visible before focus. + border-bottom-color: $clr-err; + } + + &.bit-tpc-foc:not(.bit-tpc-und) .bit-tpc-icn { + @include focus-ring($clr-err-focus); + } + + &.bit-tpc-und.bit-tpc-foc .bit-tpc-icn { + @include focus-underline-ring($clr-err-focus); + } } &.bit-rtl { @@ -128,14 +142,9 @@ } } -.bit-tpc-foc { - .bit-tpc-icn::after { - content: ""; - position: absolute; - pointer-events: none; - inset: spacing(-0.125); - border-radius: $shp-border-radius; - border: spacing(0.25) $shp-border-style $clr-pri; +.bit-tpc-foc:not(.bit-tpc-und) { + .bit-tpc-icn { + @include focus-ring; } } @@ -159,10 +168,8 @@ } &.bit-tpc-foc { - .bit-tpc-icn::after { - border: none; - border-radius: 0; - border-bottom: spacing(0.25) $shp-border-style $clr-brd-pri; + .bit-tpc-icn { + @include focus-underline-ring; } } } @@ -281,9 +288,8 @@ } &:focus { + @include focus-underline-ring; border: none; - border-bottom: $shp-border-width $shp-border-style $clr-brd-pri-active; - outline: none; } } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Toggle/BitToggle.scss b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Toggle/BitToggle.scss index 7fcabb560e..4bdccccb21 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Toggle/BitToggle.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Toggle/BitToggle.scss @@ -48,6 +48,10 @@ &.bit-inv { .bit-tgl-btn { border-color: $clr-err; + + &:focus-visible { + @include focus-ring($clr-err-focus); + } } .bit-tgl-stx { @@ -120,6 +124,10 @@ border-radius: spacing(1.25); border: $shp-border-width $shp-border-style $clr-brd-pri; + &:focus-visible { + @include focus-ring; + } + @media (hover: hover) { &:hover { border-color: $clr-brd-pri-hover; diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Layouts/Grid/BitGrid.scss b/src/BlazorUI/Bit.BlazorUI/Components/Layouts/Grid/BitGrid.scss index 319e63626e..edda089fa8 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Layouts/Grid/BitGrid.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Layouts/Grid/BitGrid.scss @@ -1,4 +1,4 @@ -@import "../../../Styles/functions.scss"; +@import "../../../Styles/functions.scss"; @import "../../../Styles/media-queries.scss"; .bit-grd { @@ -13,7 +13,7 @@ .bit-grd-itm { flex-basis: calc((100% / var(--columns) * var(--span)) - var(--spacing)); - $breakpoints: ( 'xs': $brk-sm-min, 'sm': $brk-md-min, 'md': $brk-lg-min, 'lg': $brk-xl-min, 'xl': $brk-xxl-min ); + $breakpoints: ( 'xs': 0, 'sm': $mq-sm-min, 'md': $mq-md-min, 'lg': $mq-lg-min, 'xl': $mq-xl-min, 'xxl': $mq-xxl-min ); @each $name, $breakpoint in $breakpoints { @media (min-width: $breakpoint) { diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Lists/Timeline/BitTimeline.scss b/src/BlazorUI/Bit.BlazorUI/Components/Lists/Timeline/BitTimeline.scss index 26d1728e43..b343510fd6 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Lists/Timeline/BitTimeline.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Lists/Timeline/BitTimeline.scss @@ -17,9 +17,9 @@ --bit-tln-dvd-top: 0; --bit-tln-dvd-height: 100%; --bit-tln-dvd-width: #{spacing(0.25)}; - --bit-tln-clr-dis: #{$clr-fg-dis}; - --bit-tln-clr-bg-dis: #{$clr-bg-dis}; - --bit-tln-clr-brd-dis: #{$clr-brd-dis}; + --bit-tln-clr-dis: var(--bit-tln-clr-dis-text, #{$clr-fg-dis}); + --bit-tln-clr-bg-dis: var(--bit-tln-clr-dis-bg, #{$clr-bg-dis}); + --bit-tln-clr-brd-dis: var(--bit-tln-clr-dis-bg, #{$clr-bg-dis}); &.bit-dis { cursor: default; @@ -49,7 +49,7 @@ user-select: none; pointer-events: none; color: var(--bit-tln-clr-dis); - --bit-tln-dot-ico-clr: #{$clr-fg-dis}; + --bit-tln-dot-ico-clr: var(--bit-tln-clr-dis); } } @@ -181,48 +181,64 @@ --bit-tln-clr: #{$clr-pri}; --bit-tln-clr-fg: #{$clr-pri-text}; --bit-tln-clr-ico: #{$clr-pri-text}; + --bit-tln-clr-dis-bg: #{$clr-pri-dis}; + --bit-tln-clr-dis-text: #{$clr-pri-dis-text}; } .bit-tln-sec, .bit-tln-ise { --bit-tln-clr: #{$clr-sec}; --bit-tln-clr-fg: #{$clr-sec-text}; --bit-tln-clr-ico: #{$clr-sec-text}; + --bit-tln-clr-dis-bg: #{$clr-sec-dis}; + --bit-tln-clr-dis-text: #{$clr-sec-dis-text}; } .bit-tln-ter, .bit-tln-ite { --bit-tln-clr: #{$clr-ter}; --bit-tln-clr-fg: #{$clr-ter-text}; --bit-tln-clr-ico: #{$clr-ter-text}; + --bit-tln-clr-dis-bg: #{$clr-ter-dis}; + --bit-tln-clr-dis-text: #{$clr-ter-dis-text}; } .bit-tln-inf, .bit-tln-iin { --bit-tln-clr: #{$clr-inf}; --bit-tln-clr-fg: #{$clr-inf-text}; --bit-tln-clr-ico: #{$clr-inf-text}; + --bit-tln-clr-dis-bg: #{$clr-inf-dis}; + --bit-tln-clr-dis-text: #{$clr-inf-dis-text}; } .bit-tln-suc, .bit-tln-isu { --bit-tln-clr: #{$clr-suc}; --bit-tln-clr-fg: #{$clr-suc-text}; --bit-tln-clr-ico: #{$clr-suc-text}; + --bit-tln-clr-dis-bg: #{$clr-suc-dis}; + --bit-tln-clr-dis-text: #{$clr-suc-dis-text}; } .bit-tln-wrn, .bit-tln-iwr { --bit-tln-clr: #{$clr-wrn}; --bit-tln-clr-fg: #{$clr-wrn-text}; --bit-tln-clr-ico: #{$clr-wrn-text}; + --bit-tln-clr-dis-bg: #{$clr-wrn-dis}; + --bit-tln-clr-dis-text: #{$clr-wrn-dis-text}; } .bit-tln-swr, .bit-tln-isw { --bit-tln-clr: #{$clr-swr}; --bit-tln-clr-fg: #{$clr-swr-text}; --bit-tln-clr-ico: #{$clr-swr-text}; + --bit-tln-clr-dis-bg: #{$clr-swr-dis}; + --bit-tln-clr-dis-text: #{$clr-swr-dis-text}; } .bit-tln-err, .bit-tln-ier { --bit-tln-clr: #{$clr-err}; --bit-tln-clr-fg: #{$clr-err-text}; --bit-tln-clr-ico: #{$clr-err-text}; + --bit-tln-clr-dis-bg: #{$clr-err-dis}; + --bit-tln-clr-dis-text: #{$clr-err-dis-text}; } .bit-tln-sm, .bit-tln-ism { diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.scss b/src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.scss index 49baafcb2e..b49c8bc608 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.scss @@ -67,6 +67,10 @@ background-color: $clr-bg-pri-hover; } } + + &:focus-visible { + @include focus-ring; + } } .bit-brc-itm, @@ -88,6 +92,10 @@ background-color: $clr-bg-pri-hover; } } + + &:focus-visible { + @include focus-ring; + } } .bit-brc-rvi { @@ -146,6 +154,11 @@ line-height: spacing(2.5); background-color: transparent; border: $shp-border-width $shp-border-style transparent; + border-radius: $shp-border-radius; + + &:focus-visible { + @include focus-ring; + } } .bit-brc-ofi { diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Navs/DropMenu/BitDropMenu.scss b/src/BlazorUI/Bit.BlazorUI/Components/Navs/DropMenu/BitDropMenu.scss index face6d7303..965cac30e4 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Navs/DropMenu/BitDropMenu.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Navs/DropMenu/BitDropMenu.scss @@ -50,6 +50,11 @@ justify-content: center; background-color: transparent; padding: spacing(1.0) spacing(1.5); + border-radius: $shp-border-radius; + + &:focus-visible { + @include focus-ring; + } } .bit-drm-trn { diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Navs/NavBar/BitNavBar.scss b/src/BlazorUI/Bit.BlazorUI/Components/Navs/NavBar/BitNavBar.scss index 07576906f2..b1a1954607 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Navs/NavBar/BitNavBar.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Navs/NavBar/BitNavBar.scss @@ -9,7 +9,7 @@ pointer-events: none; .bit-nbr-itm { - color: $clr-fg-dis; + color: var(--bit-nbr-clr-dis, var(--bit-nbr-clr, currentColor)); } } } @@ -64,75 +64,92 @@ } .bit-nbr-dis { - color: $clr-fg-dis; + color: var(--bit-nbr-clr-dis, var(--bit-nbr-clr, currentColor)); pointer-events: none; } .bit-nbr-pri { --bit-nbr-clr: #{$clr-pri}; + --bit-nbr-clr-dis: #{$clr-pri-dis-text}; } .bit-nbr-sec { --bit-nbr-clr: #{$clr-sec}; + --bit-nbr-clr-dis: #{$clr-sec-dis-text}; } .bit-nbr-ter { --bit-nbr-clr: #{$clr-ter}; + --bit-nbr-clr-dis: #{$clr-ter-dis-text}; } .bit-nbr-inf { --bit-nbr-clr: #{$clr-inf}; + --bit-nbr-clr-dis: #{$clr-inf-dis-text}; } .bit-nbr-suc { --bit-nbr-clr: #{$clr-suc}; + --bit-nbr-clr-dis: #{$clr-suc-dis-text}; } .bit-nbr-wrn { --bit-nbr-clr: #{$clr-wrn}; + --bit-nbr-clr-dis: #{$clr-wrn-dis-text}; } .bit-nbr-swr { --bit-nbr-clr: #{$clr-swr}; + --bit-nbr-clr-dis: #{$clr-swr-dis-text}; } .bit-nbr-err { --bit-nbr-clr: #{$clr-err}; + --bit-nbr-clr-dis: #{$clr-err-dis-text}; } .bit-nbr-pbg { --bit-nbr-clr: #{$clr-bg-pri}; + --bit-nbr-clr-dis: #{$clr-bg-pri-dis-text}; } .bit-nbr-sbg { --bit-nbr-clr: #{$clr-bg-sec}; + --bit-nbr-clr-dis: #{$clr-bg-sec-dis-text}; } .bit-nbr-tbg { --bit-nbr-clr: #{$clr-bg-ter}; + --bit-nbr-clr-dis: #{$clr-bg-ter-dis-text}; } .bit-nbr-pfg { --bit-nbr-clr: #{$clr-fg-pri}; + --bit-nbr-clr-dis: #{$clr-fg-pri-dis-text}; } .bit-nbr-sfg { --bit-nbr-clr: #{$clr-fg-sec}; + --bit-nbr-clr-dis: #{$clr-fg-sec-dis-text}; } .bit-nbr-tfg { --bit-nbr-clr: #{$clr-fg-ter}; + --bit-nbr-clr-dis: #{$clr-fg-ter-dis-text}; } .bit-nbr-pbr { --bit-nbr-clr: #{$clr-brd-pri}; + --bit-nbr-clr-dis: #{$clr-brd-pri-dis-text}; } .bit-nbr-sbr { --bit-nbr-clr: #{$clr-brd-sec}; + --bit-nbr-clr-dis: #{$clr-brd-sec-dis-text}; } .bit-nbr-tbr { --bit-nbr-clr: #{$clr-brd-ter}; + --bit-nbr-clr-dis: #{$clr-brd-ter-dis-text}; } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Navs/Pagination/BitPagination.scss b/src/BlazorUI/Bit.BlazorUI/Components/Navs/Pagination/BitPagination.scss index be21d2f680..1b30c4b65c 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Navs/Pagination/BitPagination.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Navs/Pagination/BitPagination.scss @@ -8,8 +8,8 @@ font-size: var(--bit-pgn-font-size); --bit-pgn-ico-transform-end: scaleX(1); --bit-pgn-ico-transform-start: scaleX(-1); - --bit-pgn-clr-dis: #{$clr-bg-dis}; - --bit-pgn-clr-txt-dis: #{$clr-fg-dis}; + --bit-pgn-clr-dis: var(--bit-pgn-clr-dis-bg, #{$clr-bg-dis}); + --bit-pgn-clr-txt-dis: var(--bit-pgn-clr-dis-text, #{$clr-fg-dis}); --bit-pgn-clr-btn-txt-hover: var(--bit-pgn-clr-txt); --bit-pgn-clr-btn-bg-hover: var(--bit-pgn-clr-hover); --bit-pgn-clr-btn-brd-hover: var(--bit-pgn-clr-hover); @@ -34,8 +34,8 @@ &.bit-dis { cursor: default; - color: $clr-fg-dis; pointer-events: none; + color: var(--bit-pgn-clr-txt-dis); } } @@ -90,7 +90,6 @@ &[disabled] { cursor: default; - color: $clr-fg-dis; pointer-events: none; color: var(--bit-pgn-clr-btn-txt-dis); border-color: var(--bit-pgn-clr-btn-brd-dis); @@ -151,6 +150,8 @@ --bit-pgn-clr-sel: #{$clr-pri-dark}; --bit-pgn-clr-sel-hover: #{$clr-pri-dark-hover}; --bit-pgn-clr-sel-active: #{$clr-pri-dark-active}; + --bit-pgn-clr-dis-bg: #{$clr-pri-dis}; + --bit-pgn-clr-dis-text: #{$clr-pri-dis-text}; } .bit-pgn-sec { @@ -161,6 +162,8 @@ --bit-pgn-clr-sel: #{$clr-sec-dark}; --bit-pgn-clr-sel-hover: #{$clr-sec-dark-hover}; --bit-pgn-clr-sel-active: #{$clr-sec-dark-active}; + --bit-pgn-clr-dis-bg: #{$clr-sec-dis}; + --bit-pgn-clr-dis-text: #{$clr-sec-dis-text}; } .bit-pgn-ter { @@ -171,6 +174,8 @@ --bit-pgn-clr-sel: #{$clr-ter-dark}; --bit-pgn-clr-sel-hover: #{$clr-ter-dark-hover}; --bit-pgn-clr-sel-active: #{$clr-ter-dark-active}; + --bit-pgn-clr-dis-bg: #{$clr-ter-dis}; + --bit-pgn-clr-dis-text: #{$clr-ter-dis-text}; } .bit-pgn-inf { @@ -181,6 +186,8 @@ --bit-pgn-clr-sel: #{$clr-inf-dark}; --bit-pgn-clr-sel-hover: #{$clr-inf-dark-hover}; --bit-pgn-clr-sel-active: #{$clr-inf-dark-active}; + --bit-pgn-clr-dis-bg: #{$clr-inf-dis}; + --bit-pgn-clr-dis-text: #{$clr-inf-dis-text}; } .bit-pgn-suc { @@ -191,6 +198,8 @@ --bit-pgn-clr-sel: #{$clr-suc-dark}; --bit-pgn-clr-sel-hover: #{$clr-suc-dark-hover}; --bit-pgn-clr-sel-active: #{$clr-suc-dark-active}; + --bit-pgn-clr-dis-bg: #{$clr-suc-dis}; + --bit-pgn-clr-dis-text: #{$clr-suc-dis-text}; } .bit-pgn-wrn { @@ -201,6 +210,8 @@ --bit-pgn-clr-sel: #{$clr-wrn-dark}; --bit-pgn-clr-sel-hover: #{$clr-wrn-dark-hover}; --bit-pgn-clr-sel-active: #{$clr-wrn-dark-active}; + --bit-pgn-clr-dis-bg: #{$clr-wrn-dis}; + --bit-pgn-clr-dis-text: #{$clr-wrn-dis-text}; } .bit-pgn-swr { @@ -211,6 +222,8 @@ --bit-pgn-clr-sel: #{$clr-swr-dark}; --bit-pgn-clr-sel-hover: #{$clr-swr-dark-hover}; --bit-pgn-clr-sel-active: #{$clr-swr-dark-active}; + --bit-pgn-clr-dis-bg: #{$clr-swr-dis}; + --bit-pgn-clr-dis-text: #{$clr-swr-dis-text}; } .bit-pgn-err { @@ -221,6 +234,8 @@ --bit-pgn-clr-sel: #{$clr-err-dark}; --bit-pgn-clr-sel-hover: #{$clr-err-dark-hover}; --bit-pgn-clr-sel-active: #{$clr-err-dark-active}; + --bit-pgn-clr-dis-bg: #{$clr-err-dis}; + --bit-pgn-clr-dis-text: #{$clr-err-dis-text}; } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Navs/Pivot/BitPivot.scss b/src/BlazorUI/Bit.BlazorUI/Components/Navs/Pivot/BitPivot.scss index b3b1abdc66..802b901d2c 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Navs/Pivot/BitPivot.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Navs/Pivot/BitPivot.scss @@ -8,17 +8,17 @@ .content-container { cursor: default; pointer-events: none; - color: $clr-fg-dis; + color: var(--bit-pvt-clr-dis-text); } .bit-pvti { cursor: default; pointer-events: none; - color: $clr-fg-dis; + color: var(--bit-pvt-clr-dis-text); } .bit-pvti-sel::before { - background-color: $clr-brd-dis; + background-color: var(--bit-pvt-clr-dis); } } } @@ -227,6 +227,10 @@ background-color: transparent; color: $clr-fg-pri; + &:focus-visible { + @include focus-ring(var(--bit-pvt-clr-focus)); + } + > span { height: 100%; display: flex; @@ -239,10 +243,10 @@ &.bit-dis { cursor: default; pointer-events: none; - color: $clr-fg-dis; + color: var(--bit-pvt-clr-dis-text); &.bit-pvti-sel::before { - background-color: $clr-brd-dis; + background-color: var(--bit-pvt-clr-dis); } } } @@ -266,6 +270,10 @@ line-height: spacing(5.5); background-color: transparent; + &:focus-visible { + @include focus-ring(var(--bit-pvt-clr-focus)); + } + &::before { bottom: 0; content: ""; @@ -308,104 +316,155 @@ .bit-pvt-pri { --bit-pvt-clr: #{$clr-pri}; --bit-pvt-clr-hover: #{$clr-pri-hover}; + --bit-pvt-clr-focus: #{$clr-pri-focus}; --bit-pvt-clr-text: #{$clr-pri-text}; + --bit-pvt-clr-dis: #{$clr-pri-dis}; + --bit-pvt-clr-dis-text: #{$clr-pri-dis-text}; } .bit-pvt-sec { --bit-pvt-clr: #{$clr-sec}; --bit-pvt-clr-hover: #{$clr-sec-hover}; + --bit-pvt-clr-focus: #{$clr-sec-focus}; --bit-pvt-clr-text: #{$clr-sec-text}; + --bit-pvt-clr-dis: #{$clr-sec-dis}; + --bit-pvt-clr-dis-text: #{$clr-sec-dis-text}; } .bit-pvt-ter { --bit-pvt-clr: #{$clr-ter}; --bit-pvt-clr-hover: #{$clr-ter-hover}; + --bit-pvt-clr-focus: #{$clr-ter-focus}; --bit-pvt-clr-text: #{$clr-ter-text}; + --bit-pvt-clr-dis: #{$clr-ter-dis}; + --bit-pvt-clr-dis-text: #{$clr-ter-dis-text}; } .bit-pvt-inf { --bit-pvt-clr: #{$clr-inf}; --bit-pvt-clr-hover: #{$clr-inf-hover}; + --bit-pvt-clr-focus: #{$clr-inf-focus}; --bit-pvt-clr-text: #{$clr-inf-text}; + --bit-pvt-clr-dis: #{$clr-inf-dis}; + --bit-pvt-clr-dis-text: #{$clr-inf-dis-text}; } .bit-pvt-suc { --bit-pvt-clr: #{$clr-suc}; --bit-pvt-clr-hover: #{$clr-suc-hover}; + --bit-pvt-clr-focus: #{$clr-suc-focus}; --bit-pvt-clr-text: #{$clr-suc-text}; + --bit-pvt-clr-dis: #{$clr-suc-dis}; + --bit-pvt-clr-dis-text: #{$clr-suc-dis-text}; } .bit-pvt-wrn { --bit-pvt-clr: #{$clr-wrn}; --bit-pvt-clr-hover: #{$clr-wrn-hover}; + --bit-pvt-clr-focus: #{$clr-wrn-focus}; --bit-pvt-clr-text: #{$clr-wrn-text}; + --bit-pvt-clr-dis: #{$clr-wrn-dis}; + --bit-pvt-clr-dis-text: #{$clr-wrn-dis-text}; } .bit-pvt-swr { --bit-pvt-clr: #{$clr-swr}; --bit-pvt-clr-hover: #{$clr-swr-hover}; + --bit-pvt-clr-focus: #{$clr-swr-focus}; --bit-pvt-clr-text: #{$clr-swr-text}; + --bit-pvt-clr-dis: #{$clr-swr-dis}; + --bit-pvt-clr-dis-text: #{$clr-swr-dis-text}; } .bit-pvt-err { --bit-pvt-clr: #{$clr-err}; --bit-pvt-clr-hover: #{$clr-err-hover}; + --bit-pvt-clr-focus: #{$clr-err-focus}; --bit-pvt-clr-text: #{$clr-err-text}; + --bit-pvt-clr-dis: #{$clr-err-dis}; + --bit-pvt-clr-dis-text: #{$clr-err-dis-text}; } .bit-pvt-pbg { --bit-pvt-clr: #{$clr-bg-pri}; --bit-pvt-clr-hover: #{$clr-bg-pri-hover}; + --bit-pvt-clr-focus: #{$clr-bg-pri-focus}; --bit-pvt-clr-text: #{$clr-fg-pri}; + --bit-pvt-clr-dis: #{$clr-bg-pri-dis}; + --bit-pvt-clr-dis-text: #{$clr-bg-pri-dis-text}; } .bit-pvt-sbg { --bit-pvt-clr: #{$clr-bg-sec}; --bit-pvt-clr-hover: #{$clr-bg-sec-hover}; + --bit-pvt-clr-focus: #{$clr-bg-sec-focus}; --bit-pvt-clr-text: #{$clr-fg-pri}; + --bit-pvt-clr-dis: #{$clr-bg-sec-dis}; + --bit-pvt-clr-dis-text: #{$clr-bg-sec-dis-text}; } .bit-pvt-tbg { --bit-pvt-clr: #{$clr-bg-ter}; --bit-pvt-clr-hover: #{$clr-bg-ter-hover}; + --bit-pvt-clr-focus: #{$clr-bg-ter-focus}; --bit-pvt-clr-text: #{$clr-fg-pri}; + --bit-pvt-clr-dis: #{$clr-bg-ter-dis}; + --bit-pvt-clr-dis-text: #{$clr-bg-ter-dis-text}; } .bit-pvt-pfg { --bit-pvt-clr: #{$clr-fg-pri}; --bit-pvt-clr-hover: #{$clr-fg-pri-hover}; + --bit-pvt-clr-focus: #{$clr-fg-pri-focus}; --bit-pvt-clr-text: #{$clr-bg-pri}; + --bit-pvt-clr-dis: #{$clr-fg-pri-dis}; + --bit-pvt-clr-dis-text: #{$clr-fg-pri-dis-text}; } .bit-pvt-sfg { --bit-pvt-clr: #{$clr-fg-sec}; --bit-pvt-clr-hover: #{$clr-fg-sec-hover}; + --bit-pvt-clr-focus: #{$clr-fg-sec-focus}; --bit-pvt-clr-text: #{$clr-bg-pri}; + --bit-pvt-clr-dis: #{$clr-fg-sec-dis}; + --bit-pvt-clr-dis-text: #{$clr-fg-sec-dis-text}; } .bit-pvt-tfg { --bit-pvt-clr: #{$clr-fg-ter}; --bit-pvt-clr-hover: #{$clr-fg-ter-hover}; + --bit-pvt-clr-focus: #{$clr-fg-ter-focus}; --bit-pvt-clr-text: #{$clr-bg-pri}; + --bit-pvt-clr-dis: #{$clr-fg-ter-dis}; + --bit-pvt-clr-dis-text: #{$clr-fg-ter-dis-text}; } .bit-pvt-pbr { --bit-pvt-clr: #{$clr-brd-pri}; --bit-pvt-clr-hover: #{$clr-brd-pri-hover}; + --bit-pvt-clr-focus: #{$clr-brd-pri-focus}; --bit-pvt-clr-text: #{$clr-fg-pri}; + --bit-pvt-clr-dis: #{$clr-brd-pri-dis}; + --bit-pvt-clr-dis-text: #{$clr-brd-pri-dis-text}; } .bit-pvt-sbr { --bit-pvt-clr: #{$clr-brd-sec}; --bit-pvt-clr-hover: #{$clr-brd-sec-hover}; + --bit-pvt-clr-focus: #{$clr-brd-sec-focus}; --bit-pvt-clr-text: #{$clr-fg-pri}; + --bit-pvt-clr-dis: #{$clr-brd-sec-dis}; + --bit-pvt-clr-dis-text: #{$clr-brd-sec-dis-text}; } .bit-pvt-tbr { --bit-pvt-clr: #{$clr-brd-ter}; --bit-pvt-clr-hover: #{$clr-brd-ter-hover}; + --bit-pvt-clr-focus: #{$clr-brd-ter-focus}; --bit-pvt-clr-text: #{$clr-fg-pri}; + --bit-pvt-clr-dis: #{$clr-brd-ter-dis}; + --bit-pvt-clr-dis-text: #{$clr-brd-ter-dis-text}; } // #region Slide overflow behavior diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Notifications/Badge/BitBadge.scss b/src/BlazorUI/Bit.BlazorUI/Components/Notifications/Badge/BitBadge.scss index 29312bc1cc..21de67150c 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Notifications/Badge/BitBadge.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Notifications/Badge/BitBadge.scss @@ -5,7 +5,7 @@ display: inline-block; &.bit-dis { - --bit-bdg-cnt-clr-txt: #{$clr-fg-dis}; + --bit-bdg-cnt-clr-txt: var(--bit-bdg-clr-dis-text); --bit-bdg-cnt-clr-bg: var(--bit-bdg-clr-bg-dis); --bit-bdg-cnt-clr-brd: var(--bit-bdg-clr-brd-dis); } @@ -292,8 +292,8 @@ .bit-bdg-fil { - --bit-bdg-clr-bg-dis: #{$clr-bg-dis}; - --bit-bdg-clr-brd-dis: #{$clr-brd-dis}; + --bit-bdg-clr-bg-dis: var(--bit-bdg-clr-dis); + --bit-bdg-clr-brd-dis: var(--bit-bdg-clr-dis); --bit-bdg-cnt-clr-bg: var(--bit-bdg-clr); --bit-bdg-cnt-clr-brd: var(--bit-bdg-clr); --bit-bdg-cnt-clr-txt: var(--bit-bdg-clr-txt); @@ -301,7 +301,8 @@ .bit-bdg-otl { --bit-bdg-cnt-clr-bg: transparent; - --bit-bdg-clr-brd-dis: #{$clr-brd-dis}; + --bit-bdg-clr-bg-dis: transparent; + --bit-bdg-clr-brd-dis: var(--bit-bdg-clr-dis); --bit-bdg-cnt-clr-txt: var(--bit-bdg-clr); --bit-bdg-cnt-clr-brd: var(--bit-bdg-clr); } @@ -309,6 +310,7 @@ .bit-bdg-txt { --bit-bdg-cnt-clr-bg: transparent; --bit-bdg-cnt-clr-brd: transparent; + --bit-bdg-clr-bg-dis: transparent; --bit-bdg-clr-brd-dis: transparent; --bit-bdg-cnt-clr-txt: var(--bit-bdg-clr); } @@ -317,41 +319,57 @@ .bit-bdg-pri { --bit-bdg-clr: #{$clr-pri}; --bit-bdg-clr-txt: #{$clr-pri-text}; + --bit-bdg-clr-dis: #{$clr-pri-dis}; + --bit-bdg-clr-dis-text: #{$clr-pri-dis-text}; } .bit-bdg-sec { --bit-bdg-clr: #{$clr-sec}; --bit-bdg-clr-txt: #{$clr-sec-text}; + --bit-bdg-clr-dis: #{$clr-sec-dis}; + --bit-bdg-clr-dis-text: #{$clr-sec-dis-text}; } .bit-bdg-ter { --bit-bdg-clr: #{$clr-ter}; --bit-bdg-clr-txt: #{$clr-ter-text}; + --bit-bdg-clr-dis: #{$clr-ter-dis}; + --bit-bdg-clr-dis-text: #{$clr-ter-dis-text}; } .bit-bdg-inf { --bit-bdg-clr: #{$clr-inf}; --bit-bdg-clr-txt: #{$clr-inf-text}; + --bit-bdg-clr-dis: #{$clr-inf-dis}; + --bit-bdg-clr-dis-text: #{$clr-inf-dis-text}; } .bit-bdg-suc { --bit-bdg-clr: #{$clr-suc}; --bit-bdg-clr-txt: #{$clr-suc-text}; + --bit-bdg-clr-dis: #{$clr-suc-dis}; + --bit-bdg-clr-dis-text: #{$clr-suc-dis-text}; } .bit-bdg-wrn { --bit-bdg-clr: #{$clr-wrn}; --bit-bdg-clr-txt: #{$clr-wrn-text}; + --bit-bdg-clr-dis: #{$clr-wrn-dis}; + --bit-bdg-clr-dis-text: #{$clr-wrn-dis-text}; } .bit-bdg-swr { --bit-bdg-clr: #{$clr-swr}; --bit-bdg-clr-txt: #{$clr-swr-text}; + --bit-bdg-clr-dis: #{$clr-swr-dis}; + --bit-bdg-clr-dis-text: #{$clr-swr-dis-text}; } .bit-bdg-err { --bit-bdg-clr: #{$clr-err}; --bit-bdg-clr-txt: #{$clr-err-text}; + --bit-bdg-clr-dis: #{$clr-err-dis}; + --bit-bdg-clr-dis-text: #{$clr-err-dis-text}; } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Notifications/Tag/BitTag.scss b/src/BlazorUI/Bit.BlazorUI/Components/Notifications/Tag/BitTag.scss index dba1b9dab6..9effece144 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Notifications/Tag/BitTag.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Notifications/Tag/BitTag.scss @@ -18,7 +18,6 @@ &.bit-dis { cursor: default; user-select: none; - color: $clr-fg-dis; pointer-events: none; } } @@ -45,8 +44,9 @@ background-color: var(--bit-tag-clr); &.bit-dis { - border-color: $clr-brd-dis; - background-color: $clr-bg-dis; + color: var(--bit-tag-clr-dis-text); + border-color: var(--bit-tag-clr-dis); + background-color: var(--bit-tag-clr-dis); } } @@ -56,7 +56,8 @@ background-color: transparent; &.bit-dis { - border-color: $clr-brd-dis; + color: var(--bit-tag-clr-dis-text); + border-color: var(--bit-tag-clr-dis); background-color: transparent; } } @@ -67,6 +68,7 @@ background-color: transparent; &.bit-dis { + color: var(--bit-tag-clr-dis-text); border-color: transparent; background-color: transparent; } @@ -75,86 +77,120 @@ .bit-tag-pri { --bit-tag-clr: #{$clr-pri}; --bit-tag-clr-txt: #{$clr-pri-text}; + --bit-tag-clr-dis: #{$clr-pri-dis}; + --bit-tag-clr-dis-text: #{$clr-pri-dis-text}; } .bit-tag-sec { --bit-tag-clr: #{$clr-sec}; --bit-tag-clr-txt: #{$clr-sec-text}; + --bit-tag-clr-dis: #{$clr-sec-dis}; + --bit-tag-clr-dis-text: #{$clr-sec-dis-text}; } .bit-tag-ter { --bit-tag-clr: #{$clr-ter}; --bit-tag-clr-txt: #{$clr-ter-text}; + --bit-tag-clr-dis: #{$clr-ter-dis}; + --bit-tag-clr-dis-text: #{$clr-ter-dis-text}; } .bit-tag-inf { --bit-tag-clr: #{$clr-inf}; --bit-tag-clr-txt: #{$clr-inf-text}; + --bit-tag-clr-dis: #{$clr-inf-dis}; + --bit-tag-clr-dis-text: #{$clr-inf-dis-text}; } .bit-tag-suc { --bit-tag-clr: #{$clr-suc}; --bit-tag-clr-txt: #{$clr-suc-text}; + --bit-tag-clr-dis: #{$clr-suc-dis}; + --bit-tag-clr-dis-text: #{$clr-suc-dis-text}; } .bit-tag-wrn { --bit-tag-clr: #{$clr-wrn}; --bit-tag-clr-txt: #{$clr-wrn-text}; + --bit-tag-clr-dis: #{$clr-wrn-dis}; + --bit-tag-clr-dis-text: #{$clr-wrn-dis-text}; } .bit-tag-swr { --bit-tag-clr: #{$clr-swr}; --bit-tag-clr-txt: #{$clr-swr-text}; + --bit-tag-clr-dis: #{$clr-swr-dis}; + --bit-tag-clr-dis-text: #{$clr-swr-dis-text}; } .bit-tag-err { --bit-tag-clr: #{$clr-err}; --bit-tag-clr-txt: #{$clr-err-text}; + --bit-tag-clr-dis: #{$clr-err-dis}; + --bit-tag-clr-dis-text: #{$clr-err-dis-text}; } .bit-tag-pbg { --bit-tag-clr: #{$clr-bg-pri}; --bit-tag-clr-txt: #{$clr-fg-pri}; + --bit-tag-clr-dis: #{$clr-bg-pri-dis}; + --bit-tag-clr-dis-text: #{$clr-bg-pri-dis-text}; } .bit-tag-sbg { --bit-tag-clr: #{$clr-bg-sec}; --bit-tag-clr-txt: #{$clr-fg-pri}; + --bit-tag-clr-dis: #{$clr-bg-sec-dis}; + --bit-tag-clr-dis-text: #{$clr-bg-sec-dis-text}; } .bit-tag-tbg { --bit-tag-clr: #{$clr-bg-ter}; --bit-tag-clr-txt: #{$clr-fg-pri}; + --bit-tag-clr-dis: #{$clr-bg-ter-dis}; + --bit-tag-clr-dis-text: #{$clr-bg-ter-dis-text}; } .bit-tag-pfg { --bit-tag-clr: #{$clr-fg-pri}; --bit-tag-clr-txt: #{$clr-bg-pri}; + --bit-tag-clr-dis: #{$clr-fg-pri-dis}; + --bit-tag-clr-dis-text: #{$clr-fg-pri-dis-text}; } .bit-tag-sfg { --bit-tag-clr: #{$clr-fg-sec}; --bit-tag-clr-txt: #{$clr-bg-pri}; + --bit-tag-clr-dis: #{$clr-fg-sec-dis}; + --bit-tag-clr-dis-text: #{$clr-fg-sec-dis-text}; } .bit-tag-tfg { --bit-tag-clr: #{$clr-fg-ter}; --bit-tag-clr-txt: #{$clr-bg-pri}; + --bit-tag-clr-dis: #{$clr-fg-ter-dis}; + --bit-tag-clr-dis-text: #{$clr-fg-ter-dis-text}; } .bit-tag-pbr { --bit-tag-clr: #{$clr-brd-pri}; --bit-tag-clr-txt: #{$clr-fg-pri}; + --bit-tag-clr-dis: #{$clr-brd-pri-dis}; + --bit-tag-clr-dis-text: #{$clr-brd-pri-dis-text}; } .bit-tag-sbr { --bit-tag-clr: #{$clr-brd-sec}; --bit-tag-clr-txt: #{$clr-fg-pri}; + --bit-tag-clr-dis: #{$clr-brd-sec-dis}; + --bit-tag-clr-dis-text: #{$clr-brd-sec-dis-text}; } .bit-tag-tbr { --bit-tag-clr: #{$clr-brd-ter}; --bit-tag-clr-txt: #{$clr-fg-pri}; + --bit-tag-clr-dis: #{$clr-brd-ter-dis}; + --bit-tag-clr-dis-text: #{$clr-brd-ter-dis-text}; } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Utilities/Icon/BitIcon.scss b/src/BlazorUI/Bit.BlazorUI/Components/Utilities/Icon/BitIcon.scss index ad9a4a9583..ee4f143c0d 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Utilities/Icon/BitIcon.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Utilities/Icon/BitIcon.scss @@ -7,7 +7,7 @@ border-width: $shp-border-width; &.bit-dis { - color: $clr-fg-dis; + color: var(--bit-ico-clr-dis-text); } } @@ -16,12 +16,24 @@ color: var(--bit-ico-clr-txt); border-color: var(--bit-ico-clr); background-color: var(--bit-ico-clr); + + &.bit-dis { + color: var(--bit-ico-clr-dis-text); + border-color: var(--bit-ico-clr-dis); + background-color: var(--bit-ico-clr-dis); + } } .bit-ico-out { color: var(--bit-ico-clr); border-color: var(--bit-ico-clr); background-color: transparent; + + &.bit-dis { + color: var(--bit-ico-clr-dis-text); + border-color: var(--bit-ico-clr-dis); + background-color: transparent; + } } .bit-ico-txt { @@ -30,92 +42,130 @@ background-color: transparent; border: none; padding: 0; + + &.bit-dis { + color: var(--bit-ico-clr-dis-text); + } } .bit-ico-pri { --bit-ico-clr: #{$clr-pri}; --bit-ico-clr-txt: #{$clr-pri-text}; + --bit-ico-clr-dis: #{$clr-pri-dis}; + --bit-ico-clr-dis-text: #{$clr-pri-dis-text}; } .bit-ico-sec { --bit-ico-clr: #{$clr-sec}; --bit-ico-clr-txt: #{$clr-sec-text}; + --bit-ico-clr-dis: #{$clr-sec-dis}; + --bit-ico-clr-dis-text: #{$clr-sec-dis-text}; } .bit-ico-ter { --bit-ico-clr: #{$clr-ter}; --bit-ico-clr-txt: #{$clr-ter-text}; + --bit-ico-clr-dis: #{$clr-ter-dis}; + --bit-ico-clr-dis-text: #{$clr-ter-dis-text}; } .bit-ico-inf { --bit-ico-clr: #{$clr-inf}; --bit-ico-clr-txt: #{$clr-inf-text}; + --bit-ico-clr-dis: #{$clr-inf-dis}; + --bit-ico-clr-dis-text: #{$clr-inf-dis-text}; } .bit-ico-suc { --bit-ico-clr: #{$clr-suc}; --bit-ico-clr-txt: #{$clr-suc-text}; + --bit-ico-clr-dis: #{$clr-suc-dis}; + --bit-ico-clr-dis-text: #{$clr-suc-dis-text}; } .bit-ico-wrn { --bit-ico-clr: #{$clr-wrn}; --bit-ico-clr-txt: #{$clr-wrn-text}; + --bit-ico-clr-dis: #{$clr-wrn-dis}; + --bit-ico-clr-dis-text: #{$clr-wrn-dis-text}; } .bit-ico-swr { --bit-ico-clr: #{$clr-swr}; --bit-ico-clr-txt: #{$clr-swr-text}; + --bit-ico-clr-dis: #{$clr-swr-dis}; + --bit-ico-clr-dis-text: #{$clr-swr-dis-text}; } .bit-ico-err { --bit-ico-clr: #{$clr-err}; --bit-ico-clr-txt: #{$clr-err-text}; + --bit-ico-clr-dis: #{$clr-err-dis}; + --bit-ico-clr-dis-text: #{$clr-err-dis-text}; } .bit-ico-pbg { --bit-ico-clr: #{$clr-bg-pri}; --bit-ico-clr-txt: #{$clr-fg-pri}; + --bit-ico-clr-dis: #{$clr-bg-pri-dis}; + --bit-ico-clr-dis-text: #{$clr-bg-pri-dis-text}; } .bit-ico-sbg { --bit-ico-clr: #{$clr-bg-sec}; --bit-ico-clr-txt: #{$clr-fg-sec}; + --bit-ico-clr-dis: #{$clr-bg-sec-dis}; + --bit-ico-clr-dis-text: #{$clr-bg-sec-dis-text}; } .bit-ico-tbg { --bit-ico-clr: #{$clr-bg-ter}; --bit-ico-clr-txt: #{$clr-fg-ter}; + --bit-ico-clr-dis: #{$clr-bg-ter-dis}; + --bit-ico-clr-dis-text: #{$clr-bg-ter-dis-text}; } .bit-ico-pfg { --bit-ico-clr: #{$clr-fg-pri}; --bit-ico-clr-txt: #{$clr-bg-pri}; + --bit-ico-clr-dis: #{$clr-fg-pri-dis}; + --bit-ico-clr-dis-text: #{$clr-fg-pri-dis-text}; } .bit-ico-sfg { --bit-ico-clr: #{$clr-fg-sec}; --bit-ico-clr-txt: #{$clr-bg-sec}; + --bit-ico-clr-dis: #{$clr-fg-sec-dis}; + --bit-ico-clr-dis-text: #{$clr-fg-sec-dis-text}; } .bit-ico-tfg { --bit-ico-clr: #{$clr-fg-ter}; --bit-ico-clr-txt: #{$clr-bg-ter}; + --bit-ico-clr-dis: #{$clr-fg-ter-dis}; + --bit-ico-clr-dis-text: #{$clr-fg-ter-dis-text}; } .bit-ico-pbr { - --bit-ico-clr: #{$clr-bg-pri}; + --bit-ico-clr: #{$clr-brd-pri}; --bit-ico-clr-txt: #{$clr-fg-pri}; + --bit-ico-clr-dis: #{$clr-brd-pri-dis}; + --bit-ico-clr-dis-text: #{$clr-brd-pri-dis-text}; } .bit-ico-sbr { - --bit-ico-clr: #{$clr-bg-sec}; + --bit-ico-clr: #{$clr-brd-sec}; --bit-ico-clr-txt: #{$clr-fg-sec}; + --bit-ico-clr-dis: #{$clr-brd-sec-dis}; + --bit-ico-clr-dis-text: #{$clr-brd-sec-dis-text}; } .bit-ico-tbr { - --bit-ico-clr: #{$clr-bg-ter}; + --bit-ico-clr: #{$clr-brd-ter}; --bit-ico-clr-txt: #{$clr-fg-ter}; + --bit-ico-clr-dis: #{$clr-brd-ter-dis}; + --bit-ico-clr-dis-text: #{$clr-brd-ter-dis-text}; } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Utilities/Link/BitLink.scss b/src/BlazorUI/Bit.BlazorUI/Components/Utilities/Link/BitLink.scss index 1402f2c809..ba388e114c 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Utilities/Link/BitLink.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Utilities/Link/BitLink.scss @@ -11,6 +11,7 @@ color: var(--bit-lnk-clr); font-family: $tg-font-family; background-color: transparent; + border-radius: $shp-border-radius; &:hover, &:active, @@ -18,6 +19,10 @@ color: var(--bit-lnk-clr-hover); } + &:focus-visible { + @include focus-ring(var(--bit-lnk-clr-focus)); + } + &:hover, &:active { text-decoration: underline; @@ -25,12 +30,19 @@ &.bit-dis { cursor: default; - color: $clr-fg-dis; + color: var(--bit-lnk-clr-dis); &:hover, &:active, &:focus { - color: $clr-fg-dis; + color: var(--bit-lnk-clr-dis); + } + + &:focus-visible { + // Cancel both layers of the focus-ring mixin applied above: the box-shadow ring and + // the forced-colors Highlight outline, so a disabled link never shows a focus indicator. + outline: none; + box-shadow: none; } &:hover, @@ -73,84 +85,118 @@ .bit-lnk-pri { --bit-lnk-clr: #{$clr-pri}; --bit-lnk-clr-hover: #{$clr-pri-hover}; + --bit-lnk-clr-focus: #{$clr-pri-focus}; + --bit-lnk-clr-dis: #{$clr-pri-dis-text}; } .bit-lnk-sec { --bit-lnk-clr: #{$clr-sec}; --bit-lnk-clr-hover: #{$clr-sec-hover}; + --bit-lnk-clr-focus: #{$clr-sec-focus}; + --bit-lnk-clr-dis: #{$clr-sec-dis-text}; } .bit-lnk-ter { --bit-lnk-clr: #{$clr-ter}; --bit-lnk-clr-hover: #{$clr-ter-hover}; + --bit-lnk-clr-focus: #{$clr-ter-focus}; + --bit-lnk-clr-dis: #{$clr-ter-dis-text}; } .bit-lnk-inf { --bit-lnk-clr: #{$clr-inf}; --bit-lnk-clr-hover: #{$clr-inf-hover}; + --bit-lnk-clr-focus: #{$clr-inf-focus}; + --bit-lnk-clr-dis: #{$clr-inf-dis-text}; } .bit-lnk-suc { --bit-lnk-clr: #{$clr-suc}; --bit-lnk-clr-hover: #{$clr-suc-hover}; + --bit-lnk-clr-focus: #{$clr-suc-focus}; + --bit-lnk-clr-dis: #{$clr-suc-dis-text}; } .bit-lnk-wrn { --bit-lnk-clr: #{$clr-wrn}; --bit-lnk-clr-hover: #{$clr-wrn-hover}; + --bit-lnk-clr-focus: #{$clr-wrn-focus}; + --bit-lnk-clr-dis: #{$clr-wrn-dis-text}; } .bit-lnk-swr { --bit-lnk-clr: #{$clr-swr}; --bit-lnk-clr-hover: #{$clr-swr-hover}; + --bit-lnk-clr-focus: #{$clr-swr-focus}; + --bit-lnk-clr-dis: #{$clr-swr-dis-text}; } .bit-lnk-err { --bit-lnk-clr: #{$clr-err}; --bit-lnk-clr-hover: #{$clr-err-hover}; + --bit-lnk-clr-focus: #{$clr-err-focus}; + --bit-lnk-clr-dis: #{$clr-err-dis-text}; } .bit-lnk-pbg { --bit-lnk-clr: #{$clr-bg-pri}; --bit-lnk-clr-hover: #{$clr-bg-pri-hover}; + --bit-lnk-clr-focus: #{$clr-bg-pri-focus}; + --bit-lnk-clr-dis: #{$clr-bg-pri-dis-text}; } .bit-lnk-sbg { --bit-lnk-clr: #{$clr-bg-sec}; --bit-lnk-clr-hover: #{$clr-bg-sec-hover}; + --bit-lnk-clr-focus: #{$clr-bg-sec-focus}; + --bit-lnk-clr-dis: #{$clr-bg-sec-dis-text}; } .bit-lnk-tbg { --bit-lnk-clr: #{$clr-bg-ter}; --bit-lnk-clr-hover: #{$clr-bg-ter-hover}; + --bit-lnk-clr-focus: #{$clr-bg-ter-focus}; + --bit-lnk-clr-dis: #{$clr-bg-ter-dis-text}; } .bit-lnk-pfg { --bit-lnk-clr: #{$clr-fg-pri}; --bit-lnk-clr-hover: #{$clr-fg-pri-hover}; + --bit-lnk-clr-focus: #{$clr-fg-pri-focus}; + --bit-lnk-clr-dis: #{$clr-fg-pri-dis-text}; } .bit-lnk-sfg { --bit-lnk-clr: #{$clr-fg-sec}; --bit-lnk-clr-hover: #{$clr-fg-sec-hover}; + --bit-lnk-clr-focus: #{$clr-fg-sec-focus}; + --bit-lnk-clr-dis: #{$clr-fg-sec-dis-text}; } .bit-lnk-tfg { --bit-lnk-clr: #{$clr-fg-ter}; --bit-lnk-clr-hover: #{$clr-fg-ter-hover}; + --bit-lnk-clr-focus: #{$clr-fg-ter-focus}; + --bit-lnk-clr-dis: #{$clr-fg-ter-dis-text}; } .bit-lnk-pbr { --bit-lnk-clr: #{$clr-brd-pri}; --bit-lnk-clr-hover: #{$clr-brd-pri-hover}; + --bit-lnk-clr-focus: #{$clr-brd-pri-focus}; + --bit-lnk-clr-dis: #{$clr-brd-pri-dis-text}; } .bit-lnk-sbr { --bit-lnk-clr: #{$clr-brd-sec}; --bit-lnk-clr-hover: #{$clr-brd-sec-hover}; + --bit-lnk-clr-focus: #{$clr-brd-sec-focus}; + --bit-lnk-clr-dis: #{$clr-brd-sec-dis-text}; } .bit-lnk-tbr { --bit-lnk-clr: #{$clr-brd-ter}; --bit-lnk-clr-hover: #{$clr-brd-ter-hover}; + --bit-lnk-clr-focus: #{$clr-brd-ter-focus}; + --bit-lnk-clr-dis: #{$clr-brd-ter-dis-text}; } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Utilities/MediaQuery/BitMediaQuery.razor.cs b/src/BlazorUI/Bit.BlazorUI/Components/Utilities/MediaQuery/BitMediaQuery.razor.cs index ce88ddde5a..18afd359c1 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Utilities/MediaQuery/BitMediaQuery.razor.cs +++ b/src/BlazorUI/Bit.BlazorUI/Components/Utilities/MediaQuery/BitMediaQuery.razor.cs @@ -72,41 +72,42 @@ protected override async Task OnAfterRenderAsync(bool firstRender) if (IsDisposed) return; - var query = Query ?? GetQuery(ScreenQuery); - - if (query.HasValue() && query != _query) + // A custom Query takes precedence; otherwise defer to the predefined ScreenQuery, whose + // media query is built on the JS side from the live --bit-bp-* theme breakpoints so a + // customized BitTheme.Layout.Breakpoints is honored (rather than baking fixed px here). + // A blank Query is treated as absent so a bound-but-empty value still lets ScreenQuery win. + var customQuery = Query.HasValue() ? Query : null; + var screenQuery = customQuery is null ? ScreenQuery?.ToString() : null; + var effectiveKey = customQuery ?? screenQuery; + + if (effectiveKey.HasValue()) { - _query = query; - await _js.BitMediaQuerySetup(_Id, _query, _dotnetObj); + // For a predefined ScreenQuery the actual media-query expression is resolved on the JS + // side from the live --bit-bp-* theme breakpoints, so it can change while the enum name + // stays the same (e.g. after new breakpoints are applied). Re-invoke setup on every + // render in that case and let the JS side reuse the existing listener when the resolved + // expression is unchanged; a custom Query is verbatim, so the key comparison suffices. + if (effectiveKey != _query || screenQuery is not null) + { + _query = effectiveKey; + try + { + await _js.BitMediaQuerySetup(_Id, customQuery, screenQuery, _dotnetObj); + } + catch (JSDisconnectedException) { } // circuit gone; nothing to set up + } } - } - - - - private static string GetQuery(BitScreenQuery? query) - { - return query switch + else if (_query is not null) { - BitScreenQuery.Xs => "(max-width: 600px)", - BitScreenQuery.Sm => "(min-width: 601px) and (max-width: 960px)", - BitScreenQuery.Md => "(min-width: 961px) and (max-width: 1280px)", - BitScreenQuery.Lg => "(min-width: 1281px) and (max-width: 1920px)", - BitScreenQuery.Xl => "(min-width: 1921px) and (max-width: 2560px)", - BitScreenQuery.Xxl => "(min-width: 2561px)", - - BitScreenQuery.LtSm => "(max-width: 600px)", - BitScreenQuery.LtMd => "(max-width: 960px)", - BitScreenQuery.LtLg => "(max-width: 1280px)", - BitScreenQuery.LtXl => "(max-width: 1920px)", - BitScreenQuery.LtXxl => "(max-width: 2560px)", - - BitScreenQuery.GtXs => "(min-width: 601px)", - BitScreenQuery.GtSm => "(min-width: 961px)", - BitScreenQuery.GtMd => "(min-width: 1281px)", - BitScreenQuery.GtLg => "(min-width: 1921px)", - BitScreenQuery.GtXl => "(min-width: 2561px)", - _ => string.Empty - }; + // Neither a Query nor a ScreenQuery resolves anymore: tear down the previous listener + // and reset so a later (re)assignment sets up cleanly. + _query = null; + try + { + await _js.BitMediaQueryDispose(_Id); + } + catch (JSDisconnectedException) { } // circuit gone; nothing to tear down + } } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Utilities/MediaQuery/BitMediaQuery.ts b/src/BlazorUI/Bit.BlazorUI/Components/Utilities/MediaQuery/BitMediaQuery.ts index 793e27cad9..91c7bdb10c 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Utilities/MediaQuery/BitMediaQuery.ts +++ b/src/BlazorUI/Bit.BlazorUI/Components/Utilities/MediaQuery/BitMediaQuery.ts @@ -1,16 +1,49 @@ namespace BitBlazorUI { + type BreakpointKey = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'; + export class MediaQuery { private static _abortControllers: { [key: string]: AbortController } = {}; - public static async setup(id: string, query: string, dotnetObj: DotNetObject) { - if (!query || !dotnetObj) return; + // The resolved media-query expression each listener was created with, so a repeated setup + // call can reuse the existing listener when the expression is unchanged and replace it when + // the theme breakpoints have changed the resolved query for the same ScreenQuery name. + private static _resolvedQueries: { [key: string]: string } = {}; + + // Fallback breakpoints (px), used only when the corresponding --bit-bp-* CSS variable is + // not resolvable. Kept in sync with the defaults published by media-queries.scss. + private static _defaultBreakpoints: Record = { + xs: '0', + sm: '600px', + md: '960px', + lg: '1280px', + xl: '1920px', + xxl: '2560px', + }; + + /** + * @param query A custom, verbatim media query (takes precedence when provided). + * @param screenQuery One of the predefined BitScreenQuery names (e.g. "Md", "LtLg", "GtSm"). + * When set (and no custom query), the query is built from the live + * --bit-bp-* theme breakpoints so a customized theme is honored. + */ + public static async setup(id: string, query: string | null, screenQuery: string | null, dotnetObj: DotNetObject) { + if (!dotnetObj) return; + + const resolvedQuery = query || (screenQuery ? MediaQuery.buildScreenQuery(screenQuery, id) : ''); + if (!resolvedQuery) return; + + // The C# side re-invokes setup for screen queries on every render (the expression + // depends on the live --bit-bp-* breakpoints); keep the existing listener when the + // resolved expression is unchanged and only rebuild it when it actually differs. + if (MediaQuery._abortControllers[id] && MediaQuery._resolvedQueries[id] === resolvedQuery) return; MediaQuery.dispose(id); const ac = new AbortController(); MediaQuery._abortControllers[id] = ac; + MediaQuery._resolvedQueries[id] = resolvedQuery; - const queryList = window.matchMedia(query); + const queryList = window.matchMedia(resolvedQuery); queryList.addEventListener('change', async e => { await handleMatchChange(e.matches); @@ -24,6 +57,8 @@ } public static dispose(id: string) { + delete MediaQuery._resolvedQueries[id]; + const ac = MediaQuery._abortControllers[id]; if (!ac) return; @@ -31,5 +66,69 @@ delete MediaQuery._abortControllers[id]; } + + // Builds the media query for a predefined BitScreenQuery from the resolved theme breakpoints. + // Range bounds are half-open (min inclusive, max exclusive), so the upper edge is one CSS + // pixel below the next breakpoint - matching the packaged media-queries.scss mixins. + private static buildScreenQuery(screenQuery: string, id: string): string { + const bp = MediaQuery.resolveBreakpoints(id); + const min = (v: string) => `(min-width: ${v})`; + const max = (v: string) => `(max-width: ${MediaQuery.below(v)})`; + + switch (screenQuery) { + case 'Xs': return `${min(bp.xs)} and ${max(bp.sm)}`; + case 'Sm': return `${min(bp.sm)} and ${max(bp.md)}`; + case 'Md': return `${min(bp.md)} and ${max(bp.lg)}`; + case 'Lg': return `${min(bp.lg)} and ${max(bp.xl)}`; + case 'Xl': return `${min(bp.xl)} and ${max(bp.xxl)}`; + case 'Xxl': return min(bp.xxl); + + case 'LtSm': return max(bp.sm); + case 'LtMd': return max(bp.md); + case 'LtLg': return max(bp.lg); + case 'LtXl': return max(bp.xl); + case 'LtXxl': return max(bp.xxl); + + case 'GtXs': return min(bp.sm); + case 'GtSm': return min(bp.md); + case 'GtMd': return min(bp.lg); + case 'GtLg': return min(bp.xl); + case 'GtXl': return min(bp.xxl); + + default: return ''; + } + } + + // Reads the --bit-bp-* breakpoint tokens from the queried element's themed scope, so the + // breakpoints of an enclosing BitThemeProvider are honored. Custom properties inherit, so a + // document-root definition still resolves through the element; the root itself is only read + // directly when the element is not rendered (e.g. an OnChange-only usage with no content). + // Built-in defaults fill in for any token that is unset everywhere. + private static resolveBreakpoints(id: string): Record { + const element = document.getElementById(id) ?? document.documentElement; + const styles = typeof getComputedStyle === 'function' + ? getComputedStyle(element) + : null; + + const read = (key: BreakpointKey) => { + const value = styles?.getPropertyValue(`--bit-bp-${key}`)?.trim(); + return value || MediaQuery._defaultBreakpoints[key]; + }; + + return { xs: read('xs'), sm: read('sm'), md: read('md'), lg: read('lg'), xl: read('xl'), xxl: read('xxl') }; + } + + // Returns the value one CSS pixel below `value`, for exclusive max-width bounds. A unitless + // or px value is decremented numerically ("960px" -> "959px"); any other unit (em/rem/…) is + // deferred to the browser via calc() so custom-unit breakpoints still work. + private static below(value: string): string { + const trimmed = value.trim(); + const match = /^(-?\d*\.?\d+)px$/i.exec(trimmed) ?? /^(-?\d*\.?\d+)$/.exec(trimmed); + if (match) { + return `${parseFloat(match[1]) - 1}px`; + } + + return `calc(${trimmed} - 1px)`; + } } -} \ No newline at end of file +} diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Utilities/MediaQuery/BitMediaQueryJsRuntimeExtensions.cs b/src/BlazorUI/Bit.BlazorUI/Components/Utilities/MediaQuery/BitMediaQueryJsRuntimeExtensions.cs index 9ae62a7998..cb97634f1e 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Utilities/MediaQuery/BitMediaQueryJsRuntimeExtensions.cs +++ b/src/BlazorUI/Bit.BlazorUI/Components/Utilities/MediaQuery/BitMediaQueryJsRuntimeExtensions.cs @@ -2,12 +2,13 @@ internal static class BitMediaQueryJsRuntimeExtensions { - internal static ValueTask BitMediaQuerySetup(this IJSRuntime jsRuntime, - string id, - string query, + internal static ValueTask BitMediaQuerySetup(this IJSRuntime jsRuntime, + string id, + string? query, + string? screenQuery, DotNetObjectReference? dotnetObj) { - return jsRuntime.InvokeVoid("BitBlazorUI.MediaQuery.setup", id, query, dotnetObj); + return jsRuntime.InvokeVoid("BitBlazorUI.MediaQuery.setup", id, query, screenQuery, dotnetObj); } internal static ValueTask BitMediaQueryDispose(this IJSRuntime jsRuntime, string id) diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Utilities/MediaQuery/BitScreenQuery.cs b/src/BlazorUI/Bit.BlazorUI/Components/Utilities/MediaQuery/BitScreenQuery.cs index 5914d6217c..2b843937ee 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Utilities/MediaQuery/BitScreenQuery.cs +++ b/src/BlazorUI/Bit.BlazorUI/Components/Utilities/MediaQuery/BitScreenQuery.cs @@ -3,85 +3,95 @@ /// /// The predefined screen media queries in the bit BlazorUI. /// +/// +/// When used via , the effective media query is built at +/// runtime from the live --bit-bp-* breakpoint tokens (i.e. from +/// when you customize them), not from fixed values baked +/// into the component - so overriding the theme breakpoints changes these queries too. The pixel +/// values shown on each member below are the built-in defaults (used when the matching +/// --bit-bp-* variable is unset). Range members are half-open: the upper bound is one CSS +/// pixel below the next breakpoint. For a one-off breakpoint that isn't part of the theme scale, +/// use with an explicit query string instead. +/// public enum BitScreenQuery { /// - /// Extra small query: [@media screen and (max-width: 600px)] + /// Extra small query: [@media screen and (max-width: 599px)] /// Xs, /// - /// Small query: [@media screen and (min-width: 601px) and (max-width: 960px)] + /// Small query: [@media screen and (min-width: 600px) and (max-width: 959px)] /// Sm, /// - /// Medium query: [@media screen and (min-width: 961px) and (max-width: 1280px)] + /// Medium query: [@media screen and (min-width: 960px) and (max-width: 1279px)] /// Md, /// - /// Large query: [@media screen and (min-width: 1281px) and (max-width: 1920px)] + /// Large query: [@media screen and (min-width: 1280px) and (max-width: 1919px)] /// Lg, /// - /// Extra large query: [@media screen and (min-width: 1921px) and (max-width: 2560px)] + /// Extra large query: [@media screen and (min-width: 1920px) and (max-width: 2559px)] /// Xl, /// - /// Extra extra large query: [@media screen and (min-width: 2561px)] + /// Extra extra large query: [@media screen and (min-width: 2560px)] /// Xxl, /// - /// Less than small query: [@media screen and (max-width: 600px)] + /// Less than small query: [@media screen and (max-width: 599px)] /// LtSm, /// - /// Less than medium query: [@media screen and (max-width: 960px)] + /// Less than medium query: [@media screen and (max-width: 959px)] /// LtMd, /// - /// Less than large query: [@media screen and (max-width: 1280px)] + /// Less than large query: [@media screen and (max-width: 1279px)] /// LtLg, /// - /// Less than extra large query: [@media screen and (max-width: 1920px)] + /// Less than extra large query: [@media screen and (max-width: 1919px)] /// LtXl, /// - /// Less than extra extra large query: [@media screen and (max-width: 2560px)] + /// Less than extra extra large query: [@media screen and (max-width: 2559px)] /// LtXxl, /// - /// Greater than extra small query: [@media screen and (min-width: 601px)] + /// Greater than extra small query: [@media screen and (min-width: 600px)] /// GtXs, /// - /// Greater than small query: [@media screen and (min-width: 961px)] + /// Greater than small query: [@media screen and (min-width: 960px)] /// GtSm, /// - /// Greater than medium query: [@media screen and (min-width: 1281px)] + /// Greater than medium query: [@media screen and (min-width: 1280px)] /// GtMd, /// - /// Greater than large query: [@media screen and (min-width: 1921px)] + /// Greater than large query: [@media screen and (min-width: 1920px)] /// GtLg, /// - /// Greater than extra large query: [@media screen and (min-width: 2561px)] + /// Greater than extra large query: [@media screen and (min-width: 2560px)] /// GtXl } diff --git a/src/BlazorUI/Bit.BlazorUI/Extensions/IBitBlazorUIServiceCollectionExtensions.cs b/src/BlazorUI/Bit.BlazorUI/Extensions/IBitBlazorUIServiceCollectionExtensions.cs index fd45c072d8..94cdb49624 100644 --- a/src/BlazorUI/Bit.BlazorUI/Extensions/IBitBlazorUIServiceCollectionExtensions.cs +++ b/src/BlazorUI/Bit.BlazorUI/Extensions/IBitBlazorUIServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; namespace Bit.BlazorUI; @@ -18,7 +19,24 @@ public static class IBitBlazorUIServiceCollectionExtensions /// public static IServiceCollection AddBitBlazorUIServices(this IServiceCollection services, bool trySingleton = false) { - services.TryAddScoped(); + services.TryAddScoped(sp => + new BitThemeNotifications(sp.GetService())); + + // BitThemeJsNotifierReceiver is internal (consumers should listen on + // BitThemeNotifications.ThemeChanged), but DI still resolves it for us. + services.TryAddScoped(sp => + new BitThemeJsNotifierReceiver( + sp.GetRequiredService(), + sp.GetService())); + + services.TryAddScoped(sp => + new BitThemeManager( + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetService())); + + services.TryAddScoped(); + services.TryAddScoped(); if (trySingleton) diff --git a/src/BlazorUI/Bit.BlazorUI/Extensions/JsInterop/IJSRuntimeExtensions.cs b/src/BlazorUI/Bit.BlazorUI/Extensions/JsInterop/IJSRuntimeExtensions.cs index ad822d6354..c754dfd48c 100644 --- a/src/BlazorUI/Bit.BlazorUI/Extensions/JsInterop/IJSRuntimeExtensions.cs +++ b/src/BlazorUI/Bit.BlazorUI/Extensions/JsInterop/IJSRuntimeExtensions.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; namespace Bit.BlazorUI; @@ -7,6 +8,11 @@ public static class IJSRuntimeExtensions { public const DynamicallyAccessedMemberTypes JsonSerialized = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties; + // Cache the per-runtime-type "is this runtime invalid?" probe. The probe is resolved once per + // concrete IJSRuntime type via reflection (the framework exposes no public API for this) and + // reused thereafter, so the reflection cost is paid once rather than on every interop call. + private static readonly ConcurrentDictionary> RuntimeInvalidProbes = new(); + /// @@ -78,14 +84,47 @@ public static bool IsRuntimeInvalid(this IJSRuntime jsRuntime) { if (jsRuntime is null) return false; - var type = jsRuntime.GetType(); + // Resolve (and cache) a probe for this concrete runtime type. The probe is defensive: it + // relies on framework-internal type names / members that can shift between .NET releases, + // so any missing member or reflection failure is treated as "runtime is valid" (return + // false) rather than throwing - a wrong-but-safe answer that lets the interop call proceed + // and surface a real error, instead of crashing here. + var probe = RuntimeInvalidProbes.GetOrAdd(jsRuntime.GetType(), BuildRuntimeInvalidProbe); + return probe(jsRuntime); + } - return type.Name switch + [SuppressMessage("Trimming", "IL2070:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method.", Justification = "Framework-internal members probed reflectively; failures fall back to 'valid'.")] + private static Func BuildRuntimeInvalidProbe(Type type) + { + switch (type.Name) { - "UnsupportedJavaScriptRuntime" => true, // Prerendering - "RemoteJSRuntime" => (bool)type.GetProperty("IsInitialized")!.GetValue(jsRuntime)! is false, // Blazor server - "WebViewJSRuntime" => type.GetField("_ipcSender", BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(jsRuntime) is null, // Blazor Hybrid - _ => false // Blazor WASM - }; + case "UnsupportedJavaScriptRuntime": // Prerendering + return static _ => true; + + case "RemoteJSRuntime": // Blazor Server + { + var isInitialized = type.GetProperty("IsInitialized", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (isInitialized is null) return static _ => false; + return rt => + { + try { return isInitialized.GetValue(rt) is false; } + catch { return false; } + }; + } + + case "WebViewJSRuntime": // Blazor Hybrid + { + var ipcSender = type.GetField("_ipcSender", BindingFlags.NonPublic | BindingFlags.Instance); + if (ipcSender is null) return static _ => false; + return rt => + { + try { return ipcSender.GetValue(rt) is null; } + catch { return false; } + }; + } + + default: // Blazor WASM and anything else: assume valid. + return static _ => false; + } } } diff --git a/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/bit.blazorui.fluent-dark.scss b/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/bit.blazorui.fluent-dark.scss index 991b0d46cb..80b0c72361 100644 --- a/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/bit.blazorui.fluent-dark.scss +++ b/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/bit.blazorui.fluent-dark.scss @@ -9,3 +9,9 @@ @import "shapes.fluent.scss"; @import "typography.fluent.scss"; + +@import "motion.fluent"; + +@import "forced-colors.fluent"; + +@import "semantic-tokens.fluent"; diff --git a/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/bit.blazorui.fluent-light.scss b/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/bit.blazorui.fluent-light.scss index b91212ae0c..e1af5ee056 100644 --- a/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/bit.blazorui.fluent-light.scss +++ b/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/bit.blazorui.fluent-light.scss @@ -9,3 +9,9 @@ @import "shapes.fluent.scss"; @import "typography.fluent.scss"; + +@import "motion.fluent"; + +@import "forced-colors.fluent"; + +@import "semantic-tokens.fluent"; diff --git a/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/bit.blazorui.fluent.scss b/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/bit.blazorui.fluent.scss index e01dddb9de..203b521173 100644 --- a/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/bit.blazorui.fluent.scss +++ b/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/bit.blazorui.fluent.scss @@ -13,3 +13,9 @@ @import "shapes.fluent.scss"; @import "typography.fluent.scss"; + +@import "motion.fluent"; + +@import "forced-colors.fluent"; + +@import "semantic-tokens.fluent"; diff --git a/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/colors.fluent-dark.scss b/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/colors.fluent-dark.scss index a9cf715ed3..dc9feda94a 100644 --- a/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/colors.fluent-dark.scss +++ b/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/colors.fluent-dark.scss @@ -12,6 +12,8 @@ --bit-clr-pri-light-hover: #78B2FF; --bit-clr-pri-light-active: #67A9FF; --bit-clr-pri-text: #212121; + --bit-clr-pri-dis: #2A4A6B; + --bit-clr-pri-dis-text: #4A7AAF; //secondary --bit-clr-sec: #FF9E67; --bit-clr-sec-hover: #E58E5D; @@ -23,6 +25,8 @@ --bit-clr-sec-light-hover: #FFB185; --bit-clr-sec-light-active: #FFA876; --bit-clr-sec-text: #212121; + --bit-clr-sec-dis: #5C3A22; + --bit-clr-sec-dis-text: #A06840; //tertiary --bit-clr-ter: #E6E6E6; --bit-clr-ter-hover: #CFCFCF; @@ -34,6 +38,8 @@ --bit-clr-ter-light-hover: #EBEBEB; --bit-clr-ter-light-active: #E9E9E9; --bit-clr-ter-text: #212121; + --bit-clr-ter-dis: #3D3D3D; + --bit-clr-ter-dis-text: #6B6B6B; //info --bit-clr-inf: #ABACAE; --bit-clr-inf-hover: #9A9B9D; @@ -45,6 +51,8 @@ --bit-clr-inf-light-hover: #BCBDBE; --bit-clr-inf-light-active: #B3B4B6; --bit-clr-inf-text: #212121; + --bit-clr-inf-dis: #3A3D40; + --bit-clr-inf-dis-text: #6B7078; //success --bit-clr-suc: #7FB58A; --bit-clr-suc-hover: #72A37C; @@ -56,6 +64,8 @@ --bit-clr-suc-light-hover: #99C4A1; --bit-clr-suc-light-active: #8CBC96; --bit-clr-suc-text: #212121; + --bit-clr-suc-dis: #2A4A2E; + --bit-clr-suc-dis-text: #4A8050; //warning --bit-clr-wrn: #FFD07A; --bit-clr-wrn-hover: #E5BB6E; @@ -67,6 +77,8 @@ --bit-clr-wrn-light-hover: #FFD995; --bit-clr-wrn-light-active: #FFD587; --bit-clr-wrn-text: #212121; + --bit-clr-wrn-dis: #5C4A1F; + --bit-clr-wrn-dis-text: #A08540; //severe-warning --bit-clr-swr: #E8A874; --bit-clr-swr-hover: #D19768; @@ -78,6 +90,8 @@ --bit-clr-swr-light-hover: #EDB990; --bit-clr-swr-light-active: #EAB182; --bit-clr-swr-text: #212121; + --bit-clr-swr-dis: #5C3A1F; + --bit-clr-swr-dis-text: #A06830; //error --bit-clr-err: #DC7C7C; --bit-clr-err-hover: #C67070; @@ -89,6 +103,8 @@ --bit-clr-err-light-hover: #E39696; --bit-clr-err-light-active: #E08989; --bit-clr-err-text: #212121; + --bit-clr-err-dis: #5C2A2A; + --bit-clr-err-dis-text: #A05050; //foregrounds --bit-clr-fg-pri: #F6F7F8; --bit-clr-fg-pri-hover: #DEDFE0; @@ -99,6 +115,8 @@ --bit-clr-fg-pri-light: #FFFFFF; --bit-clr-fg-pri-light-hover: #EDEDED; --bit-clr-fg-pri-light-active: #DCDCDC; + --bit-clr-fg-pri-dis: #4A4A4A; + --bit-clr-fg-pri-dis-text: #3A3A3A; --bit-clr-fg-sec: #ADB5BF; --bit-clr-fg-sec-hover: #9DA4AD; --bit-clr-fg-sec-active: #8B9199; @@ -108,6 +126,8 @@ --bit-clr-fg-sec-light: #B8C1CC; --bit-clr-fg-sec-light-hover: #A8B1BB; --bit-clr-fg-sec-light-active: #979DA7; + --bit-clr-fg-sec-dis: #3D4450; + --bit-clr-fg-sec-dis-text: #2E343D; --bit-clr-fg-ter: #616973; --bit-clr-fg-ter-hover: #565D66; --bit-clr-fg-ter-active: #4B5159; @@ -117,6 +137,8 @@ --bit-clr-fg-ter-light: #717881; --bit-clr-fg-ter-light-hover: #636A72; --bit-clr-fg-ter-light-active: #575D64; + --bit-clr-fg-ter-dis: #2E333A; + --bit-clr-fg-ter-dis-text: #22272D; --bit-clr-fg-dis: #666; //backgrounds --bit-clr-bg-pri: #010409; @@ -128,6 +150,8 @@ --bit-clr-bg-pri-light: #030914; --bit-clr-bg-pri-light-hover: #151C24; --bit-clr-bg-pri-light-active: #111A24; + --bit-clr-bg-pri-dis: #0A0E13; + --bit-clr-bg-pri-dis-text: #3A3A3A; --bit-clr-bg-sec: #101419; --bit-clr-bg-sec-hover: #1C222B; --bit-clr-bg-sec-active: #202731; @@ -137,6 +161,8 @@ --bit-clr-bg-sec-light: #1A1F26; --bit-clr-bg-sec-light-hover: #242A33; --bit-clr-bg-sec-light-active: #2B323D; + --bit-clr-bg-sec-dis: #141920; + --bit-clr-bg-sec-dis-text: #3A3A3A; --bit-clr-bg-ter: #181F26; --bit-clr-bg-ter-hover: #1B232B; --bit-clr-bg-ter-active: #202A33; @@ -146,6 +172,8 @@ --bit-clr-bg-ter-light: #202830; --bit-clr-bg-ter-light-hover: #212A33; --bit-clr-bg-ter-light-active: #27313B; + --bit-clr-bg-ter-dis: #1C232B; + --bit-clr-bg-ter-dis-text: #3A3A3A; --bit-clr-bg-dis: #222; --bit-clr-bg-overlay: rgba(255, 255, 255, 0.2); //borders @@ -158,6 +186,8 @@ --bit-clr-brd-pri-light: #78838E; --bit-clr-brd-pri-light-hover: #A1AFBD; --bit-clr-brd-pri-light-active: #B5C3D3; + --bit-clr-brd-pri-dis: #3A4250; + --bit-clr-brd-pri-dis-text: #4A5260; --bit-clr-brd-sec: #38424D; --bit-clr-brd-sec-hover: #5E6F80; --bit-clr-brd-sec-active: #718599; @@ -167,6 +197,8 @@ --bit-clr-brd-sec-light: #424C57; --bit-clr-brd-sec-light-hover: #68798A; --bit-clr-brd-sec-light-active: #7E91A4; + --bit-clr-brd-sec-dis: #252D36; + --bit-clr-brd-sec-dis-text: #3A4250; --bit-clr-brd-ter: #232933; --bit-clr-brd-ter-hover: #465266; --bit-clr-brd-ter-active: #586780; @@ -176,7 +208,33 @@ --bit-clr-brd-ter-light: #2E343E; --bit-clr-brd-ter-light-hover: #505B6F; --bit-clr-brd-ter-light-active: #637189; + --bit-clr-brd-ter-dis: #1A1F27; + --bit-clr-brd-ter-dis-text: #2A3038; --bit-clr-brd-dis: #21262D; + //req --bit-clr-req: #A4262C; + + //focus (a11y) - color of the focus indicator per role. + //Aliased to the role's base color so they track theme/brand overrides. + //Override these directly to differentiate focus from base color. + --bit-clr-pri-focus: var(--bit-clr-pri); + --bit-clr-sec-focus: var(--bit-clr-sec); + --bit-clr-ter-focus: var(--bit-clr-ter); + --bit-clr-inf-focus: var(--bit-clr-inf); + --bit-clr-suc-focus: var(--bit-clr-suc); + --bit-clr-wrn-focus: var(--bit-clr-wrn); + --bit-clr-swr-focus: var(--bit-clr-swr); + --bit-clr-err-focus: var(--bit-clr-err); + --bit-clr-fg-pri-focus: var(--bit-clr-fg-pri); + --bit-clr-fg-sec-focus: var(--bit-clr-fg-sec); + --bit-clr-fg-ter-focus: var(--bit-clr-fg-ter); + //The background roles are aliased to the foreground color instead of their own background, + //otherwise the focus indicator would be drawn in the very color it sits on and stay invisible. + --bit-clr-bg-pri-focus: var(--bit-clr-fg-pri); + --bit-clr-bg-sec-focus: var(--bit-clr-fg-pri); + --bit-clr-bg-ter-focus: var(--bit-clr-fg-pri); + --bit-clr-brd-pri-focus: var(--bit-clr-brd-pri); + --bit-clr-brd-sec-focus: var(--bit-clr-brd-sec); + --bit-clr-brd-ter-focus: var(--bit-clr-brd-ter); } diff --git a/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/colors.fluent-light.scss b/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/colors.fluent-light.scss index 8932a59828..bc6d4575e9 100644 --- a/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/colors.fluent-light.scss +++ b/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/colors.fluent-light.scss @@ -14,6 +14,8 @@ --bit-clr-pri-light-hover: #8CC3EC; --bit-clr-pri-light-active: #76B6E8; --bit-clr-pri-text: #FFFFFF; + --bit-clr-pri-dis: #A3CFEF; + --bit-clr-pri-dis-text: #8BBDE5; //secondary --bit-clr-sec: #FF7D33; --bit-clr-sec-hover: #F27730; @@ -25,6 +27,8 @@ --bit-clr-sec-light-hover: #FFB185; --bit-clr-sec-light-active: #FFA470; --bit-clr-sec-text: #FFFFFF; + --bit-clr-sec-dis: #FFCFB3; + --bit-clr-sec-dis-text: #FFAD80; //tertiary --bit-clr-ter: #434343; --bit-clr-ter-hover: #3C3C3C; @@ -36,6 +40,8 @@ --bit-clr-ter-light-hover: #8E8E8E; --bit-clr-ter-light-active: #7B7B7B; --bit-clr-ter-text: #FFFFFF; + --bit-clr-ter-dis: #B8B8B8; + --bit-clr-ter-dis-text: #9E9E9E; //info --bit-clr-inf: #888F95; --bit-clr-inf-hover: #81888E; @@ -47,6 +53,8 @@ --bit-clr-inf-light-hover: #B8BCBF; --bit-clr-inf-light-active: #ACB1B5; --bit-clr-inf-text: #FFFFFF; + --bit-clr-inf-dis: #C3C7CA; + --bit-clr-inf-dis-text: #ACB1B5; //success --bit-clr-suc: #49A94C; --bit-clr-suc-hover: #45A148; @@ -58,6 +66,8 @@ --bit-clr-suc-light-hover: #92CB94; --bit-clr-suc-light-active: #80C382; --bit-clr-suc-text: #FFFFFF; + --bit-clr-suc-dis: #B5DDB7; + --bit-clr-suc-dis-text: #8CC98F; //warning --bit-clr-wrn: #F2B01F; --bit-clr-wrn-hover: #E6A71D; @@ -69,6 +79,8 @@ --bit-clr-wrn-light-hover: #F7D079; --bit-clr-wrn-light-active: #F6C862; --bit-clr-wrn-text: #FFFFFF; + --bit-clr-wrn-dis: #FBDFAA; + --bit-clr-wrn-dis-text: #F5C75E; //severe-warning --bit-clr-swr: #D47E30; --bit-clr-swr-hover: #C9782E; @@ -80,6 +92,8 @@ --bit-clr-swr-light-hover: #DD9859; --bit-clr-swr-light-active: #DA914F; --bit-clr-swr-text: #FFFFFF; + --bit-clr-swr-dis: #EABB8E; + --bit-clr-swr-dis-text: #D9A06A; //error --bit-clr-err: #DF3A2E; --bit-clr-err-hover: #D4372C; @@ -91,6 +105,8 @@ --bit-clr-err-light-hover: #E56158; --bit-clr-err-light-active: #E4584D; --bit-clr-err-text: #FFFFFF; + --bit-clr-err-dis: #F0ADA8; + --bit-clr-err-dis-text: #E8827A; //foreground --bit-clr-fg-pri: #1A1A1A; --bit-clr-fg-pri-hover: #111111; @@ -101,6 +117,8 @@ --bit-clr-fg-pri-light: #262626; --bit-clr-fg-pri-light-hover: #191919; --bit-clr-fg-pri-light-active: #111111; + --bit-clr-fg-pri-dis: #8A8A8A; + --bit-clr-fg-pri-dis-text: #BEBEBE; --bit-clr-fg-sec: #5A5A5A; --bit-clr-fg-sec-hover: #444444; --bit-clr-fg-sec-active: #333333; @@ -110,6 +128,8 @@ --bit-clr-fg-sec-light: #484848; --bit-clr-fg-sec-light-hover: #515151; --bit-clr-fg-sec-light-active: #414141; + --bit-clr-fg-sec-dis: #A3A3A3; + --bit-clr-fg-sec-dis-text: #C8C8C8; --bit-clr-fg-ter: #999999; --bit-clr-fg-ter-hover: #666666; --bit-clr-fg-ter-active: #4D4D4D; @@ -119,6 +139,8 @@ --bit-clr-fg-ter-light: #A7A7A7; --bit-clr-fg-ter-light-hover: #747474; --bit-clr-fg-ter-light-active: #5A5A5A; + --bit-clr-fg-ter-dis: #C8C8C8; + --bit-clr-fg-ter-dis-text: #DADADA; --bit-clr-fg-dis: #AAAAAA; //backgrounds --bit-clr-bg-pri: #FFFFFF; @@ -130,6 +152,8 @@ --bit-clr-bg-pri-light: #FFFFFF; --bit-clr-bg-pri-light-hover: #F2F2F2; --bit-clr-bg-pri-light-active: #EBEBEB; + --bit-clr-bg-pri-dis: #F7F7F7; + --bit-clr-bg-pri-dis-text: #CCCCCC; --bit-clr-bg-sec: #F5F5F5; --bit-clr-bg-sec-hover: #F2F1F0; --bit-clr-bg-sec-active: #E8E7E6; @@ -139,6 +163,8 @@ --bit-clr-bg-sec-light: #F5F5F5; --bit-clr-bg-sec-light-hover: #F2F1F0; --bit-clr-bg-sec-light-active: #E8E7E6; + --bit-clr-bg-sec-dis: #F9F9F9; + --bit-clr-bg-sec-dis-text: #CCCCCC; --bit-clr-bg-ter: #EDECEB; --bit-clr-bg-ter-hover: #E5E5E4; --bit-clr-bg-ter-active: #E0DFDF; @@ -148,6 +174,8 @@ --bit-clr-bg-ter-light: #F1F1F1; --bit-clr-bg-ter-light-hover: #EDEDED; --bit-clr-bg-ter-light-active: #E6E6E6; + --bit-clr-bg-ter-dis: #F5F5F4; + --bit-clr-bg-ter-dis-text: #CCCCCC; --bit-clr-bg-dis: #F3F2F1; --bit-clr-bg-overlay: rgba(0, 0, 0, 0.4); //borders @@ -160,6 +188,8 @@ --bit-clr-brd-pri-light: #898989; --bit-clr-brd-pri-light-hover: #6C6B6B; --bit-clr-brd-pri-light-active: #535353; + --bit-clr-brd-pri-dis: #BEBEBE; + --bit-clr-brd-pri-dis-text: #CCCCCC; --bit-clr-brd-sec: #B0AFAE; --bit-clr-brd-sec-hover: #7D7C7C; --bit-clr-brd-sec-active: #636362; @@ -169,6 +199,8 @@ --bit-clr-brd-sec-light: #BFBFBF; --bit-clr-brd-sec-light-hover: #8A8A8A; --bit-clr-brd-sec-light-active: #727272; + --bit-clr-brd-sec-dis: #D8D8D7; + --bit-clr-brd-sec-dis-text: #CCCCCC; --bit-clr-brd-ter: #DEDDDC; --bit-clr-brd-ter-hover: #CBCBCB; --bit-clr-brd-ter-active: #B0B0B0; @@ -178,7 +210,33 @@ --bit-clr-brd-ter-light: #E9E9E9; --bit-clr-brd-ter-light-hover: #D7D7D7; --bit-clr-brd-ter-light-active: #C1C1C1; + --bit-clr-brd-ter-dis: #EEEDEC; + --bit-clr-brd-ter-dis-text: #CCCCCC; --bit-clr-brd-dis: #E5E3DF; + //req --bit-clr-req: #A4262C; + + //focus (a11y) - color of the focus indicator per role. + //Aliased to the role's base color so they track theme/brand overrides. + //Override these directly to differentiate focus from base color. + --bit-clr-pri-focus: var(--bit-clr-pri); + --bit-clr-sec-focus: var(--bit-clr-sec); + --bit-clr-ter-focus: var(--bit-clr-ter); + --bit-clr-inf-focus: var(--bit-clr-inf); + --bit-clr-suc-focus: var(--bit-clr-suc); + --bit-clr-wrn-focus: var(--bit-clr-wrn); + --bit-clr-swr-focus: var(--bit-clr-swr); + --bit-clr-err-focus: var(--bit-clr-err); + --bit-clr-fg-pri-focus: var(--bit-clr-fg-pri); + --bit-clr-fg-sec-focus: var(--bit-clr-fg-sec); + --bit-clr-fg-ter-focus: var(--bit-clr-fg-ter); + //The background roles are aliased to the foreground color instead of their own background, + //otherwise the focus indicator would be drawn in the very color it sits on and stay invisible. + --bit-clr-bg-pri-focus: var(--bit-clr-fg-pri); + --bit-clr-bg-sec-focus: var(--bit-clr-fg-pri); + --bit-clr-bg-ter-focus: var(--bit-clr-fg-pri); + --bit-clr-brd-pri-focus: var(--bit-clr-brd-pri); + --bit-clr-brd-sec-focus: var(--bit-clr-brd-sec); + --bit-clr-brd-ter-focus: var(--bit-clr-brd-ter); } diff --git a/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/forced-colors.fluent.scss b/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/forced-colors.fluent.scss new file mode 100644 index 0000000000..61af526970 --- /dev/null +++ b/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/forced-colors.fluent.scss @@ -0,0 +1,54 @@ +// Windows High Contrast / forced-colors: preserve readability with system palette. +@media (forced-colors: active) { + :root { + --bit-clr-fg-pri: CanvasText; + --bit-clr-fg-sec: CanvasText; + --bit-clr-fg-ter: CanvasText; + --bit-clr-bg-pri: Canvas; + --bit-clr-bg-sec: Canvas; + --bit-clr-bg-ter: Canvas; + --bit-clr-brd-pri: CanvasText; + --bit-clr-brd-sec: CanvasText; + --bit-clr-pri: LinkText; + --bit-clr-pri-hover: LinkText; + --bit-clr-pri-active: LinkText; + --bit-clr-pri-text: Canvas; + --bit-clr-err: CanvasText; + --bit-clr-suc: CanvasText; + --bit-clr-wrn: CanvasText; + --bit-shd-cal: none; + --bit-shd-sm: none; + --bit-shd-nm: none; + --bit-shd-md: none; + --bit-shd-lg: none; + + // Focus must remain visible in High Contrast - use system Highlight color and a single solid ring. + --bit-clr-pri-focus: Highlight; + --bit-clr-sec-focus: Highlight; + --bit-clr-ter-focus: Highlight; + --bit-clr-inf-focus: Highlight; + --bit-clr-suc-focus: Highlight; + --bit-clr-wrn-focus: Highlight; + --bit-clr-swr-focus: Highlight; + --bit-clr-err-focus: Highlight; + --bit-clr-fg-pri-focus: Highlight; + --bit-clr-bg-pri-focus: Canvas; + --bit-clr-brd-pri-focus: Highlight; + --bit-clr-fg-sec-focus: Highlight; + --bit-clr-bg-sec-focus: Canvas; + --bit-clr-brd-sec-focus: Highlight; + --bit-clr-fg-ter-focus: Highlight; + --bit-clr-bg-ter-focus: Canvas; + --bit-clr-brd-ter-focus: Highlight; + } +} + +// Stronger contrast preference (non-forced): deepen borders and primary text slightly. +// Use a concrete value rather than self-referencing var(--bit-shp-brd-width, …) - a custom +// property cannot reference its own current value (the cycle resolves to the fallback per +// the CSS spec, which makes the intent unclear and brittle across engines). +@media (prefers-contrast: more) { + :root { + --bit-shp-brd-width: 1px; + } +} diff --git a/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/motion.fluent.scss b/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/motion.fluent.scss new file mode 100644 index 0000000000..2b0a55ccfd --- /dev/null +++ b/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/motion.fluent.scss @@ -0,0 +1,15 @@ +:root { + --bit-mot-duration: 200ms; + --bit-mot-duration-short: 100ms; + --bit-mot-duration-long: 300ms; + --bit-mot-easing: cubic-bezier(0.33, 0, 0.67, 1); + --bit-layout-density-scale: 1; +} + +@media (prefers-reduced-motion: reduce) { + :root { + --bit-mot-duration: 0ms; + --bit-mot-duration-short: 0ms; + --bit-mot-duration-long: 0ms; + } +} diff --git a/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/semantic-tokens.fluent.scss b/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/semantic-tokens.fluent.scss new file mode 100644 index 0000000000..0d58bf1444 --- /dev/null +++ b/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/semantic-tokens.fluent.scss @@ -0,0 +1,12 @@ +// Semantic aliases over primitive tokens - use in app CSS instead of raw --bit-clr-* where intent matters. +:root { + --bit-sem-surface-page: var(--bit-clr-bg-pri); + --bit-sem-surface-elevated: var(--bit-clr-bg-sec); + --bit-sem-surface-muted: var(--bit-clr-bg-ter); + --bit-sem-text-primary: var(--bit-clr-fg-pri); + --bit-sem-text-secondary: var(--bit-clr-fg-sec); + --bit-sem-border-default: var(--bit-clr-brd-pri); + --bit-sem-accent-primary: var(--bit-clr-pri); + --bit-sem-focus-ring: var(--bit-shd-focus-ring); + --bit-sem-focus-color: var(--bit-clr-pri-focus); +} diff --git a/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/shapes.fluent.scss b/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/shapes.fluent.scss index ab1287f1b6..3aa2a8a602 100644 --- a/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/shapes.fluent.scss +++ b/src/BlazorUI/Bit.BlazorUI/Styles/Fluent/shapes.fluent.scss @@ -16,4 +16,16 @@ --bit-shp-brd-radius: 0.125rem; --bit-shp-brd-width: 0.0625rem; --bit-shp-brd-style: solid; + + //focus ring (a11y) - width is the visible thickness, offset is the gap between the element and the ring. + --bit-shp-focus-ring-width: 0.125rem; //2px + --bit-shp-focus-ring-offset: 0.125rem; //2px + + //global focus shadow used by the focus-ring mixin and the .bit-css-shd-focus utility. + //Inner ring uses the page background (--bit-clr-bg-pri, not the focus-state background token) to create a separator between the element and the colored ring (WCAG 2.4.11/2.4.13). + //The mixin sets --bit-focus-ring-color/--bit-focus-ring-offset locally on the focused element so per-call overrides apply. + //In forced-colors (High Contrast) mode box-shadows are stripped entirely; the focus-ring mixin restores a real Highlight outline there instead. + --bit-shd-focus-ring: + 0 0 0 var(--bit-focus-ring-offset, var(--bit-shp-focus-ring-offset)) var(--bit-clr-bg-pri), + 0 0 0 calc(var(--bit-focus-ring-offset, var(--bit-shp-focus-ring-offset)) + var(--bit-shp-focus-ring-width)) var(--bit-focus-ring-color, var(--bit-clr-pri-focus)); } diff --git a/src/BlazorUI/Bit.BlazorUI/Styles/bit-css.scss b/src/BlazorUI/Bit.BlazorUI/Styles/bit-css.scss index 5fbd89cc52..338c3e5c9c 100644 --- a/src/BlazorUI/Bit.BlazorUI/Styles/bit-css.scss +++ b/src/BlazorUI/Bit.BlazorUI/Styles/bit-css.scss @@ -1,4 +1,4 @@ -@import "theme-variables.scss"; +@import "functions.scss"; //primary .bit-css-clr-pri { @@ -939,3 +939,41 @@ .bit-css-shp-brd-style { border-style: $shp-border-style; } + +//focus (a11y) - color tokens applied on :focus-visible. +.bit-css-clr-pri-focus:focus-visible { + color: $clr-pri-focus; +} + +.bit-css-clr-sec-focus:focus-visible { + color: $clr-sec-focus; +} + +.bit-css-clr-ter-focus:focus-visible { + color: $clr-ter-focus; +} + +.bit-css-clr-inf-focus:focus-visible { + color: $clr-inf-focus; +} + +.bit-css-clr-suc-focus:focus-visible { + color: $clr-suc-focus; +} + +.bit-css-clr-wrn-focus:focus-visible { + color: $clr-wrn-focus; +} + +.bit-css-clr-swr-focus:focus-visible { + color: $clr-swr-focus; +} + +.bit-css-clr-err-focus:focus-visible { + color: $clr-err-focus; +} + +//focus ring utility - drop-in class to apply the global, theme-aware focus indicator. +.bit-css-shd-focus-ring:focus-visible { + @include focus-ring; +} diff --git a/src/BlazorUI/Bit.BlazorUI/Styles/functions.scss b/src/BlazorUI/Bit.BlazorUI/Styles/functions.scss index feee193b9f..233bcba63d 100644 --- a/src/BlazorUI/Bit.BlazorUI/Styles/functions.scss +++ b/src/BlazorUI/Bit.BlazorUI/Styles/functions.scss @@ -18,3 +18,65 @@ $html-font-size: 16px; @function spacing($spacingValue) { @return calc($spacing-scaling-factor * $spacingValue); } + +/* + * focus-ring($color, $offset) + * -------------------------------------------------- + * Renders a consistent, theme-aware focus indicator across components. + * + * Behavior: + * - Uses a two-layer box-shadow: an inner ring matching the page background + * (separator) and an outer colored ring (visible indicator). This satisfies + * WCAG 2.4.11 (Focus Not Obscured) and 2.4.13 (Focus Appearance). + * - Width is driven by --bit-shp-focus-ring-width and offset by --bit-shp-focus-ring-offset, + * both globally themable. + * - Color defaults to --bit-clr-pri-focus and accepts any role token (e.g. $clr-err-focus) + * so invalid/success/etc. variants can opt into matching focus colors. + * - In Windows High Contrast / forced-colors mode box-shadows are stripped, so a real + * outline in the system Highlight color is rendered instead (matching focus-underline-ring). + * + * Usage: + * .my-component { + * &:focus-visible { @include focus-ring; } + * &.bit-inv:focus-visible { @include focus-ring($clr-err-focus); } + * } + */ +@mixin focus-ring($color: $clr-pri-focus, $offset: $shp-focus-ring-offset) { + outline: none; // Replace native outline with the themed ring below. + --bit-focus-ring-color: #{$color}; + --bit-focus-ring-offset: #{$offset}; + + // Inline fallback mirrors the global --bit-shd-focus-ring expression in shapes.fluent.scss + // so focus indicators still render if a consumer-defined theme omits that token. + box-shadow: var(--bit-shd-focus-ring, + 0 0 0 var(--bit-focus-ring-offset, #{$offset}) var(--bit-clr-bg-pri), + 0 0 0 calc(var(--bit-focus-ring-offset, #{$offset}) + #{$shp-focus-ring-width}) var(--bit-focus-ring-color, #{$color})); + + // In Windows High Contrast / forced-colors mode box-shadows are stripped, which would leave the + // focused element with no visible indicator. Restore a real outline so focus stays perceivable. + @media (forced-colors: active) { + outline: #{$shp-focus-ring-width} solid Highlight; + + // Read the offset through the same custom property the box-shadow uses so a runtime + // override of --bit-focus-ring-offset stays in sync in High Contrast mode too. + outline-offset: var(--bit-focus-ring-offset, #{$offset}); + } +} + +/* + * focus-underline-ring($color) + * -------------------------------------------------- + * Renders a focus indicator as a single bottom rule, used by "underlined" + * input variants (TextField/SearchBox/DatePicker .bit-*-und) where a full ring + * conflicts with the visual language of the component. + */ +@mixin focus-underline-ring($color: $clr-pri-focus) { + outline: none; + box-shadow: inset 0 calc(-1 * #{$shp-focus-ring-width}) 0 0 #{$color}; + + // In Windows High Contrast / forced-colors mode box-shadows are stripped, which would leave the + // focused input with no visible indicator. Restore a real outline so focus stays perceivable. + @media (forced-colors: active) { + outline: #{$shp-focus-ring-width} solid Highlight; + } +} diff --git a/src/BlazorUI/Bit.BlazorUI/Styles/media-queries.scss b/src/BlazorUI/Bit.BlazorUI/Styles/media-queries.scss index c1628fe3f8..ced80bd3d4 100644 --- a/src/BlazorUI/Bit.BlazorUI/Styles/media-queries.scss +++ b/src/BlazorUI/Bit.BlazorUI/Styles/media-queries.scss @@ -1,110 +1,120 @@ // https://github.com/Necromancerx/media-queries-scss-mixins -$brk-xs-max: 600px; -$brk-sm-min: 601px; -$brk-sm-max: 960px; -$brk-md-min: 961px; -$brk-md-max: 1280px; -$brk-lg-min: 1281px; -$brk-lg-max: 1920px; -$brk-xl-min: 1921px; -$brk-xl-max: 2560px; -$brk-xxl-min: 2561px; +$mq-xs-max: 599px; +$mq-sm-min: 600px; +$mq-sm-max: 959px; +$mq-md-min: 960px; +$mq-md-max: 1279px; +$mq-lg-min: 1280px; +$mq-lg-max: 1919px; +$mq-xl-min: 1920px; +$mq-xl-max: 2559px; +$mq-xxl-min: 2560px; + +:root { + --bit-bp-xs: 0; + --bit-bp-sm: #{$mq-sm-min}; + --bit-bp-md: #{$mq-md-min}; + --bit-bp-lg: #{$mq-lg-min}; + --bit-bp-xl: #{$mq-xl-min}; + --bit-bp-xxl: #{$mq-xxl-min}; +} // media devices @mixin xs { - @media screen and (max-width: #{$brk-xs-max}) { + @media screen and (max-width: #{$mq-xs-max}) { @content; } } + @mixin sm { - @media screen and (min-width: #{$brk-sm-min}) and (max-width: #{$brk-sm-max}) { + @media screen and (min-width: #{$mq-sm-min}) and (max-width: #{$mq-sm-max}) { @content; } } @mixin md { - @media screen and (min-width: #{$brk-md-min}) and (max-width: #{$brk-md-max}) { + @media screen and (min-width: #{$mq-md-min}) and (max-width: #{$mq-md-max}) { @content; } } @mixin lg { - @media screen and (min-width: #{$brk-lg-min}) and (max-width: #{$brk-lg-max}) { + @media screen and (min-width: #{$mq-lg-min}) and (max-width: #{$mq-lg-max}) { @content; } } @mixin xl { - @media screen and (min-width: #{$brk-xl-min}) and (max-width: #{$brk-xl-max}) { + @media screen and (min-width: #{$mq-xl-min}) and (max-width: #{$mq-xl-max}) { @content; } } @mixin xxl { - @media screen and (min-width: #{$brk-xxl-min}) { + @media screen and (min-width: #{$mq-xxl-min}) { @content; } } // media lt queries @mixin lt-sm { - @media screen and (max-width: #{$brk-xs-max}) { + @media screen and (max-width: #{$mq-xs-max}) { @content; } } @mixin lt-md { - @media screen and (max-width: #{$brk-sm-max}) { + @media screen and (max-width: #{$mq-sm-max}) { @content; } } @mixin lt-lg { - @media screen and (max-width: #{$brk-md-max}) { + @media screen and (max-width: #{$mq-md-max}) { @content; } } @mixin lt-xl { - @media screen and (max-width: #{$brk-lg-max}) { + @media screen and (max-width: #{$mq-lg-max}) { @content; } } @mixin lt-xxl { - @media screen and (max-width: #{$brk-xl-max}) { + @media screen and (max-width: #{$mq-xl-max}) { @content; } } // media gt queries @mixin gt-xs { - @media screen and (min-width: #{$brk-sm-min}) { + @media screen and (min-width: #{$mq-sm-min}) { @content; } } @mixin gt-sm { - @media screen and (min-width: #{$brk-md-min}) { + @media screen and (min-width: #{$mq-md-min}) { @content; } } @mixin gt-md { - @media screen and (min-width: #{$brk-lg-min}) { + @media screen and (min-width: #{$mq-lg-min}) { @content; } } @mixin gt-lg { - @media screen and (min-width: #{$brk-xl-min}) { + @media screen and (min-width: #{$mq-xl-min}) { @content; } } @mixin gt-xl { - @media screen and (min-width: #{$brk-xxl-min}) { + @media screen and (min-width: #{$mq-xxl-min}) { @content; } } diff --git a/src/BlazorUI/Bit.BlazorUI/Styles/theme-variables.scss b/src/BlazorUI/Bit.BlazorUI/Styles/theme-variables.scss index 66c82ab061..46c73eb04f 100644 --- a/src/BlazorUI/Bit.BlazorUI/Styles/theme-variables.scss +++ b/src/BlazorUI/Bit.BlazorUI/Styles/theme-variables.scss @@ -105,6 +105,8 @@ $clr-fg-pri-dark-active: var(--bit-clr-fg-pri-dark-active); $clr-fg-pri-light: var(--bit-clr-fg-pri-light); $clr-fg-pri-light-hover: var(--bit-clr-fg-pri-light-hover); $clr-fg-pri-light-active: var(--bit-clr-fg-pri-light-active); +$clr-fg-pri-dis: var(--bit-clr-fg-pri-dis); +$clr-fg-pri-dis-text: var(--bit-clr-fg-pri-dis-text); $clr-fg-sec: var(--bit-clr-fg-sec); $clr-fg-sec-hover: var(--bit-clr-fg-sec-hover); $clr-fg-sec-active: var(--bit-clr-fg-sec-active); @@ -114,6 +116,8 @@ $clr-fg-sec-dark-active: var(--bit-clr-fg-sec-dark-active); $clr-fg-sec-light: var(--bit-clr-fg-sec-light); $clr-fg-sec-light-hover: var(--bit-clr-fg-sec-light-hover); $clr-fg-sec-light-active: var(--bit-clr-fg-sec-light-active); +$clr-fg-sec-dis: var(--bit-clr-fg-sec-dis); +$clr-fg-sec-dis-text: var(--bit-clr-fg-sec-dis-text); $clr-fg-ter: var(--bit-clr-fg-ter); $clr-fg-ter-hover: var(--bit-clr-fg-ter-hover); $clr-fg-ter-active: var(--bit-clr-fg-ter-active); @@ -123,6 +127,8 @@ $clr-fg-ter-dark-active: var(--bit-clr-fg-ter-dark-active); $clr-fg-ter-light: var(--bit-clr-fg-ter-light); $clr-fg-ter-light-hover: var(--bit-clr-fg-ter-light-hover); $clr-fg-ter-light-active: var(--bit-clr-fg-ter-light-active); +$clr-fg-ter-dis: var(--bit-clr-fg-ter-dis); +$clr-fg-ter-dis-text: var(--bit-clr-fg-ter-dis-text); $clr-fg-dis: var(--bit-clr-fg-dis); //backgrounds @@ -135,6 +141,8 @@ $clr-bg-pri-dark-active: var(--bit-clr-bg-pri-dark-active); $clr-bg-pri-light: var(--bit-clr-bg-pri-light); $clr-bg-pri-light-hover: var(--bit-clr-bg-pri-light-hover); $clr-bg-pri-light-active: var(--bit-clr-bg-pri-light-active); +$clr-bg-pri-dis: var(--bit-clr-bg-pri-dis); +$clr-bg-pri-dis-text: var(--bit-clr-bg-pri-dis-text); $clr-bg-sec: var(--bit-clr-bg-sec); $clr-bg-sec-hover: var(--bit-clr-bg-sec-hover); $clr-bg-sec-active: var(--bit-clr-bg-sec-active); @@ -144,6 +152,8 @@ $clr-bg-sec-dark-active: var(--bit-clr-bg-sec-dark-active); $clr-bg-sec-light: var(--bit-clr-bg-sec-light); $clr-bg-sec-light-hover: var(--bit-clr-bg-sec-light-hover); $clr-bg-sec-light-active: var(--bit-clr-bg-sec-light-active); +$clr-bg-sec-dis: var(--bit-clr-bg-sec-dis); +$clr-bg-sec-dis-text: var(--bit-clr-bg-sec-dis-text); $clr-bg-ter: var(--bit-clr-bg-ter); $clr-bg-ter-hover: var(--bit-clr-bg-ter-hover); $clr-bg-ter-active: var(--bit-clr-bg-ter-active); @@ -153,6 +163,8 @@ $clr-bg-ter-dark-active: var(--bit-clr-bg-ter-dark-active); $clr-bg-ter-light: var(--bit-clr-bg-ter-light); $clr-bg-ter-light-hover: var(--bit-clr-bg-ter-light-hover); $clr-bg-ter-light-active: var(--bit-clr-bg-ter-light-active); +$clr-bg-ter-dis: var(--bit-clr-bg-ter-dis); +$clr-bg-ter-dis-text: var(--bit-clr-bg-ter-dis-text); $clr-bg-dis: var(--bit-clr-bg-dis); $clr-bg-overlay: var(--bit-clr-bg-overlay); @@ -166,6 +178,8 @@ $clr-brd-pri-dark-active: var(--bit-clr-brd-pri-dark-active); $clr-brd-pri-light: var(--bit-clr-brd-pri-light); $clr-brd-pri-light-hover: var(--bit-clr-brd-pri-light-hover); $clr-brd-pri-light-active: var(--bit-clr-brd-pri-light-active); +$clr-brd-pri-dis: var(--bit-clr-brd-pri-dis); +$clr-brd-pri-dis-text: var(--bit-clr-brd-pri-dis-text); $clr-brd-sec: var(--bit-clr-brd-sec); $clr-brd-sec-hover: var(--bit-clr-brd-sec-hover); $clr-brd-sec-active: var(--bit-clr-brd-sec-active); @@ -175,6 +189,8 @@ $clr-brd-sec-dark-active: var(--bit-clr-brd-sec-dark-active); $clr-brd-sec-light: var(--bit-clr-brd-sec-light); $clr-brd-sec-light-hover: var(--bit-clr-brd-sec-light-hover); $clr-brd-sec-light-active: var(--bit-clr-brd-sec-light-active); +$clr-brd-sec-dis: var(--bit-clr-brd-sec-dis); +$clr-brd-sec-dis-text: var(--bit-clr-brd-sec-dis-text); $clr-brd-ter: var(--bit-clr-brd-ter); $clr-brd-ter-hover: var(--bit-clr-brd-ter-hover); $clr-brd-ter-active: var(--bit-clr-brd-ter-active); @@ -184,11 +200,50 @@ $clr-brd-ter-dark-active: var(--bit-clr-brd-ter-dark-active); $clr-brd-ter-light: var(--bit-clr-brd-ter-light); $clr-brd-ter-light-hover: var(--bit-clr-brd-ter-light-hover); $clr-brd-ter-light-active: var(--bit-clr-brd-ter-light-active); +$clr-brd-ter-dis: var(--bit-clr-brd-ter-dis); +$clr-brd-ter-dis-text: var(--bit-clr-brd-ter-dis-text); $clr-brd-dis: var(--bit-clr-brd-dis); //required $clr-req: var(--bit-clr-req); +//disabled - per-role disabled colors +$clr-pri-dis: var(--bit-clr-pri-dis); +$clr-pri-dis-text: var(--bit-clr-pri-dis-text); +$clr-sec-dis: var(--bit-clr-sec-dis); +$clr-sec-dis-text: var(--bit-clr-sec-dis-text); +$clr-ter-dis: var(--bit-clr-ter-dis); +$clr-ter-dis-text: var(--bit-clr-ter-dis-text); +$clr-inf-dis: var(--bit-clr-inf-dis); +$clr-inf-dis-text: var(--bit-clr-inf-dis-text); +$clr-suc-dis: var(--bit-clr-suc-dis); +$clr-suc-dis-text: var(--bit-clr-suc-dis-text); +$clr-wrn-dis: var(--bit-clr-wrn-dis); +$clr-wrn-dis-text: var(--bit-clr-wrn-dis-text); +$clr-swr-dis: var(--bit-clr-swr-dis); +$clr-swr-dis-text: var(--bit-clr-swr-dis-text); +$clr-err-dis: var(--bit-clr-err-dis); +$clr-err-dis-text: var(--bit-clr-err-dis-text); + +//focus (a11y) - per-role focus indicator color, plus shape/shadow tokens consumed by the focus-ring mixin. +$clr-pri-focus: var(--bit-clr-pri-focus); +$clr-sec-focus: var(--bit-clr-sec-focus); +$clr-ter-focus: var(--bit-clr-ter-focus); +$clr-inf-focus: var(--bit-clr-inf-focus); +$clr-suc-focus: var(--bit-clr-suc-focus); +$clr-wrn-focus: var(--bit-clr-wrn-focus); +$clr-swr-focus: var(--bit-clr-swr-focus); +$clr-err-focus: var(--bit-clr-err-focus); +$clr-fg-pri-focus: var(--bit-clr-fg-pri-focus); +$clr-fg-sec-focus: var(--bit-clr-fg-sec-focus); +$clr-fg-ter-focus: var(--bit-clr-fg-ter-focus); +$clr-bg-pri-focus: var(--bit-clr-bg-pri-focus); +$clr-bg-sec-focus: var(--bit-clr-bg-sec-focus); +$clr-bg-ter-focus: var(--bit-clr-bg-ter-focus); +$clr-brd-pri-focus: var(--bit-clr-brd-pri-focus); +$clr-brd-sec-focus: var(--bit-clr-brd-sec-focus); +$clr-brd-ter-focus: var(--bit-clr-brd-ter-focus); + //neutrals $clr-ntr-white: var(--bit-clr-ntr-white); $clr-ntr-black: var(--bit-clr-ntr-black); @@ -268,6 +323,11 @@ $zindex-base: var(--bit-zin-base); $shp-border-radius: var(--bit-shp-brd-radius); $shp-border-width: var(--bit-shp-brd-width); $shp-border-style: var(--bit-shp-brd-style); +$shp-focus-ring-width: var(--bit-shp-focus-ring-width); +$shp-focus-ring-offset: var(--bit-shp-focus-ring-offset); + +//shadows-focus +$box-shadow-focus-ring: var(--bit-shd-focus-ring); /*---- Typography ----*/ $tg-font-family: var(--bit-tpg-font-family); diff --git a/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitAccentColorPresets.cs b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitAccentColorPresets.cs new file mode 100644 index 0000000000..4d575ec0bd --- /dev/null +++ b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitAccentColorPresets.cs @@ -0,0 +1,14 @@ +namespace Bit.BlazorUI; + +/// +/// Sample accent (primary) hex values for branding - assign to or derive via . +/// +public static class BitAccentColorPresets +{ + public const string Blue = "#1A86D8"; + public const string Purple = "#8764B8"; + public const string Green = "#107C10"; + public const string Orange = "#CA5010"; + public const string Teal = "#038387"; + public const string Rose = "#C239B3"; +} diff --git a/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitExternalThemeJsExtensions.cs b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitExternalThemeJsExtensions.cs new file mode 100644 index 0000000000..e1643b94fc --- /dev/null +++ b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitExternalThemeJsExtensions.cs @@ -0,0 +1,14 @@ +namespace Bit.BlazorUI; + +internal static class BitExternalThemeJsExtensions +{ + internal static ValueTask BitExternalThemeAttach(this IJSRuntime js, string linkElementId, string href) + { + return js.InvokeVoid("BitBlazorUI.ExternalTheme.attach", linkElementId, href); + } + + internal static ValueTask BitExternalThemeDetach(this IJSRuntime js, string linkElementId) + { + return js.InvokeVoid("BitBlazorUI.ExternalTheme.detach", linkElementId); + } +} diff --git a/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitExternalThemeLoader.cs b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitExternalThemeLoader.cs new file mode 100644 index 0000000000..0dc67476f3 --- /dev/null +++ b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitExternalThemeLoader.cs @@ -0,0 +1,112 @@ +namespace Bit.BlazorUI; + +/// +/// Loads optional alternate CSS bundles by id (creates or updates a <link rel="stylesheet">). +/// Use only trusted/same-origin URLs. The loader rejects relative-with-scheme strings such as +/// javascript: and data:, rejects protocol-relative URLs (//host/...); +/// for absolute URLs the scheme must be http or https. +/// +public sealed class BitExternalThemeLoader +{ + private readonly IJSRuntime _js; + + public BitExternalThemeLoader(IJSRuntime js) + { + ArgumentNullException.ThrowIfNull(js); + + _js = js; + } + + /// + /// Creates (or updates) a <link rel="stylesheet"> element identified by + /// with the given . + /// + /// DOM id used to find / create the link element. + /// + /// Stylesheet URL. Only same-origin references are accepted: a same-origin relative path + /// (e.g. /css/dark.css) or an absolute http/https URL that resolves to the current + /// origin. Non-http(s) schemes and protocol-relative URLs are rejected here, and the JS side + /// additionally enforces same-origin (cross-origin http/https URLs are rejected there). + /// + public ValueTask AttachStylesheetAsync(string linkElementId, string href) + { + ArgumentException.ThrowIfNullOrWhiteSpace(linkElementId); + ArgumentException.ThrowIfNullOrWhiteSpace(href); + + // Defense-in-depth href validation. This C# check rejects `javascript:`/`data:`/`vbscript:` + // schemes and protocol-relative URLs up front so a tampered or user-supplied href can't + // attach a dangerous . The JS side (BitTheme.ts) repeats these checks and also + // enforces same-origin, rejecting cross-origin http/https URLs; this C# layer intentionally + // does not re-implement the origin comparison (it has no reliable view of the document + // origin) and leaves that final gate to the browser-side validator. Relative paths + // (no scheme) are allowed because that's the common same-origin case + // ("/css/dark.css", "themes/light.css"). + ValidateHref(href); + + return _js.BitExternalThemeAttach(linkElementId, href); + } + + /// Removes a previously-attached <link rel="stylesheet"> by id. + /// DOM id originally passed to . + public ValueTask DetachStylesheetAsync(string linkElementId) + { + ArgumentException.ThrowIfNullOrWhiteSpace(linkElementId); + + return _js.BitExternalThemeDetach(linkElementId); + } + + private static void ValidateHref(string href) + { + // Protocol-relative URLs (e.g. "//evil.example/x.css") are not absolute URIs as far as + // Uri.TryCreate(Absolute) is concerned, so they would otherwise slip past the scheme + // check below and reach the browser, which resolves them to a cross-origin + // http(s)://evil.example/x.css. Reject them up front; callers must use a same-origin + // relative path or an explicit http/https URL. Browsers also normalize backslashes to + // forward slashes in URLs, so `\\host\x.css` is the same attack surface - block it too. + var trimmed = href.TrimStart(); + if (trimmed.StartsWith("//", StringComparison.Ordinal) || + trimmed.StartsWith(@"\\", StringComparison.Ordinal) || + trimmed.StartsWith(@"/\", StringComparison.Ordinal) || + trimmed.StartsWith(@"\/", StringComparison.Ordinal)) + { + throw new ArgumentException( + "Stylesheet href must not be a protocol-relative URL. Use a same-origin relative path or an explicit http/https URL.", + nameof(href)); + } + + if (Uri.TryCreate(href, UriKind.Absolute, out var absolute)) + { + // Reject every absolute-URL scheme other than http(s). That covers javascript:, data:, + // file:, vbscript:, blob:, etc. - none of which belong on a . + // Uri.UriSchemeHttp/Https are static readonly fields, not constants, so they can't be + // used in a relational pattern; compare with case-insensitive ordinal equality instead. + if (!string.Equals(absolute.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) && + !string.Equals(absolute.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException( + $"Stylesheet href scheme '{absolute.Scheme}' is not allowed. Use http, https, or a same-origin relative path.", + nameof(href)); + } + + return; + } + + // Relative URLs are allowed. We still want to reject the `javascript:` form even when + // Uri.TryCreate refuses to parse it as absolute (it does for some inputs depending on + // platform), so a final string-prefix sanity check covers the residual surface. + if (LooksLikeDangerousRelativeScheme(trimmed)) + { + throw new ArgumentException( + "Stylesheet href appears to use a non-http scheme (javascript:, data:, vbscript:). Use http, https, or a same-origin relative path.", + nameof(href)); + } + } + + private static bool LooksLikeDangerousRelativeScheme(string trimmed) + { + // Match common attack vectors that browsers used to (or still) accept on attribute values. + return trimmed.StartsWith("javascript:", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("data:", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("vbscript:", StringComparison.OrdinalIgnoreCase); + } +} diff --git a/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitTheme.ts b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitTheme.ts new file mode 100644 index 0000000000..2667277e7d --- /dev/null +++ b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitTheme.ts @@ -0,0 +1,480 @@ +// Attribute / storage names - kept aligned with BitThemeAttributeNames.cs and BitThemeSsr.cs in +// C#. If you rename a constant here, mirror the change there (the contract test under +// Bit.BlazorUI.Tests.Utils.Theme will catch a mismatch). +namespace BitBlazorUI { + const ATTR_THEME = 'bit-theme'; + const ATTR_THEME_DEFAULT = 'bit-theme-default'; + const ATTR_THEME_SYSTEM = 'bit-theme-system'; + const ATTR_THEME_PERSIST = 'bit-theme-persist'; + const ATTR_THEME_PERSIST_COOKIE = 'bit-theme-persist-cookie'; + const ATTR_THEME_DARK = 'bit-theme-dark'; + const ATTR_THEME_LIGHT = 'bit-theme-light'; + const STORAGE_KEY = 'bit-current-theme'; + // Kept aligned with BitThemeCookie.PreferenceCookieName in C#. When cookie persistence is + // enabled, the client mirrors the persisted preference into this cookie so the server can read + // the user's current choice (via BitThemeSsr.BuildRootThemeAttributes) and paint the matching + // theme on first server render - keeping the client (localStorage) and server (cookie) stores + // in sync instead of drifting apart. + const COOKIE_NAME = 'bit-theme-preference'; + // ~400 days, the upper bound modern browsers clamp persistent cookies to. + const COOKIE_MAX_AGE_SECONDS = 34560000; + + type onThemeChangeType = (newThemeName: string, oldThemeName: string) => void; + + export interface ThemeOptions { + system?: boolean; + persist?: boolean; + /** Mirror the persisted preference into the COOKIE_NAME cookie so SSR stays in sync with client choices. */ + persistCookie?: boolean; + theme?: string | null; + default?: string | null; + darkTheme?: string | null; + lightTheme?: string | null; + onChange?: onThemeChangeType; + } + + interface ThemeSetOptions { + /** When true, startup init so resolved light/dark does not disable OS sync from bit-theme-system. */ + fromInit?: boolean; + /** Reserved for internal OS refresh paths (same as normal set without touching follow-system flags). */ + internalOsRefresh?: boolean; + } + + export class Theme { + private static SYSTEM_THEME = 'system'; + private static THEME_ATTRIBUTE = ATTR_THEME; + private static THEME_STORAGE_KEY = STORAGE_KEY; + + private static _persist = false; + private static _persistCookie = false; + private static _darkTheme: string = 'dark'; + private static _lightTheme: string = 'light'; + private static _initOptions: ThemeOptions = {}; + private static _currentTheme = Theme._lightTheme; + private static _onThemeChange: onThemeChangeType = () => { }; + + /** When true, user pinned an explicit theme via set (not system); disables following OS until set('system'). */ + private static _stopFollowingSystem = false; + + /** + * When true, the user explicitly opted into following the OS at runtime (e.g. via useSystem()), + * so we follow OS changes even without a persisted "system" preference or the bit-theme-system attribute. + * Cleared automatically when the user pins a concrete theme via set(...). + */ + private static _runtimeFollowSystem = false; + + private static _schemeMediaQuery: MediaQueryList | null = null; + private static _onSchemeChange = () => Theme.applyResolvedSystemThemeFromOs(); + + private static _dotnetNotifier: DotNetObject | null = null; + + private static _appliedVarKeys = new WeakMap(); + + public static init(options: ThemeOptions) { + Object.assign(Theme._initOptions, options); + + let deferPersist = false; + let deferPersistCookie = false; + + if (Theme._initOptions.onChange) { + Theme._onThemeChange = Theme._initOptions.onChange; + } + + if (Theme._initOptions.darkTheme) { + Theme._darkTheme = Theme._initOptions.darkTheme; + } + + if (Theme._initOptions.lightTheme) { + Theme._lightTheme = Theme._initOptions.lightTheme; + } + + // Cookie mirroring is independent of localStorage persistence: an SSR app may want the + // server to read the preference cookie without necessarily enabling localStorage. + // Derive from the merged _initOptions on every call (not just when truthy) so a later + // init({ persistCookie: false }) can clear the flag, while an init that omits the key + // keeps the previously merged value. + Theme._persistCookie = !!Theme._initOptions.persistCookie; + + let theme = Theme._initOptions.theme || Theme._initOptions.default || Theme._lightTheme; + + // A `system` opt-in comes from the JS `system: true` option / bit-theme-system attribute, + // OR from the base theme itself resolving to the "system" keyword (bit-theme="system" or + // bit-theme-default="system"). Capture it once so first-paint resolution, the persist + // deferral below and runtime OS-follow all treat these spellings identically. + const systemRequested = Theme._initOptions.system || theme === Theme.SYSTEM_THEME; + + // Resolve the first-paint theme with the SAME precedence as the SSR inline script in + // BitThemeSsr.cs (BuildInlineScriptBody): a `system` opt-in follows the OS and takes + // precedence over an explicit bit-theme / bit-theme-default at first paint. A persisted + // preference (handled below) still wins over this. + // + // Previously this only resolved the OS theme when there was NO explicit theme/default, + // so a document with both bit-theme="..." and bit-theme-system painted the explicit + // theme on hydration while the SSR script painted the OS-resolved one - a flash of the + // wrong theme. Keeping the two code paths in lockstep avoids that. + if (systemRequested) { + theme = Theme.isSystemDark() ? Theme._darkTheme : Theme._lightTheme; + } + + // Derive on every call (see persistCookie note above) so a later init({ persist: false }) + // clears the flag, while an omitted key preserves the merged value. + Theme._persist = !!Theme._initOptions.persist; + + // Restore a previously persisted preference. getPersisted() reads localStorage first and + // falls back to the preference cookie, so this also covers a cookie-only setup + // (persistCookie without persist) that a pure client-side render (no SSR inline script) + // would otherwise ignore - previously this read was gated on persist alone. + if (Theme._persist || Theme._persistCookie) { + const persisted = Theme.getPersisted(); + if (persisted) { + theme = persisted; + // An explicit persisted preset (anything other than "system") means the user pinned a theme; + // stop following the OS even when is present. + Theme._stopFollowingSystem = persisted !== Theme.SYSTEM_THEME; + } else if (systemRequested) { + // System mode is enabled but no value has been persisted yet. Avoid writing the + // resolved light/dark theme to storage during the initial set() - otherwise the next + // init would treat that concrete value as an explicit user choice and stop following + // the OS. Disable BOTH stores for the initial set() and re-enable them afterwards so + // SYSTEM_THEME remains the effective persisted indicator until the user picks one. + deferPersist = Theme._persist; + deferPersistCookie = Theme._persistCookie; + Theme._persist = false; + Theme._persistCookie = false; + } + } + + // Any `system` opt-in (system: true, bit-theme="system" or bit-theme-default="system") + // must on its own enable OS follow, otherwise setups without the bit-theme-system + // attribute and without a persisted "system" value would resolve the OS theme once at + // init but never get the prefers-color-scheme listener attached, because + // shouldFollowSystem() only considers the HTML attribute and persisted value. Runtime + // follow is still cleared when the user later pins a concrete theme via set(...). + if (systemRequested && !Theme._stopFollowingSystem) { + Theme._runtimeFollowSystem = true; + } + + Theme.set(theme, { fromInit: true }); + + if (deferPersist) { + Theme._persist = true; + } + + if (deferPersistCookie) { + Theme._persistCookie = true; + } + } + + public static onChange(fn: onThemeChangeType) { + Theme._onThemeChange = fn; + } + + public static get() { + // Report the theme that is actually applied: the bit-theme attribute is the source of + // truth (it is what is painted, and set()/OS-follow keep _currentTheme in sync with it). + // The persisted *preference* is a separate concept exposed via getPersisted(); the two + // can legitimately diverge - e.g. runtime OS-follow updates the attribute without + // rewriting storage - so get() must not substitute the persisted value here. + Theme._currentTheme = document.documentElement.getAttribute(Theme.THEME_ATTRIBUTE) || ''; + + return Theme._currentTheme; + } + + public static set(themeName: string, options?: ThemeSetOptions) { + // Reject null / undefined / empty inputs up-front so we never call setAttribute(...) with + // a value that coerces to the literal string "null" or "undefined". The non-null + // assertion below was unsafe because getActualTheme can return null for null input. + if (!themeName) return Theme._currentTheme; + + const fromInit = options?.fromInit === true; + const internalOs = options?.internalOsRefresh === true; + + if (!fromInit && !internalOs) { + if (themeName === Theme.SYSTEM_THEME) { + Theme._stopFollowingSystem = false; + Theme._runtimeFollowSystem = true; + } else { + Theme._stopFollowingSystem = true; + Theme._runtimeFollowSystem = false; + } + } + + const resolved = Theme.getActualTheme(themeName); + if (!resolved) return Theme._currentTheme; + Theme._currentTheme = resolved; + + if (Theme._persist) { + // localStorage can throw in Safari private mode, in iframes that block storage, + // when over quota, or under restrictive document policies (e.g. file:// in some + // browsers). Theme persistence is best-effort - never let it break theme switching. + try { + localStorage.setItem(Theme.THEME_STORAGE_KEY, themeName); + } catch { /* persistence unavailable; continue without storing */ } + } + + // Mirror the preference into the cookie so the server (BitThemeSsr.BuildRootThemeAttributes) + // can paint the matching theme on first render. Stored verbatim (the abstract key, e.g. + // "system" / "dark" / "fluent-light"), matching what localStorage holds. + if (Theme._persistCookie) { + Theme.writePreferenceCookie(themeName); + } + + const oldTheme = document.documentElement.getAttribute(Theme.THEME_ATTRIBUTE) || ''; + + document.documentElement.setAttribute(Theme.THEME_ATTRIBUTE, Theme._currentTheme); + + Theme.dispatchThemeChange(Theme._currentTheme, oldTheme); + + Theme.syncSystemThemeListener(); + + return Theme._currentTheme; + } + + public static toggleDarkLight() { + // Toggle relative to the configured dark theme: when the dark theme is active switch to + // light, otherwise switch to dark. Anchoring on the dark theme (rather than the light + // one) means a configured pair such as bit-theme-light="fluent-light" / + // bit-theme-dark="fluent-dark" toggles correctly in BOTH directions, and any other + // current value (a custom or unrecognized theme) resolves to the dark theme instead of + // silently collapsing to light. + Theme._currentTheme = Theme._currentTheme === Theme._darkTheme + ? Theme._lightTheme + : Theme._darkTheme; + + Theme.set(Theme._currentTheme); + + return Theme._currentTheme; + } + + /** Pins storage (when persist is on) to system and follows OS light/dark until an explicit preset is set. */ + public static useSystem() { + return Theme.set(Theme.SYSTEM_THEME); + } + + public static applyTheme(theme: Record, element?: HTMLElement) { + const el = element || document.body; + const keys = Object.keys(theme); + const prev = Theme._appliedVarKeys.get(el) || []; + prev.filter(key => !keys.includes(key)).forEach(key => el.style.removeProperty(key)); + keys.forEach(key => el.style.setProperty(key, theme[key])); + Theme._appliedVarKeys.set(el, keys); + } + + /** Removes --bit-* properties previously applied by applyTheme on the target (default document.body). */ + public static clearAppliedTheme(element?: HTMLElement) { + const el = element || document.body; + const keys = Theme._appliedVarKeys.get(el); + if (!keys || keys.length === 0) return; + keys.forEach(k => el.style.removeProperty(k)); + Theme._appliedVarKeys.delete(el); + } + + public static isSystemDark() { + return matchMedia('(prefers-color-scheme: dark)').matches; + } + + public static getPersisted() { + if (Theme._persist) { + // Mirror the write side: localStorage.getItem can throw under the same conditions as + // setItem (Safari private mode, blocked storage, etc.). Treat failure as "no persisted + // value" so the rest of the resolution chain (system / default / lightTheme) takes over. + try { + const stored = localStorage.getItem(Theme.THEME_STORAGE_KEY); + if (stored) return stored; + } catch { /* fall through to cookie / null */ } + } + + // Cookie-only persistence (or localStorage unavailable): read the same preference the + // server uses so the client and server agree on the stored choice. + if (Theme._persistCookie) { + return Theme.readPreferenceCookie(); + } + + return null; + } + + public static registerDotNetNotifier(dotNetRef: DotNetObject) { + Theme._dotnetNotifier = dotNetRef; + } + + public static unregisterDotNetNotifier() { + Theme._dotnetNotifier = null; + } + + private static writePreferenceCookie(value: string) { + try { + if (typeof document === 'undefined' || !value) return; + const secure = location.protocol === 'https:' ? '; Secure' : ''; + document.cookie = + `${COOKIE_NAME}=${encodeURIComponent(value)}; path=/; max-age=${COOKIE_MAX_AGE_SECONDS}; SameSite=Lax${secure}`; + } catch { /* cookies unavailable / blocked; best-effort like localStorage */ } + } + + private static readPreferenceCookie(): string | null { + try { + if (typeof document === 'undefined' || !document.cookie) return null; + const prefix = `${COOKIE_NAME}=`; + const match = document.cookie + .split(';') + .map(c => c.trim()) + .find(c => c.startsWith(prefix)); + return match ? decodeURIComponent(match.substring(prefix.length)) : null; + } catch { + return null; + } + } + + private static shouldFollowSystem(): boolean { + if (typeof document === 'undefined') return false; + if (Theme._stopFollowingSystem) return false; + // An explicitly persisted theme (anything other than SYSTEM_THEME) wins over the + // bit-theme-system attribute, otherwise a stale attribute could override the user's choice. + // Consider the persisted preference whenever any persistence source is active - including + // the cookie-only path (persistCookie without persist) that getPersisted() falls back to - + // so a persisted SYSTEM_THEME still attaches the OS-follow listener in a cookie-backed setup. + if (Theme._persist || Theme._persistCookie) { + const persisted = Theme.getPersisted(); + if (persisted && persisted !== Theme.SYSTEM_THEME) return false; + if (persisted === Theme.SYSTEM_THEME) return true; + } + if (Theme._runtimeFollowSystem) return true; + if (document.documentElement.hasAttribute(ATTR_THEME_SYSTEM)) return true; + return false; + } + + private static syncSystemThemeListener() { + Theme.detachSystemThemeListener(); + if (!Theme.shouldFollowSystem()) return; + Theme.attachSystemThemeListener(); + } + + private static attachSystemThemeListener() { + if (!window.matchMedia) return; + Theme._schemeMediaQuery = matchMedia('(prefers-color-scheme: dark)'); + const mq = Theme._schemeMediaQuery as MediaQueryList & { addListener?: (cb: () => void) => void }; + if (typeof mq.addEventListener === 'function') { + mq.addEventListener('change', Theme._onSchemeChange); + } else { + mq.addListener?.(Theme._onSchemeChange); + } + } + + private static detachSystemThemeListener() { + if (!Theme._schemeMediaQuery) return; + const mq = Theme._schemeMediaQuery as MediaQueryList & { removeListener?: (cb: () => void) => void }; + if (typeof mq.removeEventListener === 'function') { + mq.removeEventListener('change', Theme._onSchemeChange); + } else { + mq.removeListener?.(Theme._onSchemeChange); + } + Theme._schemeMediaQuery = null; + } + + private static applyResolvedSystemThemeFromOs() { + if (!Theme.shouldFollowSystem()) return; + + const resolved = Theme.isSystemDark() ? Theme._darkTheme : Theme._lightTheme; + const oldTheme = document.documentElement.getAttribute(Theme.THEME_ATTRIBUTE) || ''; + + if (resolved === oldTheme) return; + + Theme._currentTheme = resolved; + document.documentElement.setAttribute(Theme.THEME_ATTRIBUTE, resolved); + Theme.dispatchThemeChange(resolved, oldTheme); + } + + private static dispatchThemeChange(newTheme: string, oldTheme: string) { + // Isolate the application callback: a throwing _onThemeChange must not abort the + // _dotnetNotifier notification below (nor the Theme.set flow into syncSystemThemeListener()). + try { + Theme._onThemeChange?.(newTheme, oldTheme); + } catch { /* application callback failed; not actionable here */ } + const n = Theme._dotnetNotifier; + if (n) { + // Swallow rejections so a disposed circuit / receiver does not surface as an + // unhandled promise rejection. Theme dispatch is fire-and-forget by design. + n.invokeMethodAsync('NotifyThemeChangedFromJs', newTheme, oldTheme) + .catch(() => { /* receiver gone or invocation failed; nothing actionable here */ }); + } + } + + private static getActualTheme(theme: string | null) { + if (theme === Theme.SYSTEM_THEME) { + return Theme.isSystemDark() ? Theme._darkTheme : Theme._lightTheme; + } + + return theme; + } + } + + /** Attach or swap alternate theme stylesheets at runtime (prefer same-origin / trusted URLs). */ + export class ExternalTheme { + private static validateHref(href: string) { + const trimmed = href?.trimStart(); + if (!trimmed) { + throw new Error('Stylesheet href is required.'); + } + if (trimmed.startsWith('//') || trimmed.startsWith('\\\\') || + trimmed.startsWith('/\\') || trimmed.startsWith('\\/')) { + throw new Error('Stylesheet href must not be a protocol-relative URL.'); + } + if (/^javascript:/i.test(trimmed) || /^data:/i.test(trimmed) || /^vbscript:/i.test(trimmed)) { + throw new Error('Stylesheet href must not use a non-http scheme.'); + } + let url: URL; + try { + url = new URL(href, document.baseURI); + } catch { + throw new Error('Stylesheet href is not a valid URL.'); + } + if (url.protocol !== 'http:' && url.protocol !== 'https:') { + throw new Error(`Stylesheet href scheme '${url.protocol}' is not allowed.`); + } + if (url.origin !== location.origin) { + throw new Error('Stylesheet href must be same-origin.'); + } + } + + public static attach(linkId: string, href: string) { + ExternalTheme.validateHref(href); + + const existing = document.getElementById(linkId); + let link: HTMLLinkElement; + if (existing && existing.tagName === 'LINK') { + link = existing as HTMLLinkElement; + } else { + // No element, or an element with the same id but a different tag (e.g. a stale + // breakout defense-in-depth + case '\\': // CSS escape sequences + case '\0': + case '\n': + case '\r': + case '\f': // null / newlines / form feed + return true; + } + } + + // Comment markers could swallow trailing declarations in the inline-style concatenation + // (e.g. "red/*" eating the following ";--next:..." up to a later "*/"). + return value.Contains("/*", StringComparison.Ordinal) + || value.Contains("*/", StringComparison.Ordinal); + } + + /// + /// Returns a copy of with every branch object non-null, WITHOUT mutating + /// the caller's instance. Used by and so a + /// hand-constructed sparse theme (null branches, reachable via the public setters) can be walked + /// without a . + /// + /// + /// Only the container types that hold sub-objects (, + /// , , ) + /// are freshly allocated. Leaf-holding branch objects (the color/typography/etc. variants, whose + /// properties are all immutable s) are shared by reference: callers only ever + /// read from the normalized graph here - the merge writes into a separate fresh result - so no + /// shared object is mutated and no per-leaf copy is needed. Direct string leaves on the container + /// types (e.g. , , + /// ) are carried across explicitly. + /// + private static BitTheme NormalizeToNew(BitTheme? src) + { + src ??= new BitTheme(); + + return new BitTheme + { + Color = NormalizeColors(src.Color), + BoxShadow = src.BoxShadow ?? new(), + Spacing = src.Spacing ?? new(), + ZIndex = src.ZIndex ?? new(), + Shape = src.Shape ?? new(), + Typography = NormalizeTypography(src.Typography), + Motion = src.Motion ?? new(), + Layout = NormalizeLayout(src.Layout), + }; + } + + private static BitThemeColors NormalizeColors(BitThemeColors? src) + { + src ??= new BitThemeColors(); + + return new BitThemeColors + { + Primary = src.Primary ?? new(), + Secondary = src.Secondary ?? new(), + Tertiary = src.Tertiary ?? new(), + Info = src.Info ?? new(), + Success = src.Success ?? new(), + Warning = src.Warning ?? new(), + SevereWarning = src.SevereWarning ?? new(), + Error = src.Error ?? new(), + Foreground = src.Foreground ?? new(), + Background = src.Background ?? new(), + Border = src.Border ?? new(), + Neutral = src.Neutral ?? new(), + Required = src.Required, + }; + } + + private static BitThemeTypography NormalizeTypography(BitThemeTypography? src) + { + src ??= new BitThemeTypography(); + + return new BitThemeTypography + { + FontFamily = src.FontFamily, + FontWeight = src.FontWeight, + LineHeight = src.LineHeight, + GutterSize = src.GutterSize, + H1 = src.H1 ?? new(), + H2 = src.H2 ?? new(), + H3 = src.H3 ?? new(), + H4 = src.H4 ?? new(), + H5 = src.H5 ?? new(), + H6 = src.H6 ?? new(), + Subtitle1 = src.Subtitle1 ?? new(), + Subtitle2 = src.Subtitle2 ?? new(), + Body1 = src.Body1 ?? new(), + Body2 = src.Body2 ?? new(), + Button = src.Button ?? new(), + Caption1 = src.Caption1 ?? new(), + Caption2 = src.Caption2 ?? new(), + Overline = src.Overline ?? new(), + Inherit = src.Inherit ?? new(), + }; + } + + private static BitThemeLayout NormalizeLayout(BitThemeLayout? src) + { + src ??= new BitThemeLayout(); + + return new BitThemeLayout + { + Direction = src.Direction, + DensityScale = src.DensityScale, + Breakpoints = src.Breakpoints ?? new(), + }; } internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) { var result = new BitTheme(); + // Walk normalized COPIES so hand-constructed sparse themes (e.g. new BitTheme { Color = null } + // or new BitThemeColors { Primary = null }, both reachable via the public setters) can be + // traversed without NRE - WITHOUT mutating the caller's instances. (Previously this filled + // null branch objects in place on both passed themes as a side effect.) + bitTheme = NormalizeToNew(bitTheme); + other = NormalizeToNew(other); + result.Color.Primary.Main = bitTheme.Color.Primary.Main ?? other.Color.Primary.Main; result.Color.Primary.MainHover = bitTheme.Color.Primary.MainHover ?? other.Color.Primary.MainHover; result.Color.Primary.MainActive = bitTheme.Color.Primary.MainActive ?? other.Color.Primary.MainActive; @@ -376,6 +606,9 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.Color.Primary.LightHover = bitTheme.Color.Primary.LightHover ?? other.Color.Primary.LightHover; result.Color.Primary.LightActive = bitTheme.Color.Primary.LightActive ?? other.Color.Primary.LightActive; result.Color.Primary.Text = bitTheme.Color.Primary.Text ?? other.Color.Primary.Text; + result.Color.Primary.Disabled = bitTheme.Color.Primary.Disabled ?? other.Color.Primary.Disabled; + result.Color.Primary.DisabledText = bitTheme.Color.Primary.DisabledText ?? other.Color.Primary.DisabledText; + result.Color.Primary.Focus = bitTheme.Color.Primary.Focus ?? other.Color.Primary.Focus; result.Color.Secondary.Main = bitTheme.Color.Secondary.Main ?? other.Color.Secondary.Main; result.Color.Secondary.MainHover = bitTheme.Color.Secondary.MainHover ?? other.Color.Secondary.MainHover; @@ -387,6 +620,9 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.Color.Secondary.LightHover = bitTheme.Color.Secondary.LightHover ?? other.Color.Secondary.LightHover; result.Color.Secondary.LightActive = bitTheme.Color.Secondary.LightActive ?? other.Color.Secondary.LightActive; result.Color.Secondary.Text = bitTheme.Color.Secondary.Text ?? other.Color.Secondary.Text; + result.Color.Secondary.Disabled = bitTheme.Color.Secondary.Disabled ?? other.Color.Secondary.Disabled; + result.Color.Secondary.DisabledText = bitTheme.Color.Secondary.DisabledText ?? other.Color.Secondary.DisabledText; + result.Color.Secondary.Focus = bitTheme.Color.Secondary.Focus ?? other.Color.Secondary.Focus; result.Color.Tertiary.Main = bitTheme.Color.Tertiary.Main ?? other.Color.Tertiary.Main; result.Color.Tertiary.MainHover = bitTheme.Color.Tertiary.MainHover ?? other.Color.Tertiary.MainHover; @@ -398,6 +634,9 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.Color.Tertiary.LightHover = bitTheme.Color.Tertiary.LightHover ?? other.Color.Tertiary.LightHover; result.Color.Tertiary.LightActive = bitTheme.Color.Tertiary.LightActive ?? other.Color.Tertiary.LightActive; result.Color.Tertiary.Text = bitTheme.Color.Tertiary.Text ?? other.Color.Tertiary.Text; + result.Color.Tertiary.Disabled = bitTheme.Color.Tertiary.Disabled ?? other.Color.Tertiary.Disabled; + result.Color.Tertiary.DisabledText = bitTheme.Color.Tertiary.DisabledText ?? other.Color.Tertiary.DisabledText; + result.Color.Tertiary.Focus = bitTheme.Color.Tertiary.Focus ?? other.Color.Tertiary.Focus; result.Color.Info.Main = bitTheme.Color.Info.Main ?? other.Color.Info.Main; result.Color.Info.MainHover = bitTheme.Color.Info.MainHover ?? other.Color.Info.MainHover; @@ -409,6 +648,9 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.Color.Info.LightHover = bitTheme.Color.Info.LightHover ?? other.Color.Info.LightHover; result.Color.Info.LightActive = bitTheme.Color.Info.LightActive ?? other.Color.Info.LightActive; result.Color.Info.Text = bitTheme.Color.Info.Text ?? other.Color.Info.Text; + result.Color.Info.Disabled = bitTheme.Color.Info.Disabled ?? other.Color.Info.Disabled; + result.Color.Info.DisabledText = bitTheme.Color.Info.DisabledText ?? other.Color.Info.DisabledText; + result.Color.Info.Focus = bitTheme.Color.Info.Focus ?? other.Color.Info.Focus; result.Color.Success.Main = bitTheme.Color.Success.Main ?? other.Color.Success.Main; result.Color.Success.MainHover = bitTheme.Color.Success.MainHover ?? other.Color.Success.MainHover; @@ -420,6 +662,9 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.Color.Success.LightHover = bitTheme.Color.Success.LightHover ?? other.Color.Success.LightHover; result.Color.Success.LightActive = bitTheme.Color.Success.LightActive ?? other.Color.Success.LightActive; result.Color.Success.Text = bitTheme.Color.Success.Text ?? other.Color.Success.Text; + result.Color.Success.Disabled = bitTheme.Color.Success.Disabled ?? other.Color.Success.Disabled; + result.Color.Success.DisabledText = bitTheme.Color.Success.DisabledText ?? other.Color.Success.DisabledText; + result.Color.Success.Focus = bitTheme.Color.Success.Focus ?? other.Color.Success.Focus; result.Color.Warning.Main = bitTheme.Color.Warning.Main ?? other.Color.Warning.Main; result.Color.Warning.MainHover = bitTheme.Color.Warning.MainHover ?? other.Color.Warning.MainHover; @@ -431,6 +676,9 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.Color.Warning.LightHover = bitTheme.Color.Warning.LightHover ?? other.Color.Warning.LightHover; result.Color.Warning.LightActive = bitTheme.Color.Warning.LightActive ?? other.Color.Warning.LightActive; result.Color.Warning.Text = bitTheme.Color.Warning.Text ?? other.Color.Warning.Text; + result.Color.Warning.Disabled = bitTheme.Color.Warning.Disabled ?? other.Color.Warning.Disabled; + result.Color.Warning.DisabledText = bitTheme.Color.Warning.DisabledText ?? other.Color.Warning.DisabledText; + result.Color.Warning.Focus = bitTheme.Color.Warning.Focus ?? other.Color.Warning.Focus; result.Color.SevereWarning.Main = bitTheme.Color.SevereWarning.Main ?? other.Color.SevereWarning.Main; result.Color.SevereWarning.MainHover = bitTheme.Color.SevereWarning.MainHover ?? other.Color.SevereWarning.MainHover; @@ -442,6 +690,9 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.Color.SevereWarning.LightHover = bitTheme.Color.SevereWarning.LightHover ?? other.Color.SevereWarning.LightHover; result.Color.SevereWarning.LightActive = bitTheme.Color.SevereWarning.LightActive ?? other.Color.SevereWarning.LightActive; result.Color.SevereWarning.Text = bitTheme.Color.SevereWarning.Text ?? other.Color.SevereWarning.Text; + result.Color.SevereWarning.Disabled = bitTheme.Color.SevereWarning.Disabled ?? other.Color.SevereWarning.Disabled; + result.Color.SevereWarning.DisabledText = bitTheme.Color.SevereWarning.DisabledText ?? other.Color.SevereWarning.DisabledText; + result.Color.SevereWarning.Focus = bitTheme.Color.SevereWarning.Focus ?? other.Color.SevereWarning.Focus; result.Color.Error.Main = bitTheme.Color.Error.Main ?? other.Color.Error.Main; result.Color.Error.MainHover = bitTheme.Color.Error.MainHover ?? other.Color.Error.MainHover; @@ -453,6 +704,9 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.Color.Error.LightHover = bitTheme.Color.Error.LightHover ?? other.Color.Error.LightHover; result.Color.Error.LightActive = bitTheme.Color.Error.LightActive ?? other.Color.Error.LightActive; result.Color.Error.Text = bitTheme.Color.Error.Text ?? other.Color.Error.Text; + result.Color.Error.Disabled = bitTheme.Color.Error.Disabled ?? other.Color.Error.Disabled; + result.Color.Error.DisabledText = bitTheme.Color.Error.DisabledText ?? other.Color.Error.DisabledText; + result.Color.Error.Focus = bitTheme.Color.Error.Focus ?? other.Color.Error.Focus; result.Color.Foreground.Primary = bitTheme.Color.Foreground.Primary ?? other.Color.Foreground.Primary; result.Color.Foreground.PrimaryHover = bitTheme.Color.Foreground.PrimaryHover ?? other.Color.Foreground.PrimaryHover; @@ -463,6 +717,9 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.Color.Foreground.PrimaryLight = bitTheme.Color.Foreground.PrimaryLight ?? other.Color.Foreground.PrimaryLight; result.Color.Foreground.PrimaryLightHover = bitTheme.Color.Foreground.PrimaryLightHover ?? other.Color.Foreground.PrimaryLightHover; result.Color.Foreground.PrimaryLightActive = bitTheme.Color.Foreground.PrimaryLightActive ?? other.Color.Foreground.PrimaryLightActive; + result.Color.Foreground.PrimaryDisabled = bitTheme.Color.Foreground.PrimaryDisabled ?? other.Color.Foreground.PrimaryDisabled; + result.Color.Foreground.PrimaryDisabledText = bitTheme.Color.Foreground.PrimaryDisabledText ?? other.Color.Foreground.PrimaryDisabledText; + result.Color.Foreground.PrimaryFocus = bitTheme.Color.Foreground.PrimaryFocus ?? other.Color.Foreground.PrimaryFocus; result.Color.Foreground.Secondary = bitTheme.Color.Foreground.Secondary ?? other.Color.Foreground.Secondary; result.Color.Foreground.SecondaryHover = bitTheme.Color.Foreground.SecondaryHover ?? other.Color.Foreground.SecondaryHover; result.Color.Foreground.SecondaryActive = bitTheme.Color.Foreground.SecondaryActive ?? other.Color.Foreground.SecondaryActive; @@ -472,6 +729,9 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.Color.Foreground.SecondaryLight = bitTheme.Color.Foreground.SecondaryLight ?? other.Color.Foreground.SecondaryLight; result.Color.Foreground.SecondaryLightHover = bitTheme.Color.Foreground.SecondaryLightHover ?? other.Color.Foreground.SecondaryLightHover; result.Color.Foreground.SecondaryLightActive = bitTheme.Color.Foreground.SecondaryLightActive ?? other.Color.Foreground.SecondaryLightActive; + result.Color.Foreground.SecondaryDisabled = bitTheme.Color.Foreground.SecondaryDisabled ?? other.Color.Foreground.SecondaryDisabled; + result.Color.Foreground.SecondaryDisabledText = bitTheme.Color.Foreground.SecondaryDisabledText ?? other.Color.Foreground.SecondaryDisabledText; + result.Color.Foreground.SecondaryFocus = bitTheme.Color.Foreground.SecondaryFocus ?? other.Color.Foreground.SecondaryFocus; result.Color.Foreground.Tertiary = bitTheme.Color.Foreground.Tertiary ?? other.Color.Foreground.Tertiary; result.Color.Foreground.TertiaryHover = bitTheme.Color.Foreground.TertiaryHover ?? other.Color.Foreground.TertiaryHover; result.Color.Foreground.TertiaryActive = bitTheme.Color.Foreground.TertiaryActive ?? other.Color.Foreground.TertiaryActive; @@ -481,6 +741,9 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.Color.Foreground.TertiaryLight = bitTheme.Color.Foreground.TertiaryLight ?? other.Color.Foreground.TertiaryLight; result.Color.Foreground.TertiaryLightHover = bitTheme.Color.Foreground.TertiaryLightHover ?? other.Color.Foreground.TertiaryLightHover; result.Color.Foreground.TertiaryLightActive = bitTheme.Color.Foreground.TertiaryLightActive ?? other.Color.Foreground.TertiaryLightActive; + result.Color.Foreground.TertiaryDisabled = bitTheme.Color.Foreground.TertiaryDisabled ?? other.Color.Foreground.TertiaryDisabled; + result.Color.Foreground.TertiaryDisabledText = bitTheme.Color.Foreground.TertiaryDisabledText ?? other.Color.Foreground.TertiaryDisabledText; + result.Color.Foreground.TertiaryFocus = bitTheme.Color.Foreground.TertiaryFocus ?? other.Color.Foreground.TertiaryFocus; result.Color.Foreground.Disabled = bitTheme.Color.Foreground.Disabled ?? other.Color.Foreground.Disabled; result.Color.Background.Primary = bitTheme.Color.Background.Primary ?? other.Color.Background.Primary; @@ -492,6 +755,9 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.Color.Background.PrimaryLight = bitTheme.Color.Background.PrimaryLight ?? other.Color.Background.PrimaryLight; result.Color.Background.PrimaryLightHover = bitTheme.Color.Background.PrimaryLightHover ?? other.Color.Background.PrimaryLightHover; result.Color.Background.PrimaryLightActive = bitTheme.Color.Background.PrimaryLightActive ?? other.Color.Background.PrimaryLightActive; + result.Color.Background.PrimaryDisabled = bitTheme.Color.Background.PrimaryDisabled ?? other.Color.Background.PrimaryDisabled; + result.Color.Background.PrimaryDisabledText = bitTheme.Color.Background.PrimaryDisabledText ?? other.Color.Background.PrimaryDisabledText; + result.Color.Background.PrimaryFocus = bitTheme.Color.Background.PrimaryFocus ?? other.Color.Background.PrimaryFocus; result.Color.Background.Secondary = bitTheme.Color.Background.Secondary ?? other.Color.Background.Secondary; result.Color.Background.SecondaryHover = bitTheme.Color.Background.SecondaryHover ?? other.Color.Background.SecondaryHover; result.Color.Background.SecondaryActive = bitTheme.Color.Background.SecondaryActive ?? other.Color.Background.SecondaryActive; @@ -501,6 +767,9 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.Color.Background.SecondaryLight = bitTheme.Color.Background.SecondaryLight ?? other.Color.Background.SecondaryLight; result.Color.Background.SecondaryLightHover = bitTheme.Color.Background.SecondaryLightHover ?? other.Color.Background.SecondaryLightHover; result.Color.Background.SecondaryLightActive = bitTheme.Color.Background.SecondaryLightActive ?? other.Color.Background.SecondaryLightActive; + result.Color.Background.SecondaryDisabled = bitTheme.Color.Background.SecondaryDisabled ?? other.Color.Background.SecondaryDisabled; + result.Color.Background.SecondaryDisabledText = bitTheme.Color.Background.SecondaryDisabledText ?? other.Color.Background.SecondaryDisabledText; + result.Color.Background.SecondaryFocus = bitTheme.Color.Background.SecondaryFocus ?? other.Color.Background.SecondaryFocus; result.Color.Background.Tertiary = bitTheme.Color.Background.Tertiary ?? other.Color.Background.Tertiary; result.Color.Background.TertiaryHover = bitTheme.Color.Background.TertiaryHover ?? other.Color.Background.TertiaryHover; result.Color.Background.TertiaryActive = bitTheme.Color.Background.TertiaryActive ?? other.Color.Background.TertiaryActive; @@ -510,6 +779,9 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.Color.Background.TertiaryLight = bitTheme.Color.Background.TertiaryLight ?? other.Color.Background.TertiaryLight; result.Color.Background.TertiaryLightHover = bitTheme.Color.Background.TertiaryLightHover ?? other.Color.Background.TertiaryLightHover; result.Color.Background.TertiaryLightActive = bitTheme.Color.Background.TertiaryLightActive ?? other.Color.Background.TertiaryLightActive; + result.Color.Background.TertiaryDisabled = bitTheme.Color.Background.TertiaryDisabled ?? other.Color.Background.TertiaryDisabled; + result.Color.Background.TertiaryDisabledText = bitTheme.Color.Background.TertiaryDisabledText ?? other.Color.Background.TertiaryDisabledText; + result.Color.Background.TertiaryFocus = bitTheme.Color.Background.TertiaryFocus ?? other.Color.Background.TertiaryFocus; result.Color.Background.Disabled = bitTheme.Color.Background.Disabled ?? other.Color.Background.Disabled; result.Color.Background.Overlay = bitTheme.Color.Background.Overlay ?? other.Color.Background.Overlay; @@ -522,6 +794,9 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.Color.Border.PrimaryLight = bitTheme.Color.Border.PrimaryLight ?? other.Color.Border.PrimaryLight; result.Color.Border.PrimaryLightHover = bitTheme.Color.Border.PrimaryLightHover ?? other.Color.Border.PrimaryLightHover; result.Color.Border.PrimaryLightActive = bitTheme.Color.Border.PrimaryLightActive ?? other.Color.Border.PrimaryLightActive; + result.Color.Border.PrimaryDisabled = bitTheme.Color.Border.PrimaryDisabled ?? other.Color.Border.PrimaryDisabled; + result.Color.Border.PrimaryDisabledText = bitTheme.Color.Border.PrimaryDisabledText ?? other.Color.Border.PrimaryDisabledText; + result.Color.Border.PrimaryFocus = bitTheme.Color.Border.PrimaryFocus ?? other.Color.Border.PrimaryFocus; result.Color.Border.Secondary = bitTheme.Color.Border.Secondary ?? other.Color.Border.Secondary; result.Color.Border.SecondaryHover = bitTheme.Color.Border.SecondaryHover ?? other.Color.Border.SecondaryHover; result.Color.Border.SecondaryActive = bitTheme.Color.Border.SecondaryActive ?? other.Color.Border.SecondaryActive; @@ -531,6 +806,9 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.Color.Border.SecondaryLight = bitTheme.Color.Border.SecondaryLight ?? other.Color.Border.SecondaryLight; result.Color.Border.SecondaryLightHover = bitTheme.Color.Border.SecondaryLightHover ?? other.Color.Border.SecondaryLightHover; result.Color.Border.SecondaryLightActive = bitTheme.Color.Border.SecondaryLightActive ?? other.Color.Border.SecondaryLightActive; + result.Color.Border.SecondaryDisabled = bitTheme.Color.Border.SecondaryDisabled ?? other.Color.Border.SecondaryDisabled; + result.Color.Border.SecondaryDisabledText = bitTheme.Color.Border.SecondaryDisabledText ?? other.Color.Border.SecondaryDisabledText; + result.Color.Border.SecondaryFocus = bitTheme.Color.Border.SecondaryFocus ?? other.Color.Border.SecondaryFocus; result.Color.Border.Tertiary = bitTheme.Color.Border.Tertiary ?? other.Color.Border.Tertiary; result.Color.Border.TertiaryHover = bitTheme.Color.Border.TertiaryHover ?? other.Color.Border.TertiaryHover; result.Color.Border.TertiaryActive = bitTheme.Color.Border.TertiaryActive ?? other.Color.Border.TertiaryActive; @@ -540,6 +818,9 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.Color.Border.TertiaryLight = bitTheme.Color.Border.TertiaryLight ?? other.Color.Border.TertiaryLight; result.Color.Border.TertiaryLightHover = bitTheme.Color.Border.TertiaryLightHover ?? other.Color.Border.TertiaryLightHover; result.Color.Border.TertiaryLightActive = bitTheme.Color.Border.TertiaryLightActive ?? other.Color.Border.TertiaryLightActive; + result.Color.Border.TertiaryDisabled = bitTheme.Color.Border.TertiaryDisabled ?? other.Color.Border.TertiaryDisabled; + result.Color.Border.TertiaryDisabledText = bitTheme.Color.Border.TertiaryDisabledText ?? other.Color.Border.TertiaryDisabledText; + result.Color.Border.TertiaryFocus = bitTheme.Color.Border.TertiaryFocus ?? other.Color.Border.TertiaryFocus; result.Color.Border.Disabled = bitTheme.Color.Border.Disabled ?? other.Color.Border.Disabled; result.Color.Required = bitTheme.Color.Required ?? other.Color.Required; @@ -602,6 +883,7 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.BoxShadow.S22 = bitTheme.BoxShadow.S22 ?? other.BoxShadow.S22; result.BoxShadow.S23 = bitTheme.BoxShadow.S23 ?? other.BoxShadow.S23; result.BoxShadow.S24 = bitTheme.BoxShadow.S24 ?? other.BoxShadow.S24; + result.BoxShadow.FocusRing = bitTheme.BoxShadow.FocusRing ?? other.BoxShadow.FocusRing; result.Spacing.ScalingFactor = bitTheme.Spacing.ScalingFactor ?? other.Spacing.ScalingFactor; @@ -614,6 +896,8 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.Shape.BorderRadius = bitTheme.Shape.BorderRadius ?? other.Shape.BorderRadius; result.Shape.BorderWidth = bitTheme.Shape.BorderWidth ?? other.Shape.BorderWidth; result.Shape.BorderStyle = bitTheme.Shape.BorderStyle ?? other.Shape.BorderStyle; + result.Shape.FocusRingWidth = bitTheme.Shape.FocusRingWidth ?? other.Shape.FocusRingWidth; + result.Shape.FocusRingOffset = bitTheme.Shape.FocusRingOffset ?? other.Shape.FocusRingOffset; result.Typography.FontFamily = bitTheme.Typography.FontFamily ?? other.Typography.FontFamily; result.Typography.FontWeight = bitTheme.Typography.FontWeight ?? other.Typography.FontWeight; @@ -715,6 +999,20 @@ internal static BitTheme Merge(BitTheme bitTheme, BitTheme other) result.Typography.Subtitle2.LineHeight = bitTheme.Typography.Subtitle2.LineHeight ?? other.Typography.Subtitle2.LineHeight; result.Typography.Subtitle2.LetterSpacing = bitTheme.Typography.Subtitle2.LetterSpacing ?? other.Typography.Subtitle2.LetterSpacing; + result.Motion.Duration = bitTheme.Motion.Duration ?? other.Motion.Duration; + result.Motion.DurationShort = bitTheme.Motion.DurationShort ?? other.Motion.DurationShort; + result.Motion.DurationLong = bitTheme.Motion.DurationLong ?? other.Motion.DurationLong; + result.Motion.EasingStandard = bitTheme.Motion.EasingStandard ?? other.Motion.EasingStandard; + + result.Layout.Direction = bitTheme.Layout.Direction ?? other.Layout.Direction; + result.Layout.DensityScale = bitTheme.Layout.DensityScale ?? other.Layout.DensityScale; + result.Layout.Breakpoints.Xs = bitTheme.Layout.Breakpoints.Xs ?? other.Layout.Breakpoints.Xs; + result.Layout.Breakpoints.Sm = bitTheme.Layout.Breakpoints.Sm ?? other.Layout.Breakpoints.Sm; + result.Layout.Breakpoints.Md = bitTheme.Layout.Breakpoints.Md ?? other.Layout.Breakpoints.Md; + result.Layout.Breakpoints.Lg = bitTheme.Layout.Breakpoints.Lg ?? other.Layout.Breakpoints.Lg; + result.Layout.Breakpoints.Xl = bitTheme.Layout.Breakpoints.Xl ?? other.Layout.Breakpoints.Xl; + result.Layout.Breakpoints.Xxl = bitTheme.Layout.Breakpoints.Xxl ?? other.Layout.Breakpoints.Xxl; + return result; } } diff --git a/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeName.cs b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeName.cs new file mode 100644 index 0000000000..5350f29292 --- /dev/null +++ b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeName.cs @@ -0,0 +1,120 @@ +namespace Bit.BlazorUI; + +/// +/// Type-safe wrapper for the bit-theme attribute name. Use the static factories +/// (, , , etc.) for the well-known presets; +/// call for app-specific theme names that match your CSS. +/// +/// +/// +/// Existing callers can keep using the string constants on ; +/// this struct is additive and provides an implicit conversion to so it slots +/// straight into without ceremony. +/// +/// +/// The wrapper normalizes the value (trim + lower-case invariant) so Custom("Fluent-Light") +/// and BitThemeName.FluentLight compare equal. That mirrors the JS side, which writes the +/// value verbatim onto document.documentElement and lets CSS selectors (:root[bit-theme="…"]) +/// match in a case-sensitive fashion against the canonical lowercase form. +/// +/// +public readonly struct BitThemeName : IEquatable +{ + private readonly string? _value; + + private BitThemeName(string value) + { + _value = value; + } + + /// The normalized theme name (trimmed, lower-case invariant). + public string Value => _value ?? BitThemePresets.Light; + + /// Light preset ("light"). + public static BitThemeName Light { get; } = new(BitThemePresets.Light); + + /// Dark preset ("dark"). + public static BitThemeName Dark { get; } = new(BitThemePresets.Dark); + + /// Fluent base preset ("fluent"). + public static BitThemeName Fluent { get; } = new(BitThemePresets.Fluent); + + /// Fluent light preset ("fluent-light"). + public static BitThemeName FluentLight { get; } = new(BitThemePresets.FluentLight); + + /// Fluent dark preset ("fluent-dark"). + public static BitThemeName FluentDark { get; } = new(BitThemePresets.FluentDark); + + /// Special pseudo-preset that follows OS prefers-color-scheme ("system"). + public static BitThemeName System { get; } = new(BitThemePresets.System); + + /// + /// Wraps a custom theme name. Whitespace is trimmed and the result is lower-cased. The normalized + /// value must be a safe [a-z0-9-] token of at most 64 characters so it matches the SSR + /// theme-token parsing rules (see BitThemeSsr.NormalizeThemeToken) and keeps runtime and + /// first-paint validation consistent for persisted custom themes. + /// + /// + /// When is null, empty, whitespace, longer than 64 characters, or contains + /// characters outside [a-z0-9-] after normalization. + /// + public static BitThemeName Custom(string name) + { + ArgumentException.ThrowIfNullOrWhiteSpace(name); + + var token = NormalizeToken(name, out var error) ?? throw new ArgumentException(error, nameof(name)); + + return new BitThemeName(token); + } + + /// + /// Normalizes (trim + lower-case invariant) and validates a theme token, returning + /// with a reason in when the value is blank, longer than 64 characters, or contains + /// characters outside [a-z0-9-]. Shared with BitThemeSsr.NormalizeThemeToken so runtime and + /// first-paint validation stay identical; each caller maps the failure onto its own contract + /// ( throws, the SSR helper returns ). + /// + internal static string? NormalizeToken(string? value, out string? error) + { + if (string.IsNullOrWhiteSpace(value)) + { + error = "Theme name must not be empty or whitespace."; + return null; + } + + var token = value.Trim().ToLowerInvariant(); + + if (token.Length > 64) + { + error = "Theme name must be at most 64 characters."; + return null; + } + + foreach (var ch in token) + { + var allowed = (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '-'; + if (allowed is false) + { + error = "Theme name may only contain lower-case letters, digits, and hyphens."; + return null; + } + } + + error = null; + return token; + } + + /// Implicitly converts to the underlying string so existing string-typed APIs accept the wrapper unchanged. + public static implicit operator string(BitThemeName name) => name.Value; + + public override string ToString() => Value; + + public bool Equals(BitThemeName other) => string.Equals(Value, other.Value, StringComparison.Ordinal); + + public override bool Equals(object? obj) => obj is BitThemeName other && Equals(other); + + public override int GetHashCode() => StringComparer.Ordinal.GetHashCode(Value); + + public static bool operator ==(BitThemeName left, BitThemeName right) => left.Equals(right); + public static bool operator !=(BitThemeName left, BitThemeName right) => !left.Equals(right); +} diff --git a/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeNotifications.cs b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeNotifications.cs new file mode 100644 index 0000000000..373aaafb5e --- /dev/null +++ b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeNotifications.cs @@ -0,0 +1,64 @@ +using Microsoft.Extensions.Logging; + +namespace Bit.BlazorUI; + +/// +/// Raised when the global bit-theme document attribute changes (including OS-driven updates when following system theme). +/// Subscribe in scoped components; requires interop at least once per circuit so the client script can notify .NET. +/// +/// +/// +/// Subscribers MUST unsubscribe when their component is disposed. +/// is registered as a scoped service, so a leaked handler keeps the (potentially disposed) component +/// rooted for the lifetime of the circuit and produces re-renders on torn-down state. +/// +/// +/// Handlers are invoked on whichever thread is called from (typically the JS +/// interop callback thread). If you need to update Blazor component state, marshal back through +/// ComponentBase.InvokeAsync(StateHasChanged). +/// +/// +/// A throwing handler does not prevent the remaining handlers from running and is logged when an +/// is registered in DI. +/// +/// +public sealed class BitThemeNotifications +{ + private readonly ILogger? _logger; + + public BitThemeNotifications() + { + } + + public BitThemeNotifications(ILoggerFactory? loggerFactory) + { + _logger = loggerFactory?.CreateLogger(); + } + + /// Fires after BitBlazorUI.Theme.set, toggleDarkLight, or prefers-color-scheme updates while following system theme. + public event EventHandler? ThemeChanged; + + internal void Raise(string? newTheme, string? oldTheme) + { + var handler = ThemeChanged; + if (handler is null) return; + + var args = new BitThemeChangedEventArgs(newTheme, oldTheme); + + // Invoke each subscriber in isolation. EventHandler.Invoke would short-circuit the + // remaining delegates on the first throw, and any escaping exception from the JS-invoked + // path can fault the circuit. Per-subscriber try/catch + log is the correct contract for + // a notification service consumed by arbitrary user code. + foreach (var subscriber in handler.GetInvocationList()) + { + try + { + ((EventHandler)subscriber).Invoke(this, args); + } + catch (Exception ex) + { + _logger?.LogError(ex, "BitThemeNotifications.ThemeChanged handler threw and was suppressed."); + } + } + } +} diff --git a/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeProvider.cs b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeProvider.cs index 9ccecda73e..984b5bab6e 100644 --- a/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeProvider.cs +++ b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeProvider.cs @@ -1,6 +1,6 @@ namespace Bit.BlazorUI; -public partial class BitThemeProvider : ComponentBase +public class BitThemeProvider : ComponentBase { /// /// The content of the ThemeProvider. @@ -8,8 +8,18 @@ public partial class BitThemeProvider : ComponentBase [Parameter] public RenderFragment? ChildContent { get; set; } /// - /// The element used for the root node. + /// The element used for the root node. Ignored whenever no wrapping element is rendered - that is, + /// when there is nothing to apply at this layer: both and + /// are , or is + /// and an unnamed is passed through directly + /// (no to re-expose it under). /// + /// + /// When a wrapping element is rendered it defaults to display:contents so it produces no + /// layout box and stays transparent to the surrounding flex/grid layout, while still letting the + /// applied CSS custom properties inherit to descendants. Supply style="display:…" via the + /// splatted attributes to opt back into a real box (e.g. when you also set box-model styles on it). + /// [Parameter] public string? RootElement { get; set; } /// @@ -17,55 +27,213 @@ public partial class BitThemeProvider : ComponentBase /// [Parameter] public BitTheme? Theme { get; set; } + /// + /// Opts this provider into treating and as + /// immutable ("frozen"). When , the provider skips the relatively + /// expensive per-render rebuild (the merge that allocates a fresh + /// all-token plus the ~300-entry CSS-variable string build) whenever both + /// references are unchanged since the last render - it just keeps the previously cached result. + /// + /// + /// This is a performance opt-in for the common case where callers hand the provider a stable + /// instance and never mutate it in place. The trade-off is that in-place + /// mutation of a frozen theme is NOT detected (the references don't change, so nothing is + /// rebuilt or re-cascaded); assign a new instance to apply changes. + /// Leave this (the default) to keep the mutation-in-place behavior where + /// every parameters update rebuilds from the current token values. + /// + [Parameter] public bool Frozen { get; set; } + /// /// Optional name for ; when set, consumers use [CascadingParameter(Name = …)]. - /// The cascaded is the merge of with (same as inline CSS variables on this provider’s root). + /// The cascaded is the merge of with (same as inline CSS variables on this provider's root). /// [Parameter] public string? ThemeName { get; set; } + /// + /// Catch-all for HTML attributes splatted onto the wrapping element (e.g. class, id, + /// data-*, ARIA roles). Only emitted when this provider renders a wrapping element - that + /// is, when a local is applied (its CSS variables need a host element), or + /// when a cascading is re-exposed under a custom . + /// When there is nothing to apply at this layer (no local and no + /// ) the provider renders just and these + /// attributes are ignored. + /// + [Parameter(CaptureUnmatchedValues = true)] + public IReadOnlyDictionary? AdditionalAttributes { get; set; } + [CascadingParameter] public BitTheme? ParentTheme { get; set; } - - protected override void BuildRenderTree(RenderTreeBuilder builder) + // Cached merge result and the inline `style` string. + // + // BitTheme is mutable (every node is a class with public setters), so callers can keep a + // single Theme/ParentTheme instance and mutate sub-properties between renders. We therefore + // rebuild the merge + CSS string on every parameters update and use CSS-string equality as a + // cheap structural check: when the rebuilt output matches the cached one we keep the cached + // merged-theme reference, which keeps CascadingValue reference-stable so + // descendants don't re-render on parent re-renders that didn't actually change tokens. + // (CascadingValue for reference types uses ReferenceEquals for change detection, and + // BitThemeMapper.Merge produces a fresh BitTheme on every call.) + private BitTheme? _cachedMergedTheme; + private string? _cachedCssVarStyle; + + // The Theme/ParentTheme references the cached result above was built from. Only consulted when + // Frozen is true, to short-circuit the merge + CSS rebuild when neither reference changed. + private BitTheme? _lastTheme; + private BitTheme? _lastParentTheme; + + protected override void OnParametersSet() { - var seq = 0; + if (Theme is null && ParentTheme is null) + { + // No tokens to apply at this layer; we'll render ChildContent directly. + _cachedMergedTheme = null; + _cachedCssVarStyle = null; + return; + } if (Theme is null) { - builder.AddContent(seq, ChildContent); + // Local theme not set, but a ParentTheme is cascading from above. Keep it as the value + // we would re-expose, and emit no inline style at this layer (the ancestor provider owns + // those CSS vars). BuildRenderTree only renders a wrapper + CascadingValue when a custom + // ThemeName needs the value re-exposed under that name; otherwise it renders ChildContent + // directly and relies on the ancestor's existing unnamed cascade, avoiding a redundant + // wrapper element and duplicate CascadingValue. + _cachedMergedTheme = ParentTheme; + _cachedCssVarStyle = null; return; } - var mergedTheme = Theme!; + // Frozen fast path: when the caller opts into treating the theme references as immutable, + // skip the merge + CSS-string rebuild entirely if neither Theme nor ParentTheme changed by + // reference since the cached result was produced. _cachedCssVarStyle is only non-null after a + // rebuild in this branch, so this guards against reusing a stale cache from the Theme-null / + // no-tokens branches above. + if (Frozen + && _cachedCssVarStyle is not null + && ReferenceEquals(Theme, _lastTheme) + && ReferenceEquals(ParentTheme, _lastParentTheme)) + { + return; + } - if (ParentTheme is not null) + // Always produce a FRESH merged BitTheme (BitThemeMapper.Merge allocates a new instance), + // even when there is no ParentTheme. This keeps the CascadingValue change-detection + // behavior consistent: previously the no-parent path cascaded the caller's own Theme instance, + // so an in-place mutation of that instance produced the same reference and CascadingValue + // (which compares reference types with ReferenceEquals) never notified [CascadingParameter] + // consumers - whereas the with-parent path always allocated a new reference and did notify. + // Going through Merge in both cases means a token change yields a new reference (consumers are + // notified), while the CSS-string equality suppression below preserves the cached reference + // when nothing actually changed (consumers are not woken up needlessly). + var mergedTheme = BitThemeMapper.Merge(Theme, ParentTheme ?? new BitTheme()); + + var cssVarStyle = BuildCssVarStyle(mergedTheme); + + // Record the references this result was built from so a subsequent Frozen render can detect + // that nothing changed by reference and skip the rebuild above. + _lastTheme = Theme; + _lastParentTheme = ParentTheme; + + // Suppress propagation when the produced output is identical to the previous render - + // this preserves the cascaded reference and avoids waking up every descendant consumer + // when a parent re-renders without touching the theme. + if (_cachedMergedTheme is not null && string.Equals(_cachedCssVarStyle, cssVarStyle, StringComparison.Ordinal)) { - mergedTheme = BitThemeMapper.Merge(Theme, ParentTheme); + return; } - var cssVars = BitThemeMapper.MapToCssVariables(mergedTheme); - var cssVarStyle = string.Join(';', cssVars.Select(kv => $"{kv.Key}:{kv.Value}")); + _cachedMergedTheme = mergedTheme; + _cachedCssVarStyle = cssVarStyle; + } - builder.OpenElement(seq++, RootElement ?? "div"); - builder.AddAttribute(seq++, "style", cssVarStyle); + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + if (_cachedMergedTheme is null) + { + // No local Theme override and no parent theme to re-cascade: render ChildContent as-is. + builder.AddContent(0, ChildContent); + return; + } - builder.OpenComponent>(seq++); - if (ThemeName is not null) + if (_cachedCssVarStyle is null && ThemeName is null) + { + // Nothing to apply at this layer: no CSS variables to emit (Theme is null - only a + // ParentTheme is cascading from above) and no custom ThemeName to re-expose the value + // under. The ancestor's unnamed CascadingValue already reaches our + // descendants, so rendering ChildContent directly preserves the cascade while avoiding + // a redundant wrapper element and a duplicate CascadingValue. + builder.AddContent(0, ChildContent); + return; + } + + builder.OpenElement(1, RootElement ?? "div"); + + // Collect user attributes into a single dictionary (splatted via AddMultipleAttributes so + // the render-tree diff reconciles a changing attribute set correctly), pulling out any + // "style" entry first so we can merge it with our CSS-variable declarations. It is added + // separately below rather than left in the splat because last-write-wins semantics on + // RenderTreeBuilder mean a splat followed by AddAttribute("style", ...) would silently drop + // the user's style. + string? userStyle = null; + if (AdditionalAttributes is not null) + { + var attributes = new Dictionary(StringComparer.Ordinal); + foreach (var kv in AdditionalAttributes) + { + if (string.Equals(kv.Key, "style", StringComparison.OrdinalIgnoreCase)) + { + userStyle = kv.Value?.ToString(); + continue; + } + + attributes[kv.Key] = kv.Value; + } + + builder.AddMultipleAttributes(2, attributes); + } + + // Compose the inline style. The wrapper defaults to `display:contents` so it produces no + // layout box of its own: the element must exist for CSS custom properties to inherit to + // descendants (inheritance follows the DOM tree, not the box tree), but it should not + // perturb the surrounding flex/grid layout or child selectors the way a default `div` + // block box would. The CSS-variable declarations come next, and the user-supplied style + // comes last so a caller can override both the layout mode (e.g. `style="display:flex"`) + // and any individual token on conflict. + var style = "display:contents"; + if (_cachedCssVarStyle is not null) { - builder.AddAttribute(seq++, "Name", ThemeName); - builder.AddAttribute(seq++, "Value", mergedTheme); + style = $"{style};{_cachedCssVarStyle}"; } - else + if (userStyle is not null) { - builder.AddAttribute(seq++, "Value", mergedTheme); + style = $"{style};{userStyle}"; } - builder.AddAttribute(seq++, "ChildContent", (RenderFragment)(builder2 => builder2.AddContent(seq, ChildContent))); + + builder.AddAttribute(3, "style", style); + + builder.OpenComponent>(4); + if (ThemeName is not null) + { + builder.AddAttribute(5, "Name", ThemeName); + } + builder.AddAttribute(6, "Value", _cachedMergedTheme); + // IMPORTANT: do NOT close over a mutable outer sequence counter. The lambda runs lazily + // during the cascade's render pass, so capturing a mutable local would feed Blazor a + // sequence number that varies per render and defeats its diff. Sequence numbers inside a + // RenderFragment are local to that fragment, so a constant is what we want. + builder.AddAttribute(7, "ChildContent", (RenderFragment)(b => b.AddContent(0, ChildContent))); builder.CloseComponent(); builder.CloseElement(); + } - base.BuildRenderTree(builder); + private static string BuildCssVarStyle(BitTheme theme) + { + var cssVars = BitThemeUtilities.ToCssVariables(theme); + return string.Join(';', cssVars.Select(kv => $"{kv.Key}:{kv.Value}")); } } diff --git a/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSerialization.cs b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSerialization.cs index 7163386f78..b07a41fb51 100644 --- a/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSerialization.cs +++ b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSerialization.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Reflection; -using System.Text.Json; +using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; @@ -41,6 +39,28 @@ public static BitTheme Deserialize(string? json) return theme; } + /// + /// Normalizes in place so every nested branch object is non-null, + /// matching a freshly-constructed new BitTheme(). Returns the same instance for + /// convenient chaining. + /// + /// + /// Exposed for the CSS-variable mapper and merge entry points (): + /// those walk the graph (theme.Color.Primary.Main, theme.Layout.Breakpoints.Xs, …) + /// and would throw on a hand-constructed sparse theme + /// (for example new BitTheme { Color = null } or new BitThemeColors { Primary = null }, + /// both reachable because the branch setters are public). The deserialization path already runs + /// this; running it at the mapper/merge entry covers the common hand-constructed path too. + /// + internal static BitTheme Normalize(BitTheme theme) + { + ArgumentNullException.ThrowIfNull(theme); + + EnsureNestedObjects(theme); + + return theme; + } + /// Removes JSON object properties whose value is an empty object {}, depth-first, so parents collapse when all children were empty. private static void PruneEmptyObjectNodes(JsonNode? node) { @@ -61,36 +81,75 @@ private static void PruneEmptyObjectNodes(JsonNode? node) } } - /// Replaces null reference-type properties (except ) so the tree matches a new BitTheme() graph after sparse JSON. -#pragma warning disable IL2072 // Activator.CreateInstance - BitTheme-related POCOs only, all have public parameterless constructors. -#pragma warning disable IL2075 // GetType().GetProperties - only instances from BitTheme deserialization. - private static void EnsureNestedObjects(object obj, HashSet? visited = null) + /// + /// Replaces branch objects on a so the + /// graph matches a freshly-constructed new BitTheme(). Callers that walk the graph (the + /// mapper, merge, derivation helpers) rely on every branch being non-null. + /// + /// + /// + /// This is a safety net for externally-authored JSON. The sparse format produced by + /// never emits a branch as explicit null (null leaves are omitted + /// and empty objects are pruned), and leaves absent + /// properties at their constructor-initialized new() values - so a framework round-trip + /// already yields a fully-populated graph. The case this guards is hand-written or third-party + /// JSON that contains an explicit "color": null (or similar), which the serializer would + /// otherwise honor by setting the branch to . + /// + /// + /// Previously this method walked the type via reflection. Reflection breaks under trimming / + /// AOT (IL2070/IL2075/IL3050) unless the entire graph is preserved by + /// [DynamicallyAccessedMembers], which is hard to keep correct as the model evolves. + /// Walking the graph explicitly is verbose but trim-safe and removes the reflection + /// suppression pragmas that previously hid genuine warnings. + /// + /// + private static void EnsureNestedObjects(BitTheme theme) { - visited ??= new HashSet(ReferenceEqualityComparer.Instance); - if (!visited.Add(obj)) - return; - - foreach (var prop in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - if (!prop.CanRead || !prop.CanWrite || prop.GetIndexParameters().Length > 0) - continue; + // Top-level branches. + theme.Color ??= new BitThemeColors(); + theme.BoxShadow ??= new BitThemeBoxShadows(); + theme.Spacing ??= new BitThemeSpacings(); + theme.ZIndex ??= new BitThemeZIndices(); + theme.Shape ??= new BitThemeShapes(); + theme.Typography ??= new BitThemeTypography(); + theme.Motion ??= new BitThemeMotion(); + theme.Layout ??= new BitThemeLayout(); - var pt = prop.PropertyType; - if (pt == typeof(string) || pt.IsValueType) - continue; + // Color branch. + var color = theme.Color; + color.Primary ??= new BitThemeColorVariants(); + color.Secondary ??= new BitThemeColorVariants(); + color.Tertiary ??= new BitThemeColorVariants(); + color.Info ??= new BitThemeColorVariants(); + color.Success ??= new BitThemeColorVariants(); + color.Warning ??= new BitThemeColorVariants(); + color.SevereWarning ??= new BitThemeColorVariants(); + color.Error ??= new BitThemeColorVariants(); + color.Foreground ??= new BitThemeGeneralColorVariants(); + color.Background ??= new BitThemeBackgroundColorVariants(); + color.Border ??= new BitThemeGeneralColorVariants(); + color.Neutral ??= new BitThemeNeutralColorVariants(); - var val = prop.GetValue(obj); - if (val is null) - { - val = Activator.CreateInstance(pt); - if (val is null) - continue; - prop.SetValue(obj, val); - } + // Typography branch. + var typography = theme.Typography; + typography.H1 ??= new BitThemeTypographyVariants(); + typography.H2 ??= new BitThemeTypographyVariants(); + typography.H3 ??= new BitThemeTypographyVariants(); + typography.H4 ??= new BitThemeTypographyVariants(); + typography.H5 ??= new BitThemeTypographyVariants(); + typography.H6 ??= new BitThemeTypographyVariants(); + typography.Subtitle1 ??= new BitThemeTypographyVariants(); + typography.Subtitle2 ??= new BitThemeTypographyVariants(); + typography.Body1 ??= new BitThemeTypographyVariants(); + typography.Body2 ??= new BitThemeTypographyVariants(); + typography.Button ??= new BitThemeLabelTypographyVariants(); + typography.Caption1 ??= new BitThemeTypographyVariants(); + typography.Caption2 ??= new BitThemeTypographyVariants(); + typography.Overline ??= new BitThemeLabelTypographyVariants(); + typography.Inherit ??= new BitThemeInheritTypographyVariants(); - EnsureNestedObjects(val, visited); - } + // Layout branch. + theme.Layout.Breakpoints ??= new BitThemeBreakpoints(); } -#pragma warning restore IL2075 -#pragma warning restore IL2072 } diff --git a/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSsr.cs b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSsr.cs index e50663f56c..d2d510810f 100644 --- a/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSsr.cs +++ b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSsr.cs @@ -5,20 +5,168 @@ namespace Bit.BlazorUI; /// Order matches bit-theme.ts init: base from attributes, then bit-theme-system (prefers-color-scheme), then bit-theme-persist (localStorage), then resolve stored system. /// Emit at the start of <head> (before stylesheets) so the correct bit-theme attribute is set before first paint. /// +/// +/// Attribute and storage key names come from so the inline script and the runtime BitBlazorUI.Theme client stay in sync. +/// public static class BitThemeSsr { /// - /// Inline script only (no script tag). Wrap in a script element in your host page or layout. + /// Inline script body without a wrapping <script> tag. Concatenate into your own + /// <script> element when you need full control over attributes (CSP nonce, + /// type, defer, etc.). /// - public const string InlineHeadScriptBody = - "(function(){var r=document.documentElement,k='bit-current-theme',lt=r.getAttribute('bit-theme-light')||'light'," + - "dk=r.getAttribute('bit-theme-dark')||'dark',m=window.matchMedia&&matchMedia('(prefers-color-scheme:dark)').matches," + - "base=r.getAttribute('bit-theme')||r.getAttribute('bit-theme-default')||'light';" + - "if(r.hasAttribute('bit-theme-system')){base=m?dk:lt;}" + - "var cur=base;if(r.hasAttribute('bit-theme-persist')){try{cur=localStorage.getItem(k)||base;}catch(e){}}" + - "if(cur==='system'){cur=m?dk:lt;}" + - "r.setAttribute('bit-theme',cur);})();"; - - /// Full script element markup for convenience. - public static string InlineHeadScript => $""; + public static readonly string InlineHeadScriptBody = BuildInlineScriptBody(); + + /// + /// Full <script> markup ready to drop into <head>. Equivalent to + /// with a nonce. + /// + public static string InlineHeadScript => BuildInlineHeadScript(nonce: null); + + /// + /// Builds the inline first-paint script and wraps it in a <script> element. + /// Pass to satisfy a script-src 'nonce-…' Content-Security-Policy. + /// + /// Optional CSP nonce. When supplied, the value is HTML-attribute-encoded and emitted as nonce="…". + public static string BuildInlineHeadScript(string? nonce) + { + if (string.IsNullOrWhiteSpace(nonce)) + { + return $""; + } + + // HTML-attribute-encode the nonce so a tampered value cannot break out of the attribute. + // CSP nonces are base64url in practice, but defense-in-depth is cheap. + var safeNonce = HtmlEncodeAttribute(nonce); + return $""; + } + + /// + /// Builds the bit-theme* attribute string to render on the root <html> + /// element from a persisted preference (typically read from the + /// cookie) so server-rendered markup paints + /// the correct theme before the client script runs. + /// + /// + /// The stored abstract theme key (for example dark, fluent-light, or + /// system). Usually the cookie value; may be /blank when no + /// preference has been stored yet. + /// + /// + /// Optional theme to use when is missing, or as the + /// no-JS base when the OS is being followed. + /// + /// + /// An attribute fragment such as bit-theme="dark" or + /// bit-theme-system bit-theme-default="light", ready to interpolate into the root + /// <html> tag. + /// + /// + /// + /// A concrete preference is emitted as bit-theme="…" so first paint is correct with no + /// flash. The special system value (and a missing preference with no default) emit the + /// bit-theme-system marker and defer the light/dark resolution to the client inline + /// script - the server cannot read prefers-color-scheme. A missing preference with a + /// concrete paints that default directly. + /// + /// + /// Values are normalized (trim + lower-case, matching ) and validated + /// against a strict [a-z0-9-] theme-name pattern; anything else is ignored and treated as + /// missing, so a tampered cookie cannot inject markup into the document. + /// + /// + /// This is the server half of cookie-based persistence: the app is responsible for writing the + /// cookie (client- or server-side) under . + /// + /// + public static string BuildRootThemeAttributes(string? persistedPreference, string? defaultTheme = null) + { + var preference = NormalizeThemeToken(persistedPreference); + var fallback = NormalizeThemeToken(defaultTheme); + + // Explicit "system" → follow the OS; the inline head script resolves light/dark before + // paint. Carry a concrete default through for no-JS clients. + if (preference == BitThemePresets.System) + { + return fallback is null || fallback == BitThemePresets.System + ? BitThemeAttributeNames.ThemeSystem + : $"{BitThemeAttributeNames.ThemeSystem} {BitThemeAttributeNames.ThemeDefault}=\"{fallback}\""; + } + + // Concrete persisted preference → paint it directly (no flash). + if (preference is not null) + { + return $"{BitThemeAttributeNames.Theme}=\"{preference}\""; + } + + // No stored preference: use the configured default theme, or follow the OS if none. + if (fallback is not null && fallback != BitThemePresets.System) + { + return $"{BitThemeAttributeNames.Theme}=\"{fallback}\""; + } + + return BitThemeAttributeNames.ThemeSystem; + } + + /// + /// Normalizes a theme key (trim + lower-case invariant) and validates it as a safe + /// [a-z0-9-] token. Returns for blank or invalid input so callers + /// treat it as "no preference" - this also prevents a tampered cookie value from being emitted + /// into the document. + /// + private static string? NormalizeThemeToken(string? value) => BitThemeName.NormalizeToken(value, out _); + + private static string BuildInlineScriptBody() + { + // Centralizing names through BitThemeAttributeNames means the inline script picks up renames + // automatically - the previous version hard-coded each attribute literal twice (here and in + // bit-theme.ts), which was a maintenance hazard. + var theme = BitThemeAttributeNames.Theme; + var themeDefault = BitThemeAttributeNames.ThemeDefault; + var themeSystem = BitThemeAttributeNames.ThemeSystem; + var themePersist = BitThemeAttributeNames.ThemePersist; + var themePersistCookie = BitThemeAttributeNames.ThemePersistCookie; + var themeDark = BitThemeAttributeNames.ThemeDark; + var themeLight = BitThemeAttributeNames.ThemeLight; + var storageKey = BitThemeAttributeNames.ThemeStorageKey; + var cookieName = BitThemeCookie.PreferenceCookieName; + + // The inline script is intentionally compact; it runs on every first paint before stylesheets. + // Logic mirrors Theme.init in bit-theme.ts, including the localStorage→cookie persistence + // fallback so a cookie-only (SSR) persistence setup paints correctly with no flash. + return + "(function(){var r=document.documentElement," + + $"k='{storageKey}'," + + $"ck='{cookieName}'," + + $"lt=r.getAttribute('{themeLight}')||'light'," + + $"dk=r.getAttribute('{themeDark}')||'dark'," + + "m=window.matchMedia&&matchMedia('(prefers-color-scheme:dark)').matches," + + $"base=r.getAttribute('{theme}')||r.getAttribute('{themeDefault}')||'light';" + + $"if(r.hasAttribute('{themeSystem}')){{base=m?dk:lt;}}" + + "var cur=base,got=false;" + + $"if(r.hasAttribute('{themePersist}')){{try{{var s=localStorage.getItem(k);if(s){{cur=s;got=true;}}}}catch(e){{}}}}" + + $"if(!got&&r.hasAttribute('{themePersistCookie}')){{var cm=('; '+document.cookie).match('; '+ck+'=([^;]*)');if(cm){{try{{cur=decodeURIComponent(cm[1]);}}catch(e){{cur=cm[1];}}}}}}" + + "if(cur==='system'){cur=m?dk:lt;}" + + $"r.setAttribute('{theme}',cur);}})();"; + } + + private static string HtmlEncodeAttribute(string value) + { + // Minimal attribute-context encoding; we don't want a full WebUtility dep just for this and + // CSP nonces are constrained to base64url chars in practice. Encode the characters that can + // break out of a double-quoted attribute or alter parsing. + var builder = new System.Text.StringBuilder(value.Length); + foreach (var ch in value) + { + switch (ch) + { + case '&': builder.Append("&"); break; + case '"': builder.Append("""); break; + case '<': builder.Append("<"); break; + case '>': builder.Append(">"); break; + default: builder.Append(ch); break; + } + } + return builder.ToString(); + } } diff --git a/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeUtilities.cs b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeUtilities.cs index 66e6827ede..1320de5ec6 100644 --- a/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeUtilities.cs +++ b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeUtilities.cs @@ -5,13 +5,43 @@ /// public static class BitThemeUtilities { - /// Maps a theme to CSS custom property names and values for use with or inline styles. + /// + /// Maps a theme to CSS custom property names and values for use with or inline styles. + /// + /// + /// The result is recomputed on every call. is mutable, so caching by + /// instance would return stale values when callers mutate a theme between calls. The mapper + /// builds a ~280-entry dictionary of strings; for the typical call path through + /// this cost is dwarfed by the JS-interop + /// boundary. If you have a hot, allocation-sensitive path, hold the result yourself and pass + /// the same reference until you intentionally rebuild it. + /// + /// Token values that contain characters capable of escaping a single CSS declaration (for + /// example ;, {, }, <, >, or comment markers) are + /// dropped rather than emitted. Theme values can originate from untrusted sources (see + /// ), and the output is concatenated into an inline + /// style attribute by ; dropping injection-prone values + /// prevents a malicious token from adding extra CSS. A dropped token falls back to the + /// stylesheet default. + /// + /// + /// The passed is not mutated: a hand-constructed sparse theme (e.g. + /// new BitTheme { Color = null }) is normalized onto an internal copy before mapping, so + /// any deliberate branches on the original instance are preserved. + /// + /// public static IReadOnlyDictionary ToCssVariables(BitTheme? bitTheme) { return BitThemeMapper.MapToCssVariables(bitTheme ?? new BitTheme()); } /// Merges two themes: wins; missing values fall back to . + /// + /// Returns a fresh ; the inputs are neither used as the result nor mutated. + /// A hand-constructed sparse input (with branch objects) is normalized + /// onto internal copies before merging, so deliberate null branches on the passed instances are + /// preserved. + /// public static BitTheme Merge(BitTheme? overrides, BitTheme? baseline) { return BitThemeMapper.Merge(overrides ?? new BitTheme(), baseline ?? new BitTheme()); diff --git a/src/BlazorUI/Bit.BlazorUI/Utils/Theme/bit-theme.ts b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/bit-theme.ts deleted file mode 100644 index 2040d035a2..0000000000 --- a/src/BlazorUI/Bit.BlazorUI/Utils/Theme/bit-theme.ts +++ /dev/null @@ -1,134 +0,0 @@ -type onThemeChangeType = (newThemeName: string, oldThemeName: string) => void; - -interface BitThemeOptions { - system?: boolean; - persist?: boolean; - theme?: string | null; - default?: string | null; - darkTheme?: string | null; - lightTheme?: string | null; - onChange?: onThemeChangeType; -} - -class BitTheme { - private static SYSTEM_THEME = 'system'; - private static THEME_ATTRIBUTE = 'bit-theme'; - private static THEME_STORAGE_KEY = 'bit-current-theme'; - - - private static _persist = false; - private static _darkTheme: string = 'dark'; - private static _lightTheme: string = 'light'; - private static _initOptions: BitThemeOptions = {}; - private static _currentTheme = BitTheme._lightTheme; - private static _onThemeChange: onThemeChangeType = () => { }; - - - public static init(options: BitThemeOptions) { - Object.assign(BitTheme._initOptions, options); - - if (BitTheme._initOptions.onChange) { - BitTheme._onThemeChange = BitTheme._initOptions.onChange; - } - - if (BitTheme._initOptions.darkTheme) { - BitTheme._darkTheme = BitTheme._initOptions.darkTheme; - } - - if (BitTheme._initOptions.lightTheme) { - BitTheme._lightTheme = BitTheme._initOptions.lightTheme; - } - - let theme = BitTheme._initOptions.theme || BitTheme._initOptions.default || BitTheme._lightTheme; - - if (BitTheme._initOptions.system) { - theme = BitTheme.isSystemDark() ? BitTheme._darkTheme : BitTheme._lightTheme; - } - - if (BitTheme._initOptions.persist) { - BitTheme._persist = true; - theme = BitTheme.getPersisted() || theme; - } - - BitTheme.set(theme); - } - - public static onChange(fn: onThemeChangeType) { - BitTheme._onThemeChange = fn; - } - - public static get() { - BitTheme._currentTheme = document.documentElement.getAttribute(BitTheme.THEME_ATTRIBUTE) || ''; - - if (BitTheme._persist) { - var theme = BitTheme.getActualTheme(BitTheme.getPersisted()); - BitTheme._currentTheme = theme || BitTheme._currentTheme; - } - - return BitTheme._currentTheme; - } - - public static set(themeName: string) { - BitTheme._currentTheme = BitTheme.getActualTheme(themeName)!; - - if (BitTheme._persist) { - localStorage.setItem(BitTheme.THEME_STORAGE_KEY, themeName); - } - - const oldTheme = document.documentElement.getAttribute(BitTheme.THEME_ATTRIBUTE) || ''; - - document.documentElement.setAttribute(BitTheme.THEME_ATTRIBUTE, BitTheme._currentTheme); - - BitTheme._onThemeChange?.(BitTheme._currentTheme, oldTheme); - - return BitTheme._currentTheme; - } - - public static toggleDarkLight() { - BitTheme._currentTheme = BitTheme._currentTheme === BitTheme._lightTheme - ? BitTheme._darkTheme - : BitTheme._lightTheme; - - BitTheme.set(BitTheme._currentTheme); - - return BitTheme._currentTheme; - } - - public static applyBitTheme(theme: any, element?: HTMLElement) { - const el = element || document.body; - Object.keys(theme).forEach(key => el.style.setProperty(key, theme[key])); - } - - public static isSystemDark() { - return matchMedia('(prefers-color-scheme: dark)').matches; - } - - public static getPersisted() { - if (!BitTheme._persist) return null; - - return localStorage.getItem(BitTheme.THEME_STORAGE_KEY); - } - - private static getActualTheme(theme: string | null) { - if (theme === BitTheme.SYSTEM_THEME) { - return BitTheme.isSystemDark() ? BitTheme._darkTheme : BitTheme._lightTheme; - } - - return theme; - } -} - -(function () { - const options = { - system: document.documentElement.hasAttribute('bit-theme-system'), - persist: document.documentElement.hasAttribute('bit-theme-persist'), - theme: document.documentElement.getAttribute('bit-theme'), - default: document.documentElement.getAttribute('bit-theme-default'), - darkTheme: document.documentElement.getAttribute('bit-theme-dark'), - lightTheme: document.documentElement.getAttribute('bit-theme-light'), - }; - - BitTheme.init(options); -}()); - -(window as any).BitTheme = BitTheme; diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj index d75075f0e7..7d74170cd9 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj @@ -39,6 +39,7 @@ + diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/DemoPage.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/DemoPage.razor.cs index 6d3ba6036e..5cda9ce8eb 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/DemoPage.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/DemoPage.razor.cs @@ -315,6 +315,6 @@ public partial class DemoPage private readonly List _notInheritedComponents = [ - "CascadingValueProvider", "Chart", "DataGrid", "QuickGrid", "ModalService", "Params", "ProModalService" + "CascadingValueProvider", "Chart", "ChartLegacy", "DataGrid", "DataGridLegacy", "ModalService", "Params", "ProModalService" ]; } diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ActionButton/BitActionButtonDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ActionButton/BitActionButtonDemo.razor index 8e18c95075..3a73b9effb 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ActionButton/BitActionButtonDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ActionButton/BitActionButtonDemo.razor @@ -564,6 +564,39 @@ +

+
Disabled:
+
+
+ Primary + Secondary + Tertiary + Info + Success + Warning + SevereWarning + Error +
+

+
+
+ PrimaryBackground + SecondaryBackground + TertiaryBackground +
+
+
+
+ PrimaryForeground + SecondaryForeground + TertiaryForeground +
+
+
+ PrimaryBorder + SecondaryBorder + TertiaryBorder +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ActionButton/BitActionButtonDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ActionButton/BitActionButtonDemo.razor.samples.cs index 38a512d841..f4b3dc64d2 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ActionButton/BitActionButtonDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ActionButton/BitActionButtonDemo.razor.samples.cs @@ -339,7 +339,31 @@ More info TertiaryBorder -"; + + + +Primary +Secondary +Tertiary +Info +Success +Warning +SevereWarning +Error + +
+ PrimaryBackground + SecondaryBackground + TertiaryBackground +
+ +PrimaryForeground +SecondaryForeground +TertiaryForeground + +PrimaryBorder +SecondaryBorder +TertiaryBorder"; private readonly string example13RazorCode = @" diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/Button/BitButtonDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/Button/BitButtonDemo.razor index 12de1360a4..4b9aa1ed38 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/Button/BitButtonDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/Button/BitButtonDemo.razor @@ -662,6 +662,116 @@ TertiaryBorder +

+
Disabled:
+
+
+ Primary + Primary + Primary +
+
+
+ Secondary + Secondary + Secondary +
+
+
+ Tertiary + Tertiary + Tertiary +
+
+
+ Info + Info + Info +
+
+
+ Success + Success + Success +
+
+
+ Warning + Warning + Warning +
+
+
+ SevereWarning + SevereWarning + SevereWarning +
+
+
+ Error + Error + Error +
+

+
+
+ PrimaryBackground + PrimaryBackground + PrimaryBackground +
+
+
+ SecondaryBackground + SecondaryBackground + SecondaryBackground +
+
+
+ TertiaryBackground + TertiaryBackground + TertiaryBackground +
+
+
+
+
+ PrimaryForeground + PrimaryForeground + PrimaryForeground +
+
+
+ SecondaryForeground + SecondaryForeground + SecondaryForeground +
+
+
+ TertiaryForeground + TertiaryForeground + TertiaryForeground +
+
+
+
+
+ PrimaryBorder + PrimaryBorder + PrimaryBorder +
+
+
+ SecondaryBorder + SecondaryBorder + SecondaryBorder +
+
+
+ TertiaryBorder + TertiaryBorder + TertiaryBorder +
+
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/Button/BitButtonDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/Button/BitButtonDemo.razor.samples.cs index f902eab7c3..31b2e36cb5 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/Button/BitButtonDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/Button/BitButtonDemo.razor.samples.cs @@ -579,17 +579,19 @@ private async Task LoadingTemplateClick() Error -PrimaryBackground -PrimaryBackground -PrimaryBackground - -SecondaryBackground -SecondaryBackground -SecondaryBackground - -TertiaryBackground -TertiaryBackground -TertiaryBackground +
+ PrimaryBackground + PrimaryBackground + PrimaryBackground + + SecondaryBackground + SecondaryBackground + SecondaryBackground + + TertiaryBackground + TertiaryBackground + TertiaryBackground +
PrimaryForeground @@ -615,7 +617,80 @@ private async Task LoadingTemplateClick() TertiaryBorder TertiaryBorder -TertiaryBorder"; +TertiaryBorder + + +
Disabled:
+ +Primary +Primary +Primary + +Secondary +Secondary +Secondary + +Tertiary +Tertiary +Tertiary + +Info +Info +Info + +Success +Success +Success + +Warning +Warning +Warning + +SevereWarning +SevereWarning +SevereWarning + +Error +Error +Error + +
+ PrimaryBackground + PrimaryBackground + PrimaryBackground + + SecondaryBackground + SecondaryBackground + SecondaryBackground + + TertiaryBackground + TertiaryBackground + TertiaryBackground +
+ +PrimaryForeground +PrimaryForeground +PrimaryForeground + +SecondaryForeground +SecondaryForeground +SecondaryForeground + +TertiaryForeground +TertiaryForeground +TertiaryForeground + +PrimaryBorder +PrimaryBorder +PrimaryBorder + +SecondaryBorder +SecondaryBorder +SecondaryBorder + +TertiaryBorder +TertiaryBorder +TertiaryBorder"; private readonly string example20RazorCode = @" diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.scss b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.scss index 3be6c0b610..988d2a6984 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.scss +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.scss @@ -3,6 +3,11 @@ gap: 0.5rem; display: flex; flex-flow: column; + + &.row { + flex-flow: row; + flex-wrap: wrap; + } } // Constrains the group so the overflow examples actually have something to overflow, @@ -49,4 +54,4 @@ border-color: aliceblue; background-color: crimson; } -} +} \ No newline at end of file diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor index c739d88c6f..fbc043beb6 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor @@ -556,126 +556,249 @@ border colors.

-
-
Primary
+
Primary
+


-
-
Secondary
+
Secondary
+


-
-
Tertiary
+
Tertiary
+


-
-
Info
+
Info
+


-
-
Success
+
Success
+


-
-
Warning
+
Warning
+


-
-
SevereWarning
+
SevereWarning
+


-
-
Error
+
Error
+


-
-
PrimaryBackground
+
PrimaryBackground
+


-
-
SecondaryBackground
+
SecondaryBackground
+


-
-
TertiaryBackground
+
TertiaryBackground
+


-
-
PrimaryForeground
+
PrimaryForeground
+


-
-
SecondaryForeground
+
SecondaryForeground
+


-
-
TertiaryForeground
+
TertiaryForeground
+


-
-
PrimaryBorder
+
PrimaryBorder
+


-
-
SecondaryBorder
+
SecondaryBorder
+


-
-
TertiaryBorder
+
TertiaryBorder
+
+

+
Disabled:
+
+
Primary
+
+ + + +
+

+
Secondary
+
+ + + +
+

+
Tertiary
+
+ + + +
+

+
Info
+
+ + + +
+

+
Success
+
+ + + +
+

+
Warning
+
+ + + +
+

+
SevereWarning
+
+ + + +
+

+
Error
+
+ + + +
+

+
+
PrimaryBackground
+
+ + + +
+

+
SecondaryBackground
+
+ + + +
+

+
TertiaryBackground
+
+ + + +
+
+

+
PrimaryForeground
+
+ + + +
+

+
SecondaryForeground
+
+ + + +
+

+
TertiaryForeground
+
+ + + +
+

+
PrimaryBorder
+
+ + + +
+

+
SecondaryBorder
+
+ + + +
+

+
TertiaryBorder
+
+ + + +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.samples.cs index 8eb0bd0f7f..fb65dcbda5 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.samples.cs @@ -734,7 +734,83 @@ protected override void OnInitialized() -"; + + + +
Disabled:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +"; private readonly string example21CsharpCode = @" private BitButtonGroupNameSelectors nameSelector = new() { Text = { Selector = i => i.Name } }; diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor index e7a1ffd408..bd47d0cc96 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor @@ -505,126 +505,249 @@ border colors.


-
-
Primary
+
Primary
+


-
-
Secondary
+
Secondary
+


-
-
Tertiary
+
Tertiary
+


-
-
Info
+
Info
+


-
-
Success
+
Success
+


-
-
Warning
+
Warning
+


-
-
SevereWarning
+
SevereWarning
+


-
-
Error
+
Error
+


-
-
PrimaryBackground
+
PrimaryBackground
+


-
-
SecondaryBackground
+
SecondaryBackground
+


-
-
TertiaryBackground
+
TertiaryBackground
+


-
-
PrimaryForeground
+
PrimaryForeground
+


-
-
SecondaryForeground
+
SecondaryForeground
+


-
-
TertiaryForeground
+
TertiaryForeground
+


-
-
PrimaryBorder
+
PrimaryBorder
+


-
-
SecondaryBorder
+
SecondaryBorder
+


-
-
TertiaryBorder
+
TertiaryBorder
+
+



+
Disabled:
+
+
Primary
+
+ + + +
+

+
Secondary
+
+ + + +
+

+
Tertiary
+
+ + + +
+

+
Info
+
+ + + +
+

+
Success
+
+ + + +
+

+
Warning
+
+ + + +
+

+
SevereWarning
+
+ + + +
+

+
Error
+
+ + + +
+

+
+
PrimaryBackground
+
+ + + +
+

+
SecondaryBackground
+
+ + + +
+

+
TertiaryBackground
+
+ + + +
+
+

+
PrimaryForeground
+
+ + + +
+

+
SecondaryForeground
+
+ + + +
+

+
TertiaryForeground
+
+ + + +
+

+
PrimaryBorder
+
+ + + +
+

+
SecondaryBorder
+
+ + + +
+

+
TertiaryBorder
+
+ + + +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor.samples.cs index 7e8843afc2..86f1142ef8 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor.samples.cs @@ -535,7 +535,83 @@ protected override void OnInitialized() -"; + + + +
Disabled:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +"; private readonly string example21CsharpCode = @" private List basicItems = [ diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor index 0fa7c2b3ff..c1b408f870 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor @@ -949,6 +949,231 @@
+

+
Disabled:
+
+
+
Primary
+ + + + + + + + + +
+

+
+
Secondary
+ + + + + + + + + +
+

+
+
Tertiary
+ + + + + + + + + +
+

+
+
Info
+ + + + + + + + + +
+

+
+
Success
+ + + + + + + + + +
+

+
+
Warning
+ + + + + + + + + +
+

+
+
SevereWarning
+ + + + + + + + + +
+

+
+
Error
+ + + + + + + + + +
+

+
+
+
PrimaryBackground
+ + + + + + + + + +
+

+
+
SecondaryBackground
+ + + + + + + + + +
+

+
+
TertiaryBackground
+ + + + + + + + + +
+
+

+
+
PrimaryForeground
+ + + + + + + + + +
+

+
+
SecondaryForeground
+ + + + + + + + + +
+

+
+
TertiaryForeground
+ + + + + + + + + +
+

+
+
PrimaryBorder
+ + + + + + + + + +
+

+
+
SecondaryBorder
+ + + + + + + + + +
+

+
+
TertiaryBorder
+ + + + + + + + + +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor.samples.cs index d99ca70b95..b14f923297 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor.samples.cs @@ -627,6 +627,184 @@ private async Task HandleLoadingClick(string key) + + + +
Disabled:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "; private readonly string example22RazorCode = @" diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonCustomDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonCustomDemo.razor index 2e7d23a456..e0175fc902 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonCustomDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonCustomDemo.razor @@ -491,6 +491,214 @@
+

+
Disabled:
+
+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonCustomDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonCustomDemo.razor.samples.cs index ea81031db2..eb6b9529b9 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonCustomDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonCustomDemo.razor.samples.cs @@ -749,7 +749,164 @@ public class Operation -"; + + + +
Disabled:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"; private readonly string example18CsharpCode = @" public class Operation diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonItemDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonItemDemo.razor index a47c103bc1..bdaeab96c2 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonItemDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonItemDemo.razor @@ -489,6 +489,214 @@
+

+
Disabled:
+
+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
+

+
+ + + +
+
+
+ + + +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonItemDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonItemDemo.razor.samples.cs index 63f5151be5..fad6b16b50 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonItemDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonItemDemo.razor.samples.cs @@ -1,4 +1,4 @@ -namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Buttons.MenuButton; +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Buttons.MenuButton; public partial class _BitMenuButtonItemDemo { @@ -517,7 +517,162 @@ private async Task HandleOnLoadingClick() -"; + + + +
Disabled:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"; private readonly string example18CsharpCode = @" private List basicItems = diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonOptionDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonOptionDemo.razor index 0527080f28..88efc0635f 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonOptionDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonOptionDemo.razor @@ -1221,6 +1221,622 @@
+

+
Disabled:
+
+
+ + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
+

+
+ + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
+

+
+ + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
+

+
+ + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
+

+
+ + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
+

+
+ + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
+

+
+ + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
+

+
+ + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
+

+
+
+ + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
+

+
+ + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
+

+
+ + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
+
+

+
+ + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
+

+
+ + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
+

+
+ + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
+

+
+ + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
+

+
+ + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
+

+
+ + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonOptionDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonOptionDemo.razor.cs index 5f7862d7ae..0799e5531e 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonOptionDemo.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/MenuButton/_BitMenuButtonOptionDemo.razor.cs @@ -1,4 +1,4 @@ -namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Buttons.MenuButton; +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Buttons.MenuButton; public partial class _BitMenuButtonOptionDemo { @@ -1064,6 +1064,569 @@ private async Task HandleOnLoadingClick() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "; private readonly string example19RazorCode = @" diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ToggleButton/BitToggleButtonDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ToggleButton/BitToggleButtonDemo.razor index 6f83e40842..b8ae560b63 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ToggleButton/BitToggleButtonDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ToggleButton/BitToggleButtonDemo.razor @@ -539,6 +539,112 @@ TertiaryBorder TertiaryBorder
+

+
Disabled:
+
+
+ Primary + Primary + Primary +
+
+
+ Secondary + Secondary + Secondary +
+
+
+ Tertiary + Tertiary + Tertiary +
+
+
+ Info + Info + Info +
+
+
+ Success + Success + Success +
+
+
+ Warning + Warning + Warning +
+
+
+ SevereWarning + SevereWarning + SevereWarning +
+
+
+ Error + Error + Error +
+

+
+
+ PrimaryBackground + PrimaryBackground + PrimaryBackground +
+
+
+ SecondaryBackground + SecondaryBackground + SecondaryBackground +
+
+
+ TertiaryBackground + TertiaryBackground + TertiaryBackground +
+
+
+
+ PrimaryForeground + PrimaryForeground + PrimaryForeground +
+
+
+ SecondaryForeground + SecondaryForeground + SecondaryForeground +
+
+
+ TertiaryForeground + TertiaryForeground + TertiaryForeground +
+
+
+ PrimaryBorder + PrimaryBorder + PrimaryBorder +
+
+
+ SecondaryBorder + SecondaryBorder + SecondaryBorder +
+
+
+ TertiaryBorder + TertiaryBorder + TertiaryBorder +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ToggleButton/BitToggleButtonDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ToggleButton/BitToggleButtonDemo.razor.samples.cs index 30dfeac05e..fb2378365f 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ToggleButton/BitToggleButtonDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ToggleButton/BitToggleButtonDemo.razor.samples.cs @@ -302,7 +302,78 @@ private async Task HandleAutoLoadingChange() TertiaryBorder TertiaryBorder -TertiaryBorder"; +TertiaryBorder + + +Primary +Primary +Primary + +Secondary +Secondary +Secondary + +Tertiary +Tertiary +Tertiary + +Info +Info +Info + +Success +Success +Success + +Warning +Warning +Warning + +SevereWarning +SevereWarning +SevereWarning + +Error +Error +Error + +
+ PrimaryBackground + PrimaryBackground + PrimaryBackground + + SecondaryBackground + SecondaryBackground + SecondaryBackground + + TertiaryBackground + TertiaryBackground + TertiaryBackground +
+ +PrimaryForeground +PrimaryForeground +PrimaryForeground + +SecondaryForeground +SecondaryForeground +SecondaryForeground + +TertiaryForeground +TertiaryForeground +TertiaryForeground + +PrimaryBorder +PrimaryBorder +PrimaryBorder + +SecondaryBorder +SecondaryBorder +SecondaryBorder + +TertiaryBorder +TertiaryBorder +TertiaryBorder"; private readonly string example15RazorCode = @" diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor deleted file mode 100644 index 8e4b959fed..0000000000 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor +++ /dev/null @@ -1,204 +0,0 @@ -@page "/components/quickgrid" -@page "/components/quick-grid" -@inherits AppComponentBase -@using Bit.BlazorUI.Demo.Shared.Dtos.QuickGridDemo - - - - - - - To use this component, you need to install the - - - - NuGet package, as described in the Optional steps of the - Getting started page. - - - - - - - - - - - - - - - - - - - -
-
- - - - - - - - @(context.Code) - - - - - - - - - -
- - @(state.CurrentPageIndex + 1) / @(state.LastPageIndex + 1) - -
-
- - -
- - - - - - - - -
-
- -
-
- - -
- - - - - -
-
- -
-
- - -
- - - - - - - - - Loading items... - - - - -
- - @(state.CurrentPageIndex + 1) / @(state.LastPageIndex + 1) - -
- - -
- The BitQuickGrid does not have the Responsive feature built-in, - but you can achieve some level of responsiveness like what we did in this sample: -
- -
- -
- - - - - - - - -
-
- - -
See the RowTemplate parameter in action:
- -
- -
- - - - - - - - - - - - - @args.OriginalRow - @if (expandedRowTemplateCodes.Contains(args.RowItem.Code)) - { - - -
- Additional data: - Code: [@args.RowItem.Code] - - Gold: [@args.RowItem.Medals.Gold], - Silver: [@args.RowItem.Medals.Silver], - Bronze: [@args.RowItem.Medals.Bronze] - (Total: @args.RowItem.Medals.Total) -
- - - } -
-
- -
-
- -
-
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs deleted file mode 100644 index a12af8f448..0000000000 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.cs +++ /dev/null @@ -1,711 +0,0 @@ -using Bit.BlazorUI.Demo.Client.Core.Components; -using Bit.BlazorUI.Demo.Shared.Dtos.QuickGridDemo; - -namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Extras.QuickGrid; - -public partial class BitQuickGridDemo : AppComponentBase -{ - private readonly List componentParameters = - [ - new() - { - Name = "ChildContent", - Type = "RenderFragment?", - DefaultValue = "null", - Description = @"Defines the child components of this instance. - For example, you may define columns by adding components derived from the BitQuickGridColumnBase.", - }, - new() - { - Name = "Class", - Type = "string?", - DefaultValue = "null", - Description = "An optional CSS class name. If given, this will be included in the class attribute of the rendered table.", - }, - new() - { - Name = "Columns", - Type = "RenderFragment?", - DefaultValue = "null", - Description = "Alias of the ChildContent parameter.", - }, - new() - { - Name = "ItemKey", - Type = "Func", - DefaultValue = "x => x!", - Description = @"Optionally defines a value for @key on each rendered row. Typically this should be used to specify a - unique identifier, such as a primary key value, for each data item. - This allows the grid to preserve the association between row elements and data items based on their - unique identifiers, even when the TGridItem instances are replaced by new copies (for example, after a new query against the underlying data store). - If not set, the @key will be the TGridItem instance itself.", - }, - new() - { - Name = "Items", - Type = "IQueryable?", - DefaultValue = "null", - Description = @"A queryable source of data for the grid. - This could be in-memory data converted to queryable using the - System.Linq.Queryable.AsQueryable(System.Collections.IEnumerable) extension method, - or an EntityFramework DataSet or an IQueryable derived from it. - You should supply either Items or ItemsProvider, but not both.", - }, - new() - { - Name = "ItemSize", - Type = "float", - DefaultValue = "50", - Description = @"This is applicable only when using Virtualize. It defines an expected height in pixels for - each row, allowing the virtualization mechanism to fetch the correct number of items to match the display - size and to ensure accurate scrolling.", - }, - new() - { - Name = "ItemsProvider", - Type = "BitQuickGridItemsProvider?", - DefaultValue = "null", - Description = @"A callback that supplies data for the grid. - You should supply either Items or ItemsProvider, but not both.", - }, - new() - { - Name = "LoadingTemplate", - Type = "RenderFragment?", - DefaultValue = "null", - Description = "The custom template to render while loading the new items.", - }, - new() - { - Name = "Pagination", - Type = "BitQuickGridPaginationState?", - DefaultValue = "null", - Description = @"Optionally links this BitQuickGrid instance with a BitQuickGridPaginationState model, - causing the grid to fetch and render only the current page of data. - This is normally used in conjunction with a Paginator component or some other UI logic - that displays and updates the supplied BitQuickGridPaginationState instance.", - LinkType = LinkType.Link, - Href = "#pagination-state", - }, - new() - { - Name = "ResizableColumns", - Type = "bool", - DefaultValue = "false", - Description = @"If true, renders draggable handles around the column headers, allowing the user to resize the columns - manually. Size changes are not persisted.", - }, - new() - { - Name = "RowClass", - Type = "string?", - DefaultValue = "null", - Description = @"The CSS class of all rows of the BitQuickGrid.", - }, - new() - { - Name = "RowClassSelector", - Type = "Func?", - DefaultValue = "null", - Description = @"The function to generate the CSS class of each row of the BitQuickGrid.", - }, - new() - { - Name = "RowStyle", - Type = "string?", - DefaultValue = "null", - Description = @"The CSS style of all rows of the BitQuickGrid.", - }, - new() - { - Name = "RowStyleSelector", - Type = "Func?", - DefaultValue = "null", - Description = @"The function to generate the CSS style of each row of the BitQuickGrid.", - }, - new() - { - Name = "RowTemplate", - Type = "RenderFragment>?", - DefaultValue = "null", - Description = @"Optional template to customize row rendering. Receives BitQuickGridRowTemplateArgs with OriginalRow - set to the default row content; render it to include the original row, or omit to replace entirely.", - LinkType = LinkType.Link, - Href = "#row-template-args", - }, - new() - { - Name = "Theme", - Type = "string?", - DefaultValue = "default", - Description = @"A theme name, with default value ""default"". This affects which styling rules match the table.", - }, - new() - { - Name = "Virtualize", - Type = "bool", - DefaultValue = "false", - Description = @"If true, the grid will be rendered with virtualization. This is normally used in conjunction with - scrolling and causes the grid to fetch and render only the data around the current scroll viewport. - This can greatly improve the performance when scrolling through large data sets.", - } - ]; - - private readonly List componentSubClasses = - [ - new() - { - Id = "BitQuickGridColumnBase", - Title = "BitQuickGridColumnBase", - Description = "BitQuickGrid has two built-in column types, BitQuickGridPropertyColumn and BitQuickGridTemplateColumn. You can also create your own column types by subclassing the BitQuickGridColumnBase type, which all columns must derive from. It offers some common parameters.", - Parameters= - [ - new() - { - Name = "Title", - Type = "string?", - DefaultValue = "null", - Description = "Title text for the column. This is rendered automatically if HeaderTemplate is not used.", - }, - new() - { - Name = "Class", - Type = "string?", - DefaultValue = "null", - Description = "An optional CSS class name. If specified, this is included in the class attribute of table header and body cells for this column.", - }, - new() - { - Name = "Align", - Type = "BitQuickGridAlign?", - DefaultValue = "null", - Description = "If specified, controls the justification of table header and body cells for this column.", - }, - new() - { - Name = "HeaderTemplate", - Type = "RenderFragment>?", - DefaultValue = "null", - Description = @"An optional template for this column's header cell. If not specified, the default header template - includes the Title along with any applicable sort indicators and options buttons.", - }, - new() - { - Name = "ColumnOptions", - Type = "RenderFragment>?", - DefaultValue = "null", - Description = @"If specified, indicates that this column has this associated options UI. A button to display this - UI will be included in the header cell by default. - If HeaderTemplate is used, it is left up to that template to render any relevant - ""show options"" UI and invoke the grid's BitQuickGrid.ShowColumnOptions(BitQuickGridColumnBase).", - }, - new() - { - Name = "Sortable", - Type = "bool?", - DefaultValue = "null", - Description = @"Indicates whether the data should be sortable by this column. - The default value may vary according to the column type (for example, a BitQuickGridTemplateColumn - is sortable by default if any BitQuickGridTemplateColumn.SortBy parameter is specified).", - }, - new() - { - Name = "IsDefaultSort", - Type = "BitQuickGridSortDirection?", - DefaultValue = "null", - Description = "If specified and not null, indicates that this column represents the initial sort order for the grid. The supplied value controls the default sort direction.", - }, - new() - { - Name = "PlaceholderTemplate", - Type = "RenderFragment?", - DefaultValue = "null", - Description = "If specified, virtualized grids will use this template to render cells whose data has not yet been loaded.", - } - ], - - }, - new() - { - Id="BitQuickGridPropertyColumn", - Title = "BitQuickGridPropertyColumn", - Description = "It is for displaying a single value specified by the parameter Property. This column infers sorting rules automatically, and uses the property's name as its title if not otherwise set.", - Parameters= - [ - new() - { - Name = "Property", - Type = "Expression>", - Description = "Defines the value to be displayed in this column's cells.", - }, - new() - { - Name = "Format", - Type = "string?", - DefaultValue = "null", - Description = "Optionally specifies a format string for the value. Using this requires the TProp type to implement IFormattable.", - }, - ], - }, - new() - { - Id = "BitQuickGridTemplateColumn", - Title = "BitQuickGridTemplateColumn", - Description = @"It uses arbitrary Razor fragments to supply contents for its cells. It can't infer the column's title or sort order automatically. Also, it's possible to add arbitrary Blazor components to your table cells. Remember that rendering many components, or many event handlers, can impact the performance of your grid. One way to mitigate this issue is by paginating or virtualizing your grid.", - Parameters = - [ - new() - { - Name = "ChildContent", - Type = "RenderFragment", - Description = @"Specifies the content to be rendered for each row in the table.", - }, - new() - { - Name = "SortBy", - Type = "BitQuickGridSort?", - DefaultValue = "null", - Description = "Optionally specifies sorting rules for this column.", - }, - ], - }, - new() - { - Id = "BitQuickGridPaginator", - Title = "BitQuickGridPaginator", - Description = "A component that provides a user interface for pagination.", - Parameters= - [ - new() - { - Name = "GoToFirstButtonTitle", - Type = "string", - DefaultValue = "Go to first page", - Description = "The title of the go to first page button.", - }, - new() - { - Name = "GoToPrevButtonTitle", - Type = "string", - DefaultValue = "Go to previous page", - Description = "The title of the go to previous page button.", - }, - new() - { - Name = "GoToNextButtonTitle", - Type = "string", - DefaultValue = "Go to next page", - Description = "The title of the go to next page button.", - }, - new() - { - Name = "GoToLastButtonTitle", - Type = "string", - DefaultValue = "Go to last page", - Description = "The title of the go to last page button.", - }, - new() - { - Name = "SummaryFormat", - Type = "Func?", - DefaultValue = "null", - Description = "Optionally supplies a format for rendering the page count summary.", - LinkType = LinkType.Link, - Href = "#pagination-state" - }, - new() - { - Name = "SummaryTemplate", - Type = "RenderFragment?", - DefaultValue = "null", - Description = "Optionally supplies a template for rendering the page count summary.", - LinkType = LinkType.Link, - Href = "#pagination-state" - }, - new() - { - Name = "TextFormat", - Type = "Func?", - DefaultValue = "null", - Description = "The optional custom format for the main text of the paginator in the middle of it.", - LinkType = LinkType.Link, - Href = "#pagination-state" - }, - new() - { - Name = "TextTemplate", - Type = "RenderFragment?", - DefaultValue = "null", - Description = "The optional custom template for the main text of the paginator in the middle of it.", - LinkType = LinkType.Link, - Href = "#pagination-state" - }, - new() - { - Name = "Value", - Type = "BitQuickGridPaginationState", - DefaultValue = "", - Description = "Specifies the associated pagination state. This parameter is required.", - LinkType = LinkType.Link, - Href = "#pagination-state" - }, - ], - - }, - new() - { - Id = "pagination-state", - Title = "BitQuickGridPaginationState", - Description = "Holds state to represent the pagination of a BitQuickGrid. Tracks the current page index, the number of items per page, and the total item count so a paginator UI and the grid stay in sync.", - Parameters= - [ - new() - { - Name = "CurrentPageIndex", - Type = "int", - DefaultValue = "0", - Description = "Gets the current zero-based page index.", - }, - new() - { - Name = "ItemsPerPage", - Type = "int", - DefaultValue = "10", - Description = "Gets or sets the number of items on each page.", - }, - new() - { - Name = "LastPageIndex", - Type = "int?", - DefaultValue = "null", - Description = "Gets the zero-based index of the last page, if known. The value will be null until TotalItemCount is known.", - }, - new() - { - Name = "TotalItemCount", - Type = "int?", - DefaultValue = "null", - Description = "Gets the total number of items across all pages, if known. The value will be null until an associated BitQuickGrid assigns a value after loading data.", - }, - new() - { - Name = "TotalItemCountChanged", - Type = "EventHandler?", - DefaultValue = "null", - Description = "An event that is raised when the total item count has changed.", - }, - ], - - }, - new() - { - Id = "row-template-args", - Title = "BitQuickGridRowTemplateArgs", - Description = "Arguments passed to the RowTemplate render fragment.", - Parameters = - [ - new() - { - Name = "OriginalRow", - Type = "RenderFragment", - DefaultValue = "", - Description = "A render fragment that produces the original row markup (the default with all column cells). Render this to include the default row, or omit to replace entirely.", - }, - new() - { - Name = "RowIndex", - Type = "int", - DefaultValue = "0", - Description = "The 1-based row index used for accessibility (e.g. aria-rowindex).", - }, - new() - { - Name = "RowItem", - Type = "TGridItem", - DefaultValue = "", - Description = "The data item for this row.", - }, - ], - }, - ]; - - private readonly List componentSubEnums = - [ - new() - { - Id = "BitQuickGridAlign", - Name = "BitQuickGridAlign", - Description = "Describes alignment for a BitQuickGrid column.", - Items = - [ - new() - { - Name = "Left", - Value = "0", - Description = "Justifies the content against the start of the container." - }, - new() - { - Name = "Center", - Value = "1", - Description = "Justifies the content at the center of the container." - }, - new() - { - Name = "Right", - Value = "2", - Description = "Justifies the content at the end of the container." - }, - - ] - }, - ]; - - - - private static readonly CountryModel[] _countries = - [ - new CountryModel { Code = "AR", Name = "Argentina", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = "AM", Name = "Armenia", Medals = new MedalsModel { Gold = 0, Silver = 2, Bronze = 2 } }, - new CountryModel { Code = "AU", Name = "Australia", Medals = new MedalsModel { Gold = 17, Silver = 7, Bronze = 22 } }, - new CountryModel { Code = "AT", Name = "Austria", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 5 } }, - new CountryModel { Code = "AZ", Name = "Azerbaijan", Medals = new MedalsModel { Gold = 0, Silver = 3, Bronze = 4 } }, - new CountryModel { Code = "BS", Name = "Bahamas", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = "BH", Name = "Bahrain", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = "BY", Name = "Belarus", Medals = new MedalsModel { Gold = 1, Silver = 3, Bronze = 3 } }, - new CountryModel { Code = "BE", Name = "Belgium", Medals = new MedalsModel { Gold = 3, Silver = 1, Bronze = 3 } }, - new CountryModel { Code = "BM", Name = "Bermuda", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = "BW", Name = "Botswana", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = "BR", Name = "Brazil", Medals = new MedalsModel { Gold = 7, Silver = 6, Bronze = 8 } }, - new CountryModel { Code = "BF", Name = "Burkina Faso", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = "CA", Name = "Canada", Medals = new MedalsModel { Gold = 7, Silver = 6, Bronze = 11 } }, - new CountryModel { Code = "TW", Name = "Chinese Taipei", Medals = new MedalsModel { Gold = 2, Silver = 4, Bronze = 6 } }, - new CountryModel { Code = "CO", Name = "Colombia", Medals = new MedalsModel { Gold = 0, Silver = 4, Bronze = 1 } }, - new CountryModel { Code = "CI", Name = "Côte d'Ivoire", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = "HR", Name = "Croatia", Medals = new MedalsModel { Gold = 3, Silver = 3, Bronze = 2 } }, - new CountryModel { Code = "CU", Name = "Cuba", Medals = new MedalsModel { Gold = 7, Silver = 3, Bronze = 5 } }, - new CountryModel { Code = "CZ", Name = "Czech Republic", Medals = new MedalsModel { Gold = 4, Silver = 4, Bronze = 3 } }, - new CountryModel { Code = "DK", Name = "Denmark", Medals = new MedalsModel { Gold = 3, Silver = 4, Bronze = 4 } }, - new CountryModel { Code = "DO", Name = "Dominican Republic", Medals = new MedalsModel { Gold = 0, Silver = 3, Bronze = 2 } }, - new CountryModel { Code = "EC", Name = "Ecuador", Medals = new MedalsModel { Gold = 2, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = "EE", Name = "Estonia", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = "ET", Name = "Ethiopia", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = "FJ", Name = "Fiji", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = "FI", Name = "Finland", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = "FR", Name = "France", Medals = new MedalsModel { Gold = 10, Silver = 12, Bronze = 11 } }, - new CountryModel { Code = "GE", Name = "Georgia", Medals = new MedalsModel { Gold = 2, Silver = 5, Bronze = 1 } }, - new CountryModel { Code = "DE", Name = "Germany", Medals = new MedalsModel { Gold = 10, Silver = 11, Bronze = 16 } }, - new CountryModel { Code = "GH", Name = "Ghana", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = "GB", Name = "Great Britain", Medals = new MedalsModel { Gold = 22, Silver = 21, Bronze = 22 } }, - new CountryModel { Code = "GR", Name = "Greece", Medals = new MedalsModel { Gold = 2, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = "GD", Name = "Grenada", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = "HK", Name = "Hong Kong, China", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 3 } }, - new CountryModel { Code = "HU", Name = "Hungary", Medals = new MedalsModel { Gold = 6, Silver = 7, Bronze = 7 } }, - new CountryModel { Code = "ID", Name = "Indonesia", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 3 } }, - new CountryModel { Code = "IE", Name = "Ireland", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = "IR", Name = "Iran", Medals = new MedalsModel { Gold = 3, Silver = 2, Bronze = 2 } }, - new CountryModel { Code = "IL", Name = "Israel", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = "IT", Name = "Italy", Medals = new MedalsModel { Gold = 10, Silver = 10, Bronze = 20 } }, - new CountryModel { Code = "JM", Name = "Jamaica", Medals = new MedalsModel { Gold = 4, Silver = 1, Bronze = 4 } }, - new CountryModel { Code = "JO", Name = "Jordan", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = "KZ", Name = "Kazakhstan", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 8 } }, - new CountryModel { Code = "KE", Name = "Kenya", Medals = new MedalsModel { Gold = 4, Silver = 4, Bronze = 2 } }, - new CountryModel { Code = "XK", Name = "Kosovo", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = "KW", Name = "Kuwait", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = "LV", Name = "Latvia", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = "LT", Name = "Lithuania", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = "MY", Name = "Malaysia", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = "MX", Name = "Mexico", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 4 } }, - new CountryModel { Code = "MA", Name = "Morocco", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = "NA", Name = "Namibia", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = "NL", Name = "Netherlands", Medals = new MedalsModel { Gold = 10, Silver = 12, Bronze = 14 } }, - new CountryModel { Code = "NZ", Name = "New Zealand", Medals = new MedalsModel { Gold = 7, Silver = 6, Bronze = 7 } }, - new CountryModel { Code = "MK", Name = "North Macedonia", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = "NO", Name = "Norway", Medals = new MedalsModel { Gold = 4, Silver = 2, Bronze = 2 } }, - new CountryModel { Code = "PH", Name = "Philippines", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 1 } }, - new CountryModel { Code = "PL", Name = "Poland", Medals = new MedalsModel { Gold = 4, Silver = 5, Bronze = 5 } }, - new CountryModel { Code = "PT", Name = "Portugal", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = "PR", Name = "Puerto Rico", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = "QA", Name = "Qatar", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = "KR", Name = "Republic of Korea", Medals = new MedalsModel { Gold = 6, Silver = 4, Bronze = 10 } }, - new CountryModel { Code = "MD", Name = "Republic of Moldova", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = "RO", Name = "Romania", Medals = new MedalsModel { Gold = 1, Silver = 3, Bronze = 0 } }, - new CountryModel { Code = "SM", Name = "San Marino", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = "SA", Name = "Saudi Arabia", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = "RS", Name = "Serbia", Medals = new MedalsModel { Gold = 3, Silver = 1, Bronze = 5 } }, - new CountryModel { Code = "SK", Name = "Slovakia", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 1 } }, - new CountryModel { Code = "SI", Name = "Slovenia", Medals = new MedalsModel { Gold = 3, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = "ZA", Name = "South Africa", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 0 } }, - new CountryModel { Code = "ES", Name = "Spain", Medals = new MedalsModel { Gold = 3, Silver = 8, Bronze = 6 } }, - new CountryModel { Code = "SE", Name = "Sweden", Medals = new MedalsModel { Gold = 3, Silver = 6, Bronze = 0 } }, - new CountryModel { Code = "CH", Name = "Switzerland", Medals = new MedalsModel { Gold = 3, Silver = 4, Bronze = 6 } }, - new CountryModel { Code = "SY", Name = "Syrian Arab Republic", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = "TH", Name = "Thailand", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = "TR", Name = "Turkey", Medals = new MedalsModel { Gold = 2, Silver = 2, Bronze = 9 } }, - new CountryModel { Code = "TM", Name = "Turkmenistan", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = "UA", Name = "Ukraine", Medals = new MedalsModel { Gold = 1, Silver = 6, Bronze = 12 } }, - new CountryModel { Code = "US", Name = "United States of America", Medals = new MedalsModel { Gold = 39, Silver = 41, Bronze = 33 } }, - new CountryModel { Code = "UZ", Name = "Uzbekistan", Medals = new MedalsModel { Gold = 3, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = "VE", Name = "Venezuela", Medals = new MedalsModel { Gold = 1, Silver = 3, Bronze = 0 } }, - ]; - - - - private IQueryable allCountries = default!; - private BitQuickGrid dataGrid = default!; - private BitQuickGrid productsDataGrid = default!; - private BitQuickGrid loadingProductsDataGrid = default!; - private BitQuickGridItemsProvider foodRecallProvider = default!; - private BitQuickGridItemsProvider productsItemsProvider = default!; - private BitQuickGridPaginationState pagination1 = new() { ItemsPerPage = 7 }; - private BitQuickGridPaginationState pagination2 = new() { ItemsPerPage = 7 }; - private BitQuickGridPaginationState pagination3 = new() { ItemsPerPage = 7 }; - private BitQuickGridPaginationState pagination6 = new() { ItemsPerPage = 7 }; - private BitQuickGridPaginationState pagination7 = new() { ItemsPerPage = 7 }; - - private HashSet expandedRowTemplateCodes = []; - - private void ToggleRowRendererExpand(string code) - { - if (expandedRowTemplateCodes.Remove(code)) return; - - expandedRowTemplateCodes.Add(code); - } - - private IQueryable? FilteredItems1 => allCountries?.Where(x => x.Name.Contains(typicalSampleNameFilter1 ?? string.Empty, StringComparison.CurrentCultureIgnoreCase)); - private IQueryable? FilteredItems2 => allCountries?.Where(x => x.Name.Contains(typicalSampleNameFilter2 ?? string.Empty, StringComparison.CurrentCultureIgnoreCase)); - - string typicalSampleNameFilter1 = string.Empty; - string typicalSampleNameFilter2 = string.Empty; - - string _virtualSampleNameFilter = string.Empty; - string VirtualSampleNameFilter - { - get => _virtualSampleNameFilter; - set - { - _virtualSampleNameFilter = value; - _ = dataGrid?.RefreshDataAsync(); - } - } - - string _odataSampleNameFilter = string.Empty; - - string ODataSampleNameFilter - { - get => _odataSampleNameFilter; - set - { - _odataSampleNameFilter = value; - // The OData and LoadingTemplate demos share this filter and the same productsItemsProvider, - // so refresh both grids; otherwise the LoadingTemplate grid keeps showing data for the - // previous filter until it is independently re-queried. - _ = productsDataGrid?.RefreshDataAsync(); - _ = loadingProductsDataGrid?.RefreshDataAsync(); - } - } - - - - protected override async Task OnInitAsync() - { - allCountries = _countries.AsQueryable(); - - foodRecallProvider = async req => - { - try - { - // Strip characters that would break the openFDA Lucene query syntax (the firm name is - // wrapped in quotes), namely double quotes and backslashes, before interpolating it. - var firmFilter = _virtualSampleNameFilter?.Replace("\\", string.Empty).Replace("\"", string.Empty).Trim() ?? string.Empty; - var query = new Dictionary - { - { "skip", req.StartIndex }, - { "limit", req.Count ?? 50 } - }; - - // Only constrain by firm when the user actually typed something: sending an empty - // recalling_firm clause is an empty Lucene query that suppresses the default results - // on the initial (unfiltered) load. - if (!string.IsNullOrWhiteSpace(firmFilter)) - { - query.Add("search", $"recalling_firm:\"{firmFilter}\""); - } - - var sort = req.GetSortByProperties().SingleOrDefault(); - - if (sort != default) - { - var sortByColumnName = sort.PropertyName switch - { - nameof(FoodRecall.ReportDate) => "report_date", - _ => throw new InvalidOperationException() - }; - - query.Add("sort", $"{sortByColumnName}:{(sort.Direction == BitQuickGridSortDirection.Ascending ? "asc" : "desc")}"); - } - - var url = NavigationManager.GetUriWithQueryParameters("https://api.fda.gov/food/enforcement.json", query); - - var data = await HttpClient.GetFromJsonAsync(url, AppJsonContext.Default.FoodRecallQueryResult, req.CancellationToken); - - return BitQuickGridItemsProviderResult.From(data!.Results!, data!.Meta!.Results!.Total); - } - catch (OperationCanceledException) when (req.CancellationToken.IsCancellationRequested) - { - // A rapid refresh superseded this request; let the cancellation propagate so the grid - // treats it as a cancelled load rather than a genuine zero-item result. - throw; - } - catch - { - return BitQuickGridItemsProviderResult.From([], 0); - } - }; - - productsItemsProvider = async req => - { - try - { - // https://docs.microsoft.com/en-us/odata/concepts/queryoptions-overview - - var query = new Dictionary() - { - { "$top", req.Count ?? 50 }, - { "$skip", req.StartIndex } - }; - - if (string.IsNullOrWhiteSpace(_odataSampleNameFilter) is false) - { - // Use the trimmed value so a whitespace-only entry isn't treated as a real search - // term, while still escaping apostrophes to keep the OData string literal valid. - var escapedFilter = _odataSampleNameFilter.Trim().Replace("'", "''"); - query.Add("$filter", $"contains(Name,'{escapedFilter}')"); - } - - if (req.GetSortByProperties().Any()) - { - query.Add("$orderby", string.Join(", ", req.GetSortByProperties().Select(p => $"{p.PropertyName} {(p.Direction == BitQuickGridSortDirection.Ascending ? "asc" : "desc")}"))); - } - - var url = NavigationManager.GetUriWithQueryParameters("api/Products/GetProducts", query); - - var data = await HttpClient.GetFromJsonAsync(url, AppJsonContext.Default.PagedResultProductDto, req.CancellationToken); - - return BitQuickGridItemsProviderResult.From(data!.Items!, data!.TotalCount); - } - catch (OperationCanceledException) when (req.CancellationToken.IsCancellationRequested) - { - // A rapid refresh superseded this request; let the cancellation propagate so the grid - // treats it as a cancelled load rather than a genuine zero-item result. - throw; - } - catch - { - return BitQuickGridItemsProviderResult.From(new List { }, 0); - } - }; - - await base.OnInitAsync(); - } -} diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs deleted file mode 100644 index a62094aecb..0000000000 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.samples.cs +++ /dev/null @@ -1,1274 +0,0 @@ -using Bit.BlazorUI.Demo.Client.Core.Components; - -namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Extras.QuickGrid; - -public partial class BitQuickGridDemo : AppComponentBase -{ - private readonly string example1RazorCode = @" - - c.Name)"" Sortable=""true"" IsDefaultSort=""BitQuickGridSortDirection.Ascending""> - - {{""autofocus"", true}})"" /> - - - c.Medals.Gold)"" Sortable=""true"" /> - c.Medals.Silver)"" Sortable=""true"" /> - c.Medals.Bronze)"" Sortable=""true"" /> - c.Medals.Total)"" Sortable=""true"" /> - -"; - private readonly string example1CsharpCode = @" -private IQueryable allCountries; -private string typicalSampleNameFilter = string.Empty; -private BitQuickGridPaginationState pagination = new() { ItemsPerPage = 7 }; -private IQueryable FilteredItems => - allCountries?.Where(x => x.Name.Contains(typicalSampleNameFilter ?? string.Empty, StringComparison.CurrentCultureIgnoreCase)); - -protected override async Task OnInitializedAsync() -{ - allCountries = _countries.AsQueryable(); -} - -private static readonly CountryModel[] _countries = -[ - new CountryModel { Code = ""AR"", Name = ""Argentina"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = ""AM"", Name = ""Armenia"", Medals = new MedalsModel { Gold = 0, Silver = 2, Bronze = 2 } }, - new CountryModel { Code = ""AU"", Name = ""Australia"", Medals = new MedalsModel { Gold = 17, Silver = 7, Bronze = 22 } }, - new CountryModel { Code = ""AT"", Name = ""Austria"", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 5 } }, - new CountryModel { Code = ""AZ"", Name = ""Azerbaijan"", Medals = new MedalsModel { Gold = 0, Silver = 3, Bronze = 4 } }, - new CountryModel { Code = ""BS"", Name = ""Bahamas"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""BH"", Name = ""Bahrain"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""BY"", Name = ""Belarus"", Medals = new MedalsModel { Gold = 1, Silver = 3, Bronze = 3 } }, - new CountryModel { Code = ""BE"", Name = ""Belgium"", Medals = new MedalsModel { Gold = 3, Silver = 1, Bronze = 3 } }, - new CountryModel { Code = ""BM"", Name = ""Bermuda"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""BW"", Name = ""Botswana"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""BR"", Name = ""Brazil"", Medals = new MedalsModel { Gold = 7, Silver = 6, Bronze = 8 } }, - new CountryModel { Code = ""BF"", Name = ""Burkina Faso"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""CA"", Name = ""Canada"", Medals = new MedalsModel { Gold = 7, Silver = 6, Bronze = 11 } }, - new CountryModel { Code = ""TW"", Name = ""Chinese Taipei"", Medals = new MedalsModel { Gold = 2, Silver = 4, Bronze = 6 } }, - new CountryModel { Code = ""CO"", Name = ""Colombia"", Medals = new MedalsModel { Gold = 0, Silver = 4, Bronze = 1 } }, - new CountryModel { Code = ""CI"", Name = ""Côte d'Ivoire"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""HR"", Name = ""Croatia"", Medals = new MedalsModel { Gold = 3, Silver = 3, Bronze = 2 } }, - new CountryModel { Code = ""CU"", Name = ""Cuba"", Medals = new MedalsModel { Gold = 7, Silver = 3, Bronze = 5 } }, - new CountryModel { Code = ""CZ"", Name = ""Czech Republic"", Medals = new MedalsModel { Gold = 4, Silver = 4, Bronze = 3 } }, - new CountryModel { Code = ""DK"", Name = ""Denmark"", Medals = new MedalsModel { Gold = 3, Silver = 4, Bronze = 4 } }, - new CountryModel { Code = ""DO"", Name = ""Dominican Republic"", Medals = new MedalsModel { Gold = 0, Silver = 3, Bronze = 2 } }, - new CountryModel { Code = ""EC"", Name = ""Ecuador"", Medals = new MedalsModel { Gold = 2, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""EE"", Name = ""Estonia"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""ET"", Name = ""Ethiopia"", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = ""FJ"", Name = ""Fiji"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""FI"", Name = ""Finland"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = ""FR"", Name = ""France"", Medals = new MedalsModel { Gold = 10, Silver = 12, Bronze = 11 } }, - new CountryModel { Code = ""GE"", Name = ""Georgia"", Medals = new MedalsModel { Gold = 2, Silver = 5, Bronze = 1 } }, - new CountryModel { Code = ""DE"", Name = ""Germany"", Medals = new MedalsModel { Gold = 10, Silver = 11, Bronze = 16 } }, - new CountryModel { Code = ""GH"", Name = ""Ghana"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""GB"", Name = ""Great Britain"", Medals = new MedalsModel { Gold = 22, Silver = 21, Bronze = 22 } }, - new CountryModel { Code = ""GR"", Name = ""Greece"", Medals = new MedalsModel { Gold = 2, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = ""GD"", Name = ""Grenada"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""HK"", Name = ""Hong Kong, China"", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 3 } }, - new CountryModel { Code = ""HU"", Name = ""Hungary"", Medals = new MedalsModel { Gold = 6, Silver = 7, Bronze = 7 } }, - new CountryModel { Code = ""ID"", Name = ""Indonesia"", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 3 } }, - new CountryModel { Code = ""IE"", Name = ""Ireland"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = ""IR"", Name = ""Iran"", Medals = new MedalsModel { Gold = 3, Silver = 2, Bronze = 2 } }, - new CountryModel { Code = ""IL"", Name = ""Israel"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = ""IT"", Name = ""Italy"", Medals = new MedalsModel { Gold = 10, Silver = 10, Bronze = 20 } }, - new CountryModel { Code = ""JM"", Name = ""Jamaica"", Medals = new MedalsModel { Gold = 4, Silver = 1, Bronze = 4 } }, - new CountryModel { Code = ""JO"", Name = ""Jordan"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = ""KZ"", Name = ""Kazakhstan"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 8 } }, - new CountryModel { Code = ""KE"", Name = ""Kenya"", Medals = new MedalsModel { Gold = 4, Silver = 4, Bronze = 2 } }, - new CountryModel { Code = ""XK"", Name = ""Kosovo"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""KW"", Name = ""Kuwait"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""LV"", Name = ""Latvia"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""LT"", Name = ""Lithuania"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""MY"", Name = ""Malaysia"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = ""MX"", Name = ""Mexico"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 4 } }, - new CountryModel { Code = ""MA"", Name = ""Morocco"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""NA"", Name = ""Namibia"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""NL"", Name = ""Netherlands"", Medals = new MedalsModel { Gold = 10, Silver = 12, Bronze = 14 } }, - new CountryModel { Code = ""NZ"", Name = ""New Zealand"", Medals = new MedalsModel { Gold = 7, Silver = 6, Bronze = 7 } }, - new CountryModel { Code = ""MK"", Name = ""North Macedonia"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""NO"", Name = ""Norway"", Medals = new MedalsModel { Gold = 4, Silver = 2, Bronze = 2 } }, - new CountryModel { Code = ""PH"", Name = ""Philippines"", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 1 } }, - new CountryModel { Code = ""PL"", Name = ""Poland"", Medals = new MedalsModel { Gold = 4, Silver = 5, Bronze = 5 } }, - new CountryModel { Code = ""PT"", Name = ""Portugal"", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = ""PR"", Name = ""Puerto Rico"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""QA"", Name = ""Qatar"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""KR"", Name = ""Republic of Korea"", Medals = new MedalsModel { Gold = 6, Silver = 4, Bronze = 10 } }, - new CountryModel { Code = ""MD"", Name = ""Republic of Moldova"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""RO"", Name = ""Romania"", Medals = new MedalsModel { Gold = 1, Silver = 3, Bronze = 0 } }, - new CountryModel { Code = ""SM"", Name = ""San Marino"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = ""SA"", Name = ""Saudi Arabia"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""RS"", Name = ""Serbia"", Medals = new MedalsModel { Gold = 3, Silver = 1, Bronze = 5 } }, - new CountryModel { Code = ""SK"", Name = ""Slovakia"", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 1 } }, - new CountryModel { Code = ""SI"", Name = ""Slovenia"", Medals = new MedalsModel { Gold = 3, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = ""ZA"", Name = ""South Africa"", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 0 } }, - new CountryModel { Code = ""ES"", Name = ""Spain"", Medals = new MedalsModel { Gold = 3, Silver = 8, Bronze = 6 } }, - new CountryModel { Code = ""SE"", Name = ""Sweden"", Medals = new MedalsModel { Gold = 3, Silver = 6, Bronze = 0 } }, - new CountryModel { Code = ""CH"", Name = ""Switzerland"", Medals = new MedalsModel { Gold = 3, Silver = 4, Bronze = 6 } }, - new CountryModel { Code = ""SY"", Name = ""Syrian Arab Republic"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""TH"", Name = ""Thailand"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""TR"", Name = ""Turkey"", Medals = new MedalsModel { Gold = 2, Silver = 2, Bronze = 9 } }, - new CountryModel { Code = ""TM"", Name = ""Turkmenistan"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""UA"", Name = ""Ukraine"", Medals = new MedalsModel { Gold = 1, Silver = 6, Bronze = 12 } }, - new CountryModel { Code = ""US"", Name = ""United States of America"", Medals = new MedalsModel { Gold = 39, Silver = 41, Bronze = 33 } }, - new CountryModel { Code = ""UZ"", Name = ""Uzbekistan"", Medals = new MedalsModel { Gold = 3, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = ""VE"", Name = ""Venezuela"", Medals = new MedalsModel { Gold = 1, Silver = 3, Bronze = 0 } }, -]; - -public class CountryModel -{ - public string Code { get; set; } - public string Name { get; set; } - public MedalsModel Medals { get; set; } -} - -public class MedalsModel -{ - public int Gold { get; set; } - public int Silver { get; set; } - public int Bronze { get; set; } - public int Total => Gold + Silver + Bronze; -}"; - - private readonly string example2RazorCode = @" - - -
-
- - c.Name)"" IsDefaultSort=""BitQuickGridSortDirection.Ascending"" Sortable=""true"" Class=""wide""> - - {{""autofocus"", true}})"" /> - - - - - - c.Medals.Gold)"" Sortable=""true"" /> - c.Medals.Silver)"" Sortable=""true"" /> - c.Medals.Bronze)"" Sortable=""true"" /> - - - - - -
- $""Total: {v.TotalItemCount}"")""> - @(state.CurrentPageIndex + 1) / @(state.LastPageIndex + 1) - -
"; - private readonly string example2CsharpCode = @" -private IQueryable allCountries; -private string typicalSampleNameFilter = string.Empty; -private BitQuickGridPaginationState pagination = new() { ItemsPerPage = 7 }; -private IQueryable FilteredItems - => allCountries?.Where(x => x.Name.Contains(typicalSampleNameFilter ?? string.Empty, StringComparison.CurrentCultureIgnoreCase)); - -protected override async Task OnInitializedAsync() -{ - allCountries = _countries.AsQueryable(); -} - -private static readonly CountryModel[] _countries = -[ - new CountryModel { Code = ""AR"", Name = ""Argentina"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = ""AM"", Name = ""Armenia"", Medals = new MedalsModel { Gold = 0, Silver = 2, Bronze = 2 } }, - new CountryModel { Code = ""AU"", Name = ""Australia"", Medals = new MedalsModel { Gold = 17, Silver = 7, Bronze = 22 } }, - new CountryModel { Code = ""AT"", Name = ""Austria"", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 5 } }, - new CountryModel { Code = ""AZ"", Name = ""Azerbaijan"", Medals = new MedalsModel { Gold = 0, Silver = 3, Bronze = 4 } }, - new CountryModel { Code = ""BS"", Name = ""Bahamas"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""BH"", Name = ""Bahrain"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""BY"", Name = ""Belarus"", Medals = new MedalsModel { Gold = 1, Silver = 3, Bronze = 3 } }, - new CountryModel { Code = ""BE"", Name = ""Belgium"", Medals = new MedalsModel { Gold = 3, Silver = 1, Bronze = 3 } }, - new CountryModel { Code = ""BM"", Name = ""Bermuda"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""BW"", Name = ""Botswana"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""BR"", Name = ""Brazil"", Medals = new MedalsModel { Gold = 7, Silver = 6, Bronze = 8 } }, - new CountryModel { Code = ""BF"", Name = ""Burkina Faso"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""CA"", Name = ""Canada"", Medals = new MedalsModel { Gold = 7, Silver = 6, Bronze = 11 } }, - new CountryModel { Code = ""TW"", Name = ""Chinese Taipei"", Medals = new MedalsModel { Gold = 2, Silver = 4, Bronze = 6 } }, - new CountryModel { Code = ""CO"", Name = ""Colombia"", Medals = new MedalsModel { Gold = 0, Silver = 4, Bronze = 1 } }, - new CountryModel { Code = ""CI"", Name = ""Côte d'Ivoire"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""HR"", Name = ""Croatia"", Medals = new MedalsModel { Gold = 3, Silver = 3, Bronze = 2 } }, - new CountryModel { Code = ""CU"", Name = ""Cuba"", Medals = new MedalsModel { Gold = 7, Silver = 3, Bronze = 5 } }, - new CountryModel { Code = ""CZ"", Name = ""Czech Republic"", Medals = new MedalsModel { Gold = 4, Silver = 4, Bronze = 3 } }, - new CountryModel { Code = ""DK"", Name = ""Denmark"", Medals = new MedalsModel { Gold = 3, Silver = 4, Bronze = 4 } }, - new CountryModel { Code = ""DO"", Name = ""Dominican Republic"", Medals = new MedalsModel { Gold = 0, Silver = 3, Bronze = 2 } }, - new CountryModel { Code = ""EC"", Name = ""Ecuador"", Medals = new MedalsModel { Gold = 2, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""EE"", Name = ""Estonia"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""ET"", Name = ""Ethiopia"", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = ""FJ"", Name = ""Fiji"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""FI"", Name = ""Finland"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = ""FR"", Name = ""France"", Medals = new MedalsModel { Gold = 10, Silver = 12, Bronze = 11 } }, - new CountryModel { Code = ""GE"", Name = ""Georgia"", Medals = new MedalsModel { Gold = 2, Silver = 5, Bronze = 1 } }, - new CountryModel { Code = ""DE"", Name = ""Germany"", Medals = new MedalsModel { Gold = 10, Silver = 11, Bronze = 16 } }, - new CountryModel { Code = ""GH"", Name = ""Ghana"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""GB"", Name = ""Great Britain"", Medals = new MedalsModel { Gold = 22, Silver = 21, Bronze = 22 } }, - new CountryModel { Code = ""GR"", Name = ""Greece"", Medals = new MedalsModel { Gold = 2, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = ""GD"", Name = ""Grenada"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""HK"", Name = ""Hong Kong, China"", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 3 } }, - new CountryModel { Code = ""HU"", Name = ""Hungary"", Medals = new MedalsModel { Gold = 6, Silver = 7, Bronze = 7 } }, - new CountryModel { Code = ""ID"", Name = ""Indonesia"", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 3 } }, - new CountryModel { Code = ""IE"", Name = ""Ireland"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = ""IR"", Name = ""Iran"", Medals = new MedalsModel { Gold = 3, Silver = 2, Bronze = 2 } }, - new CountryModel { Code = ""IL"", Name = ""Israel"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = ""IT"", Name = ""Italy"", Medals = new MedalsModel { Gold = 10, Silver = 10, Bronze = 20 } }, - new CountryModel { Code = ""JM"", Name = ""Jamaica"", Medals = new MedalsModel { Gold = 4, Silver = 1, Bronze = 4 } }, - new CountryModel { Code = ""JO"", Name = ""Jordan"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = ""KZ"", Name = ""Kazakhstan"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 8 } }, - new CountryModel { Code = ""KE"", Name = ""Kenya"", Medals = new MedalsModel { Gold = 4, Silver = 4, Bronze = 2 } }, - new CountryModel { Code = ""XK"", Name = ""Kosovo"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""KW"", Name = ""Kuwait"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""LV"", Name = ""Latvia"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""LT"", Name = ""Lithuania"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""MY"", Name = ""Malaysia"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = ""MX"", Name = ""Mexico"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 4 } }, - new CountryModel { Code = ""MA"", Name = ""Morocco"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""NA"", Name = ""Namibia"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""NL"", Name = ""Netherlands"", Medals = new MedalsModel { Gold = 10, Silver = 12, Bronze = 14 } }, - new CountryModel { Code = ""NZ"", Name = ""New Zealand"", Medals = new MedalsModel { Gold = 7, Silver = 6, Bronze = 7 } }, - new CountryModel { Code = ""MK"", Name = ""North Macedonia"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""NO"", Name = ""Norway"", Medals = new MedalsModel { Gold = 4, Silver = 2, Bronze = 2 } }, - new CountryModel { Code = ""PH"", Name = ""Philippines"", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 1 } }, - new CountryModel { Code = ""PL"", Name = ""Poland"", Medals = new MedalsModel { Gold = 4, Silver = 5, Bronze = 5 } }, - new CountryModel { Code = ""PT"", Name = ""Portugal"", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = ""PR"", Name = ""Puerto Rico"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""QA"", Name = ""Qatar"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""KR"", Name = ""Republic of Korea"", Medals = new MedalsModel { Gold = 6, Silver = 4, Bronze = 10 } }, - new CountryModel { Code = ""MD"", Name = ""Republic of Moldova"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""RO"", Name = ""Romania"", Medals = new MedalsModel { Gold = 1, Silver = 3, Bronze = 0 } }, - new CountryModel { Code = ""SM"", Name = ""San Marino"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = ""SA"", Name = ""Saudi Arabia"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""RS"", Name = ""Serbia"", Medals = new MedalsModel { Gold = 3, Silver = 1, Bronze = 5 } }, - new CountryModel { Code = ""SK"", Name = ""Slovakia"", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 1 } }, - new CountryModel { Code = ""SI"", Name = ""Slovenia"", Medals = new MedalsModel { Gold = 3, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = ""ZA"", Name = ""South Africa"", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 0 } }, - new CountryModel { Code = ""ES"", Name = ""Spain"", Medals = new MedalsModel { Gold = 3, Silver = 8, Bronze = 6 } }, - new CountryModel { Code = ""SE"", Name = ""Sweden"", Medals = new MedalsModel { Gold = 3, Silver = 6, Bronze = 0 } }, - new CountryModel { Code = ""CH"", Name = ""Switzerland"", Medals = new MedalsModel { Gold = 3, Silver = 4, Bronze = 6 } }, - new CountryModel { Code = ""SY"", Name = ""Syrian Arab Republic"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""TH"", Name = ""Thailand"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""TR"", Name = ""Turkey"", Medals = new MedalsModel { Gold = 2, Silver = 2, Bronze = 9 } }, - new CountryModel { Code = ""TM"", Name = ""Turkmenistan"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""UA"", Name = ""Ukraine"", Medals = new MedalsModel { Gold = 1, Silver = 6, Bronze = 12 } }, - new CountryModel { Code = ""US"", Name = ""United States of America"", Medals = new MedalsModel { Gold = 39, Silver = 41, Bronze = 33 } }, - new CountryModel { Code = ""UZ"", Name = ""Uzbekistan"", Medals = new MedalsModel { Gold = 3, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = ""VE"", Name = ""Venezuela"", Medals = new MedalsModel { Gold = 1, Silver = 3, Bronze = 0 } }, -]; - -public class CountryModel -{ - public string Code { get; set; } - public string Name { get; set; } - public MedalsModel Medals { get; set; } -} - -public class MedalsModel -{ - public int Gold { get; set; } - public int Silver { get; set; } - public int Bronze { get; set; } - public int Total => Gold + Silver + Bronze; -} -"; - - private readonly string example3RazorCode = @" -@using System.Text.Json; -@using System.Text.Json.Serialization; -@inject HttpClient HttpClient -@inject NavigationManager NavManager - - - -
- - c.EventId)"" /> - c.State)"" /> - c.City)"" /> - c.RecallingFirm)"" Title=""Company"" /> - c.Status)"" /> - c.ReportDate)"" Title=""Report Date"" Sortable=""true"" /> - -
-
- -
"; - private readonly string example3CsharpCode = @" -BitQuickGrid? dataGrid; -string _virtualSampleNameFilter = string.Empty; -BitQuickGridItemsProvider foodRecallProvider; - -string VirtualSampleNameFilter -{ - get => _virtualSampleNameFilter; - set - { - _virtualSampleNameFilter = value; - _ = dataGrid?.RefreshDataAsync(); - } -} - -protected override async Task OnInitializedAsync() -{ - foodRecallProvider = async req => - { - try - { - // Strip characters that would break the openFDA Lucene query syntax (the firm name is - // wrapped in quotes), namely double quotes and backslashes, before interpolating it. Trim - // surrounding whitespace too, since the quoted phrase is matched exactly and stray spaces - // would prevent otherwise-valid firm names from matching. - var firmFilter = _virtualSampleNameFilter?.Replace(""\\"", string.Empty).Replace(""\"""""", string.Empty).Trim() ?? string.Empty; - var query = new Dictionary - { - { ""skip"", req.StartIndex }, - { ""limit"", req.Count ?? 50 } - }; - - // Only add the firm filter when the user typed something; an empty Lucene clause - // would suppress the default (unfiltered) results. - if (!string.IsNullOrWhiteSpace(firmFilter)) - { - query.Add(""search"", $""recalling_firm:\""{firmFilter}\""""); - } - - var sort = req.GetSortByProperties().SingleOrDefault(); - - if (sort != default) - { - var sortByColumnName = sort.PropertyName switch - { - nameof(FoodRecall.ReportDate) => ""report_date"", - _ => throw new InvalidOperationException() - }; - - query.Add(""sort"", $""{sortByColumnName}:{(sort.Direction == BitQuickGridSortDirection.Ascending ? ""asc"" : ""desc"")}""); - } - - var url = NavManager.GetUriWithQueryParameters(""https://api.fda.gov/food/enforcement.json"", query); - - var data = await HttpClient.GetFromJsonAsync(url, AppJsonContext.Default.FoodRecallQueryResult, req.CancellationToken); - - return BitQuickGridItemsProviderResult.From( - items: data!.Results, - totalItemCount: data!.Meta.Results.Total); - } - catch (OperationCanceledException) when (req.CancellationToken.IsCancellationRequested) - { - throw; // a rapid refresh superseded this request; let cancellation flow through - } - catch - { - return BitQuickGridItemsProviderResult.From(new List { }, 0); - } - }; -} - -//https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator/ -[JsonSerializable(typeof(FoodRecallQueryResult))] -[JsonSerializable(typeof(Meta))] -[JsonSerializable(typeof(FoodRecall))] -[JsonSerializable(typeof(Results))] -[JsonSerializable(typeof(Openfda))] -public partial class AppJsonContext : JsonSerializerContext -{ - -} - -public class FoodRecallQueryResult -{ - [JsonPropertyName(""meta"")] - public Meta? Meta { get; set; } - - [JsonPropertyName(""results"")] - public List? Results { get; set; } -} - -public class Meta -{ - [JsonPropertyName(""disclaimer"")] - public string? Disclaimer { get; set; } - - [JsonPropertyName(""terms"")] - public string? Terms { get; set; } - - [JsonPropertyName(""license"")] - public string? License { get; set; } - - [JsonPropertyName(""last_updated"")] - public string? LastUpdated { get; set; } - - [JsonPropertyName(""results"")] - public Results? Results { get; set; } -} - -public class FoodRecall -{ - [JsonPropertyName(""country"")] - public string? CountryModel { get; set; } - - [JsonPropertyName(""city"")] - public string? City { get; set; } - - [JsonPropertyName(""address_1"")] - public string? Address1 { get; set; } - - [JsonPropertyName(""reason_for_recall"")] - public string? ReasonForRecall { get; set; } - - [JsonPropertyName(""address_2"")] - public string? Address2 { get; set; } - - [JsonPropertyName(""product_quantity"")] - public string? ProductQuantity { get; set; } - - [JsonPropertyName(""code_info"")] - public string? CodeInfo { get; set; } - - [JsonPropertyName(""center_classification_date"")] - public string? CenterClassificationDate { get; set; } - - [JsonPropertyName(""distribution_pattern"")] - public string? DistributionPattern { get; set; } - - [JsonPropertyName(""state"")] - public string? State { get; set; } - - [JsonPropertyName(""product_description"")] - public string? ProductDescription { get; set; } - - [JsonPropertyName(""report_date"")] - public string? ReportDate { get; set; } - - [JsonPropertyName(""classification"")] - public string? Classification { get; set; } - - [JsonPropertyName(""openfda"")] - public Openfda? Openfda { get; set; } - - [JsonPropertyName(""recalling_firm"")] - public string? RecallingFirm { get; set; } - - [JsonPropertyName(""recall_number"")] - public string? RecallNumber { get; set; } - - [JsonPropertyName(""initial_firm_notification"")] - public string? InitialFirmNotification { get; set; } - - [JsonPropertyName(""product_type"")] - public string? ProductType { get; set; } - - [JsonPropertyName(""event_id"")] - public string? EventId { get; set; } - - [JsonPropertyName(""more_code_info"")] - public string? MoreCodeInfo { get; set; } - - [JsonPropertyName(""recall_initiation_date"")] - public string? RecallInitiationDate { get; set; } - - [JsonPropertyName(""postal_code"")] - public string? PostalCode { get; set; } - - [JsonPropertyName(""voluntary_mandated"")] - public string? VoluntaryMandated { get; set; } - - [JsonPropertyName(""status"")] - public string? Status { get; set; } -} - -public class Results -{ - [JsonPropertyName(""skip"")] - public int Skip { get; set; } - - [JsonPropertyName(""limit"")] - public int Limit { get; set; } - - [JsonPropertyName(""total"")] - public int Total { get; set; } -} - -public class Openfda -{ -} -"; - - private readonly string example4RazorCode = @" -@using System.Text.Json; -@using System.Text.Json.Serialization; -@inject HttpClient HttpClient -@inject NavigationManager NavManager - - - -
- p.Id)"" TGridItem=""ProductDto"" Virtualize ItemSize=""32""> - p.Id)"" Sortable=""true"" IsDefaultSort=""BitQuickGridSortDirection.Ascending"" /> - p.Name)"" Sortable=""true"" /> - p.Price)"" Sortable=""true"" /> - -
-
- -
"; - private readonly string example4CsharpCode = @" - -// ========== Server code ========== - -// To make following aspnetcore controller work, simply change services.AddControllers(); to services.AddControllers().AddOData(options => options.EnableQueryFeatures()) -// Note that this need Microsoft.AspNetCore.OData nuget package to be installed - -[ApiController] -[Route(""api/[controller]/[action]"")] -public class ProductsController : ControllerBase -{ - private static readonly Random _random = new Random(); - - private static readonly ProductDto[] _products = Enumerable.Range(1, 500_000) - .Select(i => new ProductDto { Id = i, Name = Guid.NewGuid().ToString(""N""), Price = _random.Next(1, 100) }) - .ToArray(); - - [HttpGet] - public async Task> GetProducts(ODataQueryOptions odataQuery, CancellationToken cancellationToken) - { - var query = _products.AsQueryable(); - - query = (IQueryable)odataQuery.ApplyTo(query, ignoreQueryOptions: AllowedQueryOptions.Top | AllowedQueryOptions.Skip); - - var totalCount = query.Count(); - - if (odataQuery.Skip is not null) - query = query.Skip(odataQuery.Skip.Value); - - query = query.Take(odataQuery.Top?.Value ?? 50); - - return new PagedResult(query.ToArray(), totalCount); - } -} - - -// ========== Shared code ========== - - -//https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator/ -[JsonSerializable(typeof(PagedResult))] -public partial class AppJsonContext : JsonSerializerContext -{ - -} - -public class ProductDto -{ - public int Id { get; set; } - - public string Name { get; set; } - - public decimal Price { get; set; } -} - -public class PagedResult -{ - public IList? Items { get; set; } - - public int TotalCount { get; set; } - - public PagedResult(IList items, int totalCount) - { - Items = items; - TotalCount = totalCount; - } - - public PagedResult() - { - - } -} - - -// ========== Client code ========== - - -BitQuickGrid? productsDataGrid; -string _odataSampleNameFilter = string.Empty; -BitQuickGridItemsProvider productsItemsProvider; - -string ODataSampleNameFilter -{ - get => _odataSampleNameFilter; - set - { - _odataSampleNameFilter = value; - _ = productsDataGrid?.RefreshDataAsync(); - } -} - -protected override async Task OnInitializedAsync() -{ - productsItemsProvider = async req => - { - try - { - // https://docs.microsoft.com/en-us/odata/concepts/queryoptions-overview - - var query = new Dictionary() - { - { ""$top"", req.Count ?? 50 }, - { ""$skip"", req.StartIndex } - }; - - if (string.IsNullOrWhiteSpace(_odataSampleNameFilter) is false) - { - // Use the trimmed value so a whitespace-only entry isn't treated as a real search - // term, while still escaping apostrophes to keep the OData string literal valid. - var escapedFilter = _odataSampleNameFilter.Trim().Replace(""'"", ""''""); - query.Add(""$filter"", $""contains(Name,'{escapedFilter}')""); - } - - if (req.GetSortByProperties().Any()) - { - query.Add(""$orderby"", string.Join("", "", req.GetSortByProperties().Select(p => $""{p.PropertyName} {(p.Direction == BitQuickGridSortDirection.Ascending ? ""asc"" : ""desc"")}""))); - } - - var url = NavManager.GetUriWithQueryParameters(""api/Products/GetProducts"", query); - - var data = await HttpClient.GetFromJsonAsync(url, AppJsonContext.Default.PagedResultProductDto, req.CancellationToken); - - return BitQuickGridItemsProviderResult.From(data!.Items, (int)data!.TotalCount); - } - catch (OperationCanceledException) when (req.CancellationToken.IsCancellationRequested) - { - throw; // a rapid refresh superseded this request; let cancellation flow through - } - catch - { - return BitQuickGridItemsProviderResult.From(new List { }, 0); - } - }; -}"; - - private readonly string example5RazorCode = @" -@using System.Text.Json; -@using System.Text.Json.Serialization; -@inject HttpClient HttpClient -@inject NavigationManager NavManager - - - -
- p.Id)"" TGridItem=""ProductDto"" Pagination=""@pagination""> - - p.Id)"" Sortable=""true"" IsDefaultSort=""BitQuickGridSortDirection.Ascending"" /> - p.Name)"" Sortable=""true"" /> - p.Price)"" Sortable=""true"" /> - - - - Loading items... - - - - -
- $""Total: {v.TotalItemCount?.ToString(""N0"")}"")""> - @(state.CurrentPageIndex + 1) / @(state.LastPageIndex + 1) -"; - private readonly string example5CsharpCode = @" - -// ========== Server code ========== - -// To make following aspnetcore controller work, simply change services.AddControllers(); to services.AddControllers().AddOData(options => options.EnableQueryFeatures()) -// Note that this need Microsoft.AspNetCore.OData nuget package to be installed - -[ApiController] -[Route(""api/[controller]/[action]"")] -public class ProductsController : ControllerBase -{ - private static readonly Random _random = new Random(); - - private static readonly ProductDto[] _products = Enumerable.Range(1, 500_000) - .Select(i => new ProductDto { Id = i, Name = Guid.NewGuid().ToString(""N""), Price = _random.Next(1, 100) }) - .ToArray(); - - [HttpGet] - public async Task> GetProducts(ODataQueryOptions odataQuery, CancellationToken cancellationToken) - { - var query = _products.AsQueryable(); - - query = (IQueryable)odataQuery.ApplyTo(query, ignoreQueryOptions: AllowedQueryOptions.Top | AllowedQueryOptions.Skip); - - var totalCount = query.Count(); - - if (odataQuery.Skip is not null) - { - query = query.Skip(odataQuery.Skip.Value); - } - - query = query.Take(odataQuery.Top?.Value ?? 50); - - return new PagedResult(query.ToArray(), totalCount); - } -} - - -// ========== Shared code ========== - - -//https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator/ -[JsonSerializable(typeof(PagedResult))] -public partial class AppJsonContext : JsonSerializerContext -{ - -} - -public class ProductDto -{ - public int Id { get; set; } - - public string Name { get; set; } - - public decimal Price { get; set; } -} - -public class PagedResult -{ - public IList? Items { get; set; } - - public int TotalCount { get; set; } - - public PagedResult(IList items, int totalCount) - { - Items = items; - TotalCount = totalCount; - } - - public PagedResult() - { - - } -} - - -// ========== Client code ========== - - -BitQuickGrid? productsDataGrid; -BitQuickGridItemsProvider productsItemsProvider; -BitQuickGridPaginationState pagination = new() { ItemsPerPage = 7 }; - -protected override async Task OnInitializedAsync() -{ - productsItemsProvider = async req => - { - try - { - // https://docs.microsoft.com/en-us/odata/concepts/queryoptions-overview - - var query = new Dictionary() - { - { ""$top"", req.Count ?? 50 }, - { ""$skip"", req.StartIndex } - }; - - if (req.GetSortByProperties().Any()) - { - query.Add(""$orderby"", string.Join("", "", req.GetSortByProperties().Select(p => $""{p.PropertyName} {(p.Direction == BitQuickGridSortDirection.Ascending ? ""asc"" : ""desc"")}""))); - } - - var url = NavManager.GetUriWithQueryParameters(""api/Products/GetProducts"", query); - - var data = await HttpClient.GetFromJsonAsync(url, AppJsonContext.Default.PagedResultProductDto, req.CancellationToken); - - return BitQuickGridItemsProviderResult.From(data!.Items, (int)data!.TotalCount); - } - catch (OperationCanceledException) when (req.CancellationToken.IsCancellationRequested) - { - throw; // a rapid refresh superseded this request; let cancellation flow through - } - catch - { - return BitQuickGridItemsProviderResult.From(new List { }, 0); - } - }; -}"; - - private readonly string example6RazorCode = @" - - -
- - c.Name)"" /> - c.Medals.Gold)"" /> - c.Medals.Silver)"" /> - c.Medals.Bronze)"" /> - c.Medals.Total)"" /> - - -
"; - private readonly string example6CsharpCode = @" -private IQueryable allCountries; -private BitQuickGridPaginationState pagination = new() { ItemsPerPage = 7 }; - -protected override async Task OnInitializedAsync() -{ - allCountries = _countries.AsQueryable(); -} - -private static readonly CountryModel[] _countries = -[ - new CountryModel { Code = ""AR"", Name = ""Argentina"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = ""AM"", Name = ""Armenia"", Medals = new MedalsModel { Gold = 0, Silver = 2, Bronze = 2 } }, - new CountryModel { Code = ""AU"", Name = ""Australia"", Medals = new MedalsModel { Gold = 17, Silver = 7, Bronze = 22 } }, - new CountryModel { Code = ""AT"", Name = ""Austria"", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 5 } }, - new CountryModel { Code = ""AZ"", Name = ""Azerbaijan"", Medals = new MedalsModel { Gold = 0, Silver = 3, Bronze = 4 } }, - new CountryModel { Code = ""BS"", Name = ""Bahamas"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""BH"", Name = ""Bahrain"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""BY"", Name = ""Belarus"", Medals = new MedalsModel { Gold = 1, Silver = 3, Bronze = 3 } }, - new CountryModel { Code = ""BE"", Name = ""Belgium"", Medals = new MedalsModel { Gold = 3, Silver = 1, Bronze = 3 } }, - new CountryModel { Code = ""BM"", Name = ""Bermuda"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""BW"", Name = ""Botswana"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""BR"", Name = ""Brazil"", Medals = new MedalsModel { Gold = 7, Silver = 6, Bronze = 8 } }, - new CountryModel { Code = ""BF"", Name = ""Burkina Faso"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""CA"", Name = ""Canada"", Medals = new MedalsModel { Gold = 7, Silver = 6, Bronze = 11 } }, - new CountryModel { Code = ""TW"", Name = ""Chinese Taipei"", Medals = new MedalsModel { Gold = 2, Silver = 4, Bronze = 6 } }, - new CountryModel { Code = ""CO"", Name = ""Colombia"", Medals = new MedalsModel { Gold = 0, Silver = 4, Bronze = 1 } }, - new CountryModel { Code = ""CI"", Name = ""Côte d'Ivoire"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""HR"", Name = ""Croatia"", Medals = new MedalsModel { Gold = 3, Silver = 3, Bronze = 2 } }, - new CountryModel { Code = ""CU"", Name = ""Cuba"", Medals = new MedalsModel { Gold = 7, Silver = 3, Bronze = 5 } }, - new CountryModel { Code = ""CZ"", Name = ""Czech Republic"", Medals = new MedalsModel { Gold = 4, Silver = 4, Bronze = 3 } }, - new CountryModel { Code = ""DK"", Name = ""Denmark"", Medals = new MedalsModel { Gold = 3, Silver = 4, Bronze = 4 } }, - new CountryModel { Code = ""DO"", Name = ""Dominican Republic"", Medals = new MedalsModel { Gold = 0, Silver = 3, Bronze = 2 } }, - new CountryModel { Code = ""EC"", Name = ""Ecuador"", Medals = new MedalsModel { Gold = 2, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""EE"", Name = ""Estonia"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""ET"", Name = ""Ethiopia"", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = ""FJ"", Name = ""Fiji"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""FI"", Name = ""Finland"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = ""FR"", Name = ""France"", Medals = new MedalsModel { Gold = 10, Silver = 12, Bronze = 11 } }, - new CountryModel { Code = ""GE"", Name = ""Georgia"", Medals = new MedalsModel { Gold = 2, Silver = 5, Bronze = 1 } }, - new CountryModel { Code = ""DE"", Name = ""Germany"", Medals = new MedalsModel { Gold = 10, Silver = 11, Bronze = 16 } }, - new CountryModel { Code = ""GH"", Name = ""Ghana"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""GB"", Name = ""Great Britain"", Medals = new MedalsModel { Gold = 22, Silver = 21, Bronze = 22 } }, - new CountryModel { Code = ""GR"", Name = ""Greece"", Medals = new MedalsModel { Gold = 2, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = ""GD"", Name = ""Grenada"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""HK"", Name = ""Hong Kong, China"", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 3 } }, - new CountryModel { Code = ""HU"", Name = ""Hungary"", Medals = new MedalsModel { Gold = 6, Silver = 7, Bronze = 7 } }, - new CountryModel { Code = ""ID"", Name = ""Indonesia"", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 3 } }, - new CountryModel { Code = ""IE"", Name = ""Ireland"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = ""IR"", Name = ""Iran"", Medals = new MedalsModel { Gold = 3, Silver = 2, Bronze = 2 } }, - new CountryModel { Code = ""IL"", Name = ""Israel"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = ""IT"", Name = ""Italy"", Medals = new MedalsModel { Gold = 10, Silver = 10, Bronze = 20 } }, - new CountryModel { Code = ""JM"", Name = ""Jamaica"", Medals = new MedalsModel { Gold = 4, Silver = 1, Bronze = 4 } }, - new CountryModel { Code = ""JO"", Name = ""Jordan"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = ""KZ"", Name = ""Kazakhstan"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 8 } }, - new CountryModel { Code = ""KE"", Name = ""Kenya"", Medals = new MedalsModel { Gold = 4, Silver = 4, Bronze = 2 } }, - new CountryModel { Code = ""XK"", Name = ""Kosovo"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""KW"", Name = ""Kuwait"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""LV"", Name = ""Latvia"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""LT"", Name = ""Lithuania"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""MY"", Name = ""Malaysia"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = ""MX"", Name = ""Mexico"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 4 } }, - new CountryModel { Code = ""MA"", Name = ""Morocco"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""NA"", Name = ""Namibia"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""NL"", Name = ""Netherlands"", Medals = new MedalsModel { Gold = 10, Silver = 12, Bronze = 14 } }, - new CountryModel { Code = ""NZ"", Name = ""New Zealand"", Medals = new MedalsModel { Gold = 7, Silver = 6, Bronze = 7 } }, - new CountryModel { Code = ""MK"", Name = ""North Macedonia"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""NO"", Name = ""Norway"", Medals = new MedalsModel { Gold = 4, Silver = 2, Bronze = 2 } }, - new CountryModel { Code = ""PH"", Name = ""Philippines"", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 1 } }, - new CountryModel { Code = ""PL"", Name = ""Poland"", Medals = new MedalsModel { Gold = 4, Silver = 5, Bronze = 5 } }, - new CountryModel { Code = ""PT"", Name = ""Portugal"", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = ""PR"", Name = ""Puerto Rico"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""QA"", Name = ""Qatar"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""KR"", Name = ""Republic of Korea"", Medals = new MedalsModel { Gold = 6, Silver = 4, Bronze = 10 } }, - new CountryModel { Code = ""MD"", Name = ""Republic of Moldova"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""RO"", Name = ""Romania"", Medals = new MedalsModel { Gold = 1, Silver = 3, Bronze = 0 } }, - new CountryModel { Code = ""SM"", Name = ""San Marino"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = ""SA"", Name = ""Saudi Arabia"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""RS"", Name = ""Serbia"", Medals = new MedalsModel { Gold = 3, Silver = 1, Bronze = 5 } }, - new CountryModel { Code = ""SK"", Name = ""Slovakia"", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 1 } }, - new CountryModel { Code = ""SI"", Name = ""Slovenia"", Medals = new MedalsModel { Gold = 3, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = ""ZA"", Name = ""South Africa"", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 0 } }, - new CountryModel { Code = ""ES"", Name = ""Spain"", Medals = new MedalsModel { Gold = 3, Silver = 8, Bronze = 6 } }, - new CountryModel { Code = ""SE"", Name = ""Sweden"", Medals = new MedalsModel { Gold = 3, Silver = 6, Bronze = 0 } }, - new CountryModel { Code = ""CH"", Name = ""Switzerland"", Medals = new MedalsModel { Gold = 3, Silver = 4, Bronze = 6 } }, - new CountryModel { Code = ""SY"", Name = ""Syrian Arab Republic"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""TH"", Name = ""Thailand"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""TR"", Name = ""Turkey"", Medals = new MedalsModel { Gold = 2, Silver = 2, Bronze = 9 } }, - new CountryModel { Code = ""TM"", Name = ""Turkmenistan"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""UA"", Name = ""Ukraine"", Medals = new MedalsModel { Gold = 1, Silver = 6, Bronze = 12 } }, - new CountryModel { Code = ""US"", Name = ""United States of America"", Medals = new MedalsModel { Gold = 39, Silver = 41, Bronze = 33 } }, - new CountryModel { Code = ""UZ"", Name = ""Uzbekistan"", Medals = new MedalsModel { Gold = 3, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = ""VE"", Name = ""Venezuela"", Medals = new MedalsModel { Gold = 1, Silver = 3, Bronze = 0 } }, -]; - -public class CountryModel -{ - public string Code { get; set; } - public string Name { get; set; } - public MedalsModel Medals { get; set; } -} - -public class MedalsModel -{ - public int Gold { get; set; } - public int Silver { get; set; } - public int Bronze { get; set; } - public int Total => Gold + Silver + Bronze; -}"; - - private readonly string example7RazorCode = @" - - -
- - - - ToggleRowRendererExpand(context.Code))"" /> - - c.Name)"" /> - c.Medals.Gold)"" /> - c.Medals.Silver)"" /> - c.Medals.Bronze)"" /> - c.Medals.Total)"" /> - - - @args.OriginalRow - @if (expandedRowTemplateCodes.Contains(args.RowItem.Code)) - { - - -
- Additional data: - Code: [@args.RowItem.Code] - - Gold: [@args.RowItem.Medals.Gold], - Silver: [@args.RowItem.Medals.Silver], - Bronze: [@args.RowItem.Medals.Bronze] - (Total: @args.RowItem.Medals.Total) -
- - - } -
-
- -
"; - private readonly string example7CsharpCode = @" -private IQueryable allCountries; -private BitQuickGridPaginationState pagination = new() { ItemsPerPage = 7 }; - -protected override async Task OnInitializedAsync() -{ - allCountries = _countries.AsQueryable(); -} - -private HashSet expandedRowTemplateCodes = []; - -private void ToggleRowRendererExpand(string code) -{ - if (expandedRowTemplateCodes.Remove(code)) return; - - expandedRowTemplateCodes.Add(code); -} - -private static readonly CountryModel[] _countries = -[ - new CountryModel { Code = ""AR"", Name = ""Argentina"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = ""AM"", Name = ""Armenia"", Medals = new MedalsModel { Gold = 0, Silver = 2, Bronze = 2 } }, - new CountryModel { Code = ""AU"", Name = ""Australia"", Medals = new MedalsModel { Gold = 17, Silver = 7, Bronze = 22 } }, - new CountryModel { Code = ""AT"", Name = ""Austria"", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 5 } }, - new CountryModel { Code = ""AZ"", Name = ""Azerbaijan"", Medals = new MedalsModel { Gold = 0, Silver = 3, Bronze = 4 } }, - new CountryModel { Code = ""BS"", Name = ""Bahamas"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""BH"", Name = ""Bahrain"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""BY"", Name = ""Belarus"", Medals = new MedalsModel { Gold = 1, Silver = 3, Bronze = 3 } }, - new CountryModel { Code = ""BE"", Name = ""Belgium"", Medals = new MedalsModel { Gold = 3, Silver = 1, Bronze = 3 } }, - new CountryModel { Code = ""BM"", Name = ""Bermuda"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""BW"", Name = ""Botswana"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""BR"", Name = ""Brazil"", Medals = new MedalsModel { Gold = 7, Silver = 6, Bronze = 8 } }, - new CountryModel { Code = ""BF"", Name = ""Burkina Faso"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""CA"", Name = ""Canada"", Medals = new MedalsModel { Gold = 7, Silver = 6, Bronze = 11 } }, - new CountryModel { Code = ""TW"", Name = ""Chinese Taipei"", Medals = new MedalsModel { Gold = 2, Silver = 4, Bronze = 6 } }, - new CountryModel { Code = ""CO"", Name = ""Colombia"", Medals = new MedalsModel { Gold = 0, Silver = 4, Bronze = 1 } }, - new CountryModel { Code = ""CI"", Name = ""Côte d'Ivoire"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""HR"", Name = ""Croatia"", Medals = new MedalsModel { Gold = 3, Silver = 3, Bronze = 2 } }, - new CountryModel { Code = ""CU"", Name = ""Cuba"", Medals = new MedalsModel { Gold = 7, Silver = 3, Bronze = 5 } }, - new CountryModel { Code = ""CZ"", Name = ""Czech Republic"", Medals = new MedalsModel { Gold = 4, Silver = 4, Bronze = 3 } }, - new CountryModel { Code = ""DK"", Name = ""Denmark"", Medals = new MedalsModel { Gold = 3, Silver = 4, Bronze = 4 } }, - new CountryModel { Code = ""DO"", Name = ""Dominican Republic"", Medals = new MedalsModel { Gold = 0, Silver = 3, Bronze = 2 } }, - new CountryModel { Code = ""EC"", Name = ""Ecuador"", Medals = new MedalsModel { Gold = 2, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""EE"", Name = ""Estonia"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""ET"", Name = ""Ethiopia"", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = ""FJ"", Name = ""Fiji"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""FI"", Name = ""Finland"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = ""FR"", Name = ""France"", Medals = new MedalsModel { Gold = 10, Silver = 12, Bronze = 11 } }, - new CountryModel { Code = ""GE"", Name = ""Georgia"", Medals = new MedalsModel { Gold = 2, Silver = 5, Bronze = 1 } }, - new CountryModel { Code = ""DE"", Name = ""Germany"", Medals = new MedalsModel { Gold = 10, Silver = 11, Bronze = 16 } }, - new CountryModel { Code = ""GH"", Name = ""Ghana"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""GB"", Name = ""Great Britain"", Medals = new MedalsModel { Gold = 22, Silver = 21, Bronze = 22 } }, - new CountryModel { Code = ""GR"", Name = ""Greece"", Medals = new MedalsModel { Gold = 2, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = ""GD"", Name = ""Grenada"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""HK"", Name = ""Hong Kong, China"", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 3 } }, - new CountryModel { Code = ""HU"", Name = ""Hungary"", Medals = new MedalsModel { Gold = 6, Silver = 7, Bronze = 7 } }, - new CountryModel { Code = ""ID"", Name = ""Indonesia"", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 3 } }, - new CountryModel { Code = ""IE"", Name = ""Ireland"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = ""IR"", Name = ""Iran"", Medals = new MedalsModel { Gold = 3, Silver = 2, Bronze = 2 } }, - new CountryModel { Code = ""IL"", Name = ""Israel"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = ""IT"", Name = ""Italy"", Medals = new MedalsModel { Gold = 10, Silver = 10, Bronze = 20 } }, - new CountryModel { Code = ""JM"", Name = ""Jamaica"", Medals = new MedalsModel { Gold = 4, Silver = 1, Bronze = 4 } }, - new CountryModel { Code = ""JO"", Name = ""Jordan"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = ""KZ"", Name = ""Kazakhstan"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 8 } }, - new CountryModel { Code = ""KE"", Name = ""Kenya"", Medals = new MedalsModel { Gold = 4, Silver = 4, Bronze = 2 } }, - new CountryModel { Code = ""XK"", Name = ""Kosovo"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""KW"", Name = ""Kuwait"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""LV"", Name = ""Latvia"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""LT"", Name = ""Lithuania"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""MY"", Name = ""Malaysia"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = ""MX"", Name = ""Mexico"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 4 } }, - new CountryModel { Code = ""MA"", Name = ""Morocco"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""NA"", Name = ""Namibia"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""NL"", Name = ""Netherlands"", Medals = new MedalsModel { Gold = 10, Silver = 12, Bronze = 14 } }, - new CountryModel { Code = ""NZ"", Name = ""New Zealand"", Medals = new MedalsModel { Gold = 7, Silver = 6, Bronze = 7 } }, - new CountryModel { Code = ""MK"", Name = ""North Macedonia"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""NO"", Name = ""Norway"", Medals = new MedalsModel { Gold = 4, Silver = 2, Bronze = 2 } }, - new CountryModel { Code = ""PH"", Name = ""Philippines"", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 1 } }, - new CountryModel { Code = ""PL"", Name = ""Poland"", Medals = new MedalsModel { Gold = 4, Silver = 5, Bronze = 5 } }, - new CountryModel { Code = ""PT"", Name = ""Portugal"", Medals = new MedalsModel { Gold = 1, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = ""PR"", Name = ""Puerto Rico"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 0 } }, - new CountryModel { Code = ""QA"", Name = ""Qatar"", Medals = new MedalsModel { Gold = 2, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""KR"", Name = ""Republic of Korea"", Medals = new MedalsModel { Gold = 6, Silver = 4, Bronze = 10 } }, - new CountryModel { Code = ""MD"", Name = ""Republic of Moldova"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""RO"", Name = ""Romania"", Medals = new MedalsModel { Gold = 1, Silver = 3, Bronze = 0 } }, - new CountryModel { Code = ""SM"", Name = ""San Marino"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 2 } }, - new CountryModel { Code = ""SA"", Name = ""Saudi Arabia"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""RS"", Name = ""Serbia"", Medals = new MedalsModel { Gold = 3, Silver = 1, Bronze = 5 } }, - new CountryModel { Code = ""SK"", Name = ""Slovakia"", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 1 } }, - new CountryModel { Code = ""SI"", Name = ""Slovenia"", Medals = new MedalsModel { Gold = 3, Silver = 1, Bronze = 1 } }, - new CountryModel { Code = ""ZA"", Name = ""South Africa"", Medals = new MedalsModel { Gold = 1, Silver = 2, Bronze = 0 } }, - new CountryModel { Code = ""ES"", Name = ""Spain"", Medals = new MedalsModel { Gold = 3, Silver = 8, Bronze = 6 } }, - new CountryModel { Code = ""SE"", Name = ""Sweden"", Medals = new MedalsModel { Gold = 3, Silver = 6, Bronze = 0 } }, - new CountryModel { Code = ""CH"", Name = ""Switzerland"", Medals = new MedalsModel { Gold = 3, Silver = 4, Bronze = 6 } }, - new CountryModel { Code = ""SY"", Name = ""Syrian Arab Republic"", Medals = new MedalsModel { Gold = 0, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""TH"", Name = ""Thailand"", Medals = new MedalsModel { Gold = 1, Silver = 0, Bronze = 1 } }, - new CountryModel { Code = ""TR"", Name = ""Turkey"", Medals = new MedalsModel { Gold = 2, Silver = 2, Bronze = 9 } }, - new CountryModel { Code = ""TM"", Name = ""Turkmenistan"", Medals = new MedalsModel { Gold = 0, Silver = 1, Bronze = 0 } }, - new CountryModel { Code = ""UA"", Name = ""Ukraine"", Medals = new MedalsModel { Gold = 1, Silver = 6, Bronze = 12 } }, - new CountryModel { Code = ""US"", Name = ""United States of America"", Medals = new MedalsModel { Gold = 39, Silver = 41, Bronze = 33 } }, - new CountryModel { Code = ""UZ"", Name = ""Uzbekistan"", Medals = new MedalsModel { Gold = 3, Silver = 0, Bronze = 2 } }, - new CountryModel { Code = ""VE"", Name = ""Venezuela"", Medals = new MedalsModel { Gold = 1, Silver = 3, Bronze = 0 } }, -]; - -public class CountryModel -{ - public string Code { get; set; } - public string Name { get; set; } - public MedalsModel Medals { get; set; } -} - -public class MedalsModel -{ - public int Gold { get; set; } - public int Silver { get; set; } - public int Bronze { get; set; } - public int Total => Gold + Silver + Bronze; -}"; -} diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss deleted file mode 100644 index 83e80b6df1..0000000000 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/BitQuickGridDemo.razor.scss +++ /dev/null @@ -1,131 +0,0 @@ -.custom-grid { - border: 1px solid; - - .container { - overflow: auto; - } - - .flag { - vertical-align: middle; - } - - ::deep { - table { - border-spacing: 0; - } - - tr { - height: 42px; - - &:nth-child(even) { - background: var(--bit-clr-bg-sec); - } - - &:nth-child(odd) { - background: var(--bit-clr-bg-pri); - } - } - - th { - border-bottom: 1px solid; - background-color: var(--bit-clr-bg-sec); - border-bottom-color: var(--bit-clr-brd-sec); - - &:not(:last-child) { - border-right: 1px solid; - border-right-color: var(--bit-clr-brd-sec); - } - } - - td { - border-bottom: 1px solid var(--bit-clr-brd-sec); - - &:not(:last-child) { - border-right: 1px solid var(--bit-clr-brd-sec); - } - } - - .bit-qkg-drg::after { - border-left: unset; - } - - .wide { - width: 220px; - } - } -} - -.grid { - height: 15rem; - overflow-y: auto; - - ::deep { - thead { - top: 0; - z-index: 1; - position: sticky; - background-color: var(--bit-clr-bg-sec); - } - - tr { - height: 2rem; - } - - td { - max-width: 0; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - } -} - -.search-panel { - max-width: 15rem; - margin-top: 2rem; -} - -.responsive-grid { - ::deep { - table { - border-collapse: collapse; - } - - tr { - padding: 1rem; - margin-bottom: 1rem; - border: 1px solid var(--bit-clr-brd-sec); - } - - @media (max-width: 600px) { - table { - width: 100%; - } - - table, thead, tbody, th, td, tr, td { - display: block; - } - - thead tr { - display: none; - } - - td::before { - font-weight: bold; - content: attr(data-title) " : "; - } - } - } -} - -.row-template-grid { - ::deep { - .row-template-expand-col { - width: 2rem; - } - - .row-template-detail { - padding: 0.5rem; - } - } -} diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/CountryModel.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/CountryModel.cs deleted file mode 100644 index 00c8a9e187..0000000000 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/CountryModel.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Extras.QuickGrid; - -public class CountryModel -{ - public string Code { get; set; } = default!; - public string Name { get; set; } = default!; - public MedalsModel Medals { get; set; } = default!; -} diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/MedalsModel.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/MedalsModel.cs deleted file mode 100644 index df4afcb371..0000000000 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/QuickGrid/MedalsModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Extras.QuickGrid; - -public class MedalsModel -{ - public int Gold { get; set; } - public int Silver { get; set; } - public int Bronze { get; set; } - - public int Total => Gold + Silver + Bronze; -} diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/Checkbox/BitCheckboxDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/Checkbox/BitCheckboxDemo.razor index 1ef0a6dd69..d78404f0cd 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/Checkbox/BitCheckboxDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/Checkbox/BitCheckboxDemo.razor @@ -448,6 +448,116 @@
+

+
Disabled:
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+

+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/Checkbox/BitCheckboxDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/Checkbox/BitCheckboxDemo.razor.samples.cs index 3c607a8524..52a0d9d69a 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/Checkbox/BitCheckboxDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/Checkbox/BitCheckboxDemo.razor.samples.cs @@ -298,7 +298,78 @@ private async Task HandleValidSubmit() { } -"; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + +"; private readonly string example15RazorCode = @" diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupCustomDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupCustomDemo.razor index 818df6054f..bb9d21a746 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupCustomDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupCustomDemo.razor @@ -483,9 +483,184 @@
+

+
Disabled:
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
- + +
Use icons from external libraries like FontAwesome or Bootstrap Icons with the Icon parameter and NameSelectors.

diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupCustomDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupCustomDemo.razor.samples.cs index f7a5e2e121..0c643917e9 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupCustomDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupCustomDemo.razor.samples.cs @@ -1,4 +1,4 @@ -namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Inputs.ChoiceGroup; +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Inputs.ChoiceGroup; public partial class _BitChoiceGroupCustomDemo { @@ -767,6 +767,144 @@ public class Order Horizontal Items=""basicCustoms"" DefaultValue=""basicCustoms[1].ItemValue"" + NameSelectors=""@(new() { Text = { Name = nameof(Order.Name) }, Value = { Name = nameof(Order.ItemValue) } })"" /> + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + + + + + + + +"; private readonly string example11CsharpCode = @" public class Order diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupItemDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupItemDemo.razor index 7b2beaea3f..7664e93515 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupItemDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupItemDemo.razor @@ -362,8 +362,166 @@
+

+
Disabled:
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
Use icons from external libraries like FontAwesome or Bootstrap Icons with the Icon parameter.
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupItemDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupItemDemo.razor.samples.cs index fd446cc780..a4c66ebc1c 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupItemDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupItemDemo.razor.samples.cs @@ -1,4 +1,4 @@ -namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Inputs.ChoiceGroup; +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Inputs.ChoiceGroup; public partial class _BitChoiceGroupItemDemo { @@ -556,7 +556,128 @@ Horizontal Inline /> Label=""TertiaryBorder"" Horizontal Items=""basicItems"" - DefaultValue=""basicItems[1].Value"" />"; + DefaultValue=""basicItems[1].Value"" /> + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + + + + + + + +"; private readonly string example11CsharpCode = @" private readonly List> basicItems = [ diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupOptionDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupOptionDemo.razor.samples.cs index a4ae96f13c..e4a69d15cd 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupOptionDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/ChoiceGroup/_BitChoiceGroupOptionDemo.razor.samples.cs @@ -523,6 +523,127 @@ private void HandleValidSubmit() { } + + +"" TValue=""string"" Horizontal> + + + + + + +"" TValue=""string"" Horizontal> + + + + + + +"" TValue=""string"" Horizontal> + + + + + + +"" TValue=""string"" Horizontal> + + + + + + +"" TValue=""string"" Horizontal> + + + + + + +"" TValue=""string"" Horizontal> + + + + + + +"" TValue=""string"" Horizontal> + + + + + + +"" TValue=""string"" Horizontal> + + + + + + +
+ "" TValue=""string"" Horizontal> + + + + + + + "" TValue=""string"" Horizontal> + + + + + + + "" TValue=""string"" Horizontal> + + + + + +
+ +"" TValue=""string"" Horizontal> + + + + + + +"" TValue=""string"" Horizontal> + + + + + + +"" TValue=""string"" Horizontal> + + + + + + +"" TValue=""string"" Horizontal> + + + + + + +"" TValue=""string"" Horizontal> + + + + + + +"" TValue=""string"" Horizontal> + + + + "; private readonly string example12RazorCode = @" diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/SearchBox/BitSearchBoxDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/SearchBox/BitSearchBoxDemo.razor index 3f9e060339..7b2ced96c3 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/SearchBox/BitSearchBoxDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/SearchBox/BitSearchBoxDemo.razor @@ -281,17 +281,19 @@



- -
- -


- -
- -


- -
- +
+ +
+ +


+ +
+ +


+ +
+ +




@@ -316,6 +318,78 @@
+

+
Disabled:
+
+ +
+ +

+ +
+ +

+ +
+ +

+ +
+ +

+ +
+ +

+ +
+ +

+ +
+ +

+ +
+ +

+
+ +
+ +

+ +
+ +

+ +
+ +
+

+ +
+ +

+ +
+ +

+ +
+ +

+ +
+ +

+ +
+ +

+ +
+
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/SearchBox/BitSearchBoxDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/SearchBox/BitSearchBoxDemo.razor.samples.cs index 29b17d7f8a..80cfcd81f6 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/SearchBox/BitSearchBoxDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/SearchBox/BitSearchBoxDemo.razor.samples.cs @@ -238,8 +238,112 @@ public class ValidationSearchBoxModel private ValidationSearchBoxModel validationBoxModel = new();"; private readonly string example12RazorCode = @" - -"; + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +"; private readonly string example13RazorCode = @" diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/BitChartLegacyDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/BitChartLegacyDemo.razor index fe01129ea6..ea5d83ee84 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/BitChartLegacyDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/BitChartLegacyDemo.razor @@ -1,4 +1,3 @@ -@namespace Bit.BlazorUI.Legacy.Demo.Chart @page "/components/legacy/chart"
A bar chart provides a way of showing data values represented as vertical bars. @@ -6,7 +5,7 @@
- +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyBarDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyBarDemo.razor.cs index ef066708ad..3f63528f85 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyBarDemo.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyBarDemo.razor.cs @@ -1,5 +1,4 @@ -using Bit.BlazorUI.Legacy; -namespace Bit.BlazorUI.Legacy.Demo.Chart; +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Legacy.Chart; public partial class _BitChartLegacyBarDemo { diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyBubbleDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyBubbleDemo.razor index 1cc23eb811..7a57ab1942 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyBubbleDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyBubbleDemo.razor @@ -1,4 +1,3 @@ -@namespace Bit.BlazorUI.Legacy.Demo.Chart
A bubble chart is used to display three dimensions of data at the same time. @@ -6,7 +5,7 @@
- +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyBubbleDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyBubbleDemo.razor.cs index f82631ff3e..96de124611 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyBubbleDemo.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyBubbleDemo.razor.cs @@ -1,5 +1,4 @@ -using Bit.BlazorUI.Legacy; -namespace Bit.BlazorUI.Legacy.Demo.Chart; +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Legacy.Chart; public partial class _BitChartLegacyBubbleDemo { diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyDoughnutDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyDoughnutDemo.razor index 2b328cf39f..c9287566ed 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyDoughnutDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyDoughnutDemo.razor @@ -1,4 +1,3 @@ -@namespace Bit.BlazorUI.Legacy.Demo.Chart
The doughnut chart is the same as the Pie chart. @@ -6,7 +5,7 @@
- +
\ No newline at end of file diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyDoughnutDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyDoughnutDemo.razor.cs index 4b78bfa79c..aae14417b4 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyDoughnutDemo.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyDoughnutDemo.razor.cs @@ -1,5 +1,4 @@ -using Bit.BlazorUI.Legacy; -namespace Bit.BlazorUI.Legacy.Demo.Chart; +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Legacy.Chart; public partial class _BitChartLegacyDoughnutDemo { diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyHorizontalBarDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyHorizontalBarDemo.razor index 62ab39eb3b..f2693e32de 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyHorizontalBarDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyHorizontalBarDemo.razor @@ -1,4 +1,3 @@ -@namespace Bit.BlazorUI.Legacy.Demo.Chart
A horizontal bar chart is a variation on a vertical bar chart. It is sometimes used to show trend data, @@ -7,7 +6,7 @@
- +
\ No newline at end of file diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyHorizontalBarDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyHorizontalBarDemo.razor.cs index 2854f1f0c6..6d35a44ed1 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyHorizontalBarDemo.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyHorizontalBarDemo.razor.cs @@ -1,5 +1,4 @@ -using Bit.BlazorUI.Legacy; -namespace Bit.BlazorUI.Legacy.Demo.Chart; +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Legacy.Chart; public partial class _BitChartLegacyHorizontalBarDemo { diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyLineDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyLineDemo.razor index 0b533de463..b74c8853a3 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyLineDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyLineDemo.razor @@ -1,11 +1,10 @@ -@namespace Bit.BlazorUI.Legacy.Demo.Chart
A line chart is a way of plotting data points on a line. Often, it is used to show trend data, or the comparison of two data sets.
- +
\ No newline at end of file diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyLineDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyLineDemo.razor.cs index 8284300fa7..2b4e4dadcf 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyLineDemo.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyLineDemo.razor.cs @@ -1,5 +1,4 @@ -using Bit.BlazorUI.Legacy; -namespace Bit.BlazorUI.Legacy.Demo.Chart; +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Legacy.Chart; public partial class _BitChartLegacyLineDemo { diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyPieDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyPieDemo.razor index 60765b5c9a..925e4793f2 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyPieDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyPieDemo.razor @@ -1,4 +1,3 @@ -@namespace Bit.BlazorUI.Legacy.Demo.Chart
Pie and doughnut charts are probably the most commonly used charts. @@ -7,7 +6,7 @@
- +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyPieDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyPieDemo.razor.cs index 307ecd4c53..96c3c4bad9 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyPieDemo.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyPieDemo.razor.cs @@ -1,5 +1,4 @@ -using Bit.BlazorUI.Legacy; -namespace Bit.BlazorUI.Legacy.Demo.Chart; +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Legacy.Chart; public partial class _BitChartLegacyPieDemo { diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyPolarAreaDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyPolarAreaDemo.razor index d545eba1e1..5b612d11ea 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyPolarAreaDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyPolarAreaDemo.razor @@ -1,4 +1,3 @@ -@namespace Bit.BlazorUI.Legacy.Demo.Chart
Polar area charts are similar to pie charts, but each segment has the same angle - the radius of the segment differs depending on the value. @@ -6,7 +5,7 @@
- +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyPolarAreaDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyPolarAreaDemo.razor.cs index eada54b171..deed9e4fee 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyPolarAreaDemo.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyPolarAreaDemo.razor.cs @@ -1,5 +1,4 @@ -using Bit.BlazorUI.Legacy; -namespace Bit.BlazorUI.Legacy.Demo.Chart; +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Legacy.Chart; public partial class _BitChartLegacyPolarAreaDemo { diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyRadarDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyRadarDemo.razor index f18599c619..f039ca0f00 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyRadarDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyRadarDemo.razor @@ -1,4 +1,3 @@ -@namespace Bit.BlazorUI.Legacy.Demo.Chart
A radar chart is a way of showing multiple data points and the variation between them. @@ -6,7 +5,7 @@
- +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyRadarDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyRadarDemo.razor.cs index 7d074632f8..63f0a647a1 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyRadarDemo.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyRadarDemo.razor.cs @@ -1,5 +1,4 @@ -using Bit.BlazorUI.Legacy; -namespace Bit.BlazorUI.Legacy.Demo.Chart; +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Legacy.Chart; public partial class _BitChartLegacyRadarDemo { diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyScatterDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyScatterDemo.razor index 03c53aa21d..9182341f15 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyScatterDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyScatterDemo.razor @@ -1,4 +1,3 @@ -@namespace Bit.BlazorUI.Legacy.Demo.Chart
Scatter charts are based on basic line charts with the x-axis changed to a linear axis. @@ -6,7 +5,7 @@
- +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyScatterDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyScatterDemo.razor.cs index 8bb1ca50ec..cc33c13d0f 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyScatterDemo.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyScatterDemo.razor.cs @@ -1,5 +1,4 @@ -using Bit.BlazorUI.Legacy; -namespace Bit.BlazorUI.Legacy.Demo.Chart; +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Legacy.Chart; public partial class _BitChartLegacyScatterDemo { diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyStackedBarDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyStackedBarDemo.razor index 445644a672..ad95fc6e76 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyStackedBarDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyStackedBarDemo.razor @@ -1,4 +1,3 @@ -@namespace Bit.BlazorUI.Legacy.Demo.Chart
Bar charts can be configured into stacked bar charts by changing the settings on the X and Y axes to enable stacking. @@ -6,7 +5,7 @@
- +
\ No newline at end of file diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyStackedBarDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyStackedBarDemo.razor.cs index e99670bc1f..6baa1a9008 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyStackedBarDemo.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyStackedBarDemo.razor.cs @@ -1,5 +1,4 @@ -using Bit.BlazorUI.Legacy; -namespace Bit.BlazorUI.Legacy.Demo.Chart; +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Legacy.Chart; public partial class _BitChartLegacyStackedBarDemo { diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyTimeDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyTimeDemo.razor index 1eb38fad4b..12dbb84cd4 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyTimeDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyTimeDemo.razor @@ -1,4 +1,3 @@ -@namespace Bit.BlazorUI.Legacy.Demo.Chart
The time scale is used to display times and dates. @@ -6,7 +5,7 @@
- +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyTimeDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyTimeDemo.razor.cs index dd957c423d..b80bc29dea 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyTimeDemo.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/Chart/_BitChartLegacyTimeDemo.razor.cs @@ -1,5 +1,4 @@ -using Bit.BlazorUI.Legacy; -namespace Bit.BlazorUI.Legacy.Demo.Chart; +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Legacy.Chart; public partial class _BitChartLegacyTimeDemo { diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/BitDataGridLegacyDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/BitDataGridLegacyDemo.razor index a14bd19316..5ed9c92d33 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/BitDataGridLegacyDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/BitDataGridLegacyDemo.razor @@ -1,4 +1,3 @@ -@namespace Bit.BlazorUI.Legacy.Demo.DataGrid @page "/components/legacy/datagrid" @inherits AppComponentBase @using Bit.BlazorUI.Demo.Shared.Dtos.QuickGridDemo @@ -28,8 +27,8 @@ - - + + - - - - - - - + + + + + + +
- - + + - - + + @(context.Code) - - - - - + + + + + - - + +
- + @(state.CurrentPageIndex + 1) / @(state.LastPageIndex + 1) - +
- - - - - - - - + + + + + + + +
- - - - - + + + + +
- + - - - + + + @@ -128,11 +127,11 @@ - +
- + @(state.CurrentPageIndex + 1) / @(state.LastPageIndex + 1) - + @@ -144,14 +143,14 @@
- - - - - - - - + + + + + + + +
@@ -161,20 +160,20 @@
- + - + - - - - - - + + + + + + @args.OriginalRow @@ -194,8 +193,8 @@ } - - + +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/BitDataGridLegacyDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/BitDataGridLegacyDemo.razor.cs index 5e836f9033..bee0ed63a0 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/BitDataGridLegacyDemo.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/BitDataGridLegacyDemo.razor.cs @@ -1,7 +1,7 @@ using Bit.BlazorUI.Demo.Client.Core.Components; using Bit.BlazorUI.Demo.Shared.Dtos.QuickGridDemo; -namespace Bit.BlazorUI.Legacy.Demo.DataGrid; +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Legacy.DataGrid; public partial class BitDataGridLegacyDemo : AppComponentBase { diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/BitDataGridLegacyDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/BitDataGridLegacyDemo.razor.samples.cs index fb408ecb0c..cc840bf1f3 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/BitDataGridLegacyDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/BitDataGridLegacyDemo.razor.samples.cs @@ -1,6 +1,6 @@ using Bit.BlazorUI.Demo.Client.Core.Components; -namespace Bit.BlazorUI.Legacy.Demo.DataGrid; +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Legacy.DataGrid; public partial class BitDataGridLegacyDemo : AppComponentBase { diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/CountryModel.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/CountryModel.cs index 8d62796c64..f0f3cc023d 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/CountryModel.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/CountryModel.cs @@ -1,4 +1,4 @@ -namespace Bit.BlazorUI.Legacy.Demo.DataGrid; +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Legacy.DataGrid; public class CountryModel { diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/MedalsModel.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/MedalsModel.cs index c45ab7cfb9..f5d45ed7dd 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/MedalsModel.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/DataGrid/MedalsModel.cs @@ -1,4 +1,4 @@ -namespace Bit.BlazorUI.Legacy.Demo.DataGrid; +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Legacy.DataGrid; public class MedalsModel { diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/MarkdownEditor/BitMarkdownEditorLegacyDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/MarkdownEditor/BitMarkdownEditorLegacyDemo.razor index 1cb3a8d5f4..a45f549412 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/MarkdownEditor/BitMarkdownEditorLegacyDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Legacy/MarkdownEditor/BitMarkdownEditorLegacyDemo.razor @@ -1,4 +1,3 @@ -@namespace Bit.BlazorUI.Legacy.Demo.MarkdownEditor @page "/components/legacy/markdowneditor"
- - + +
@@ -45,7 +44,7 @@

- +
@@ -56,7 +55,7 @@

- + =>
                     @value
@@ -67,7 +66,7 @@
         
             
By utilizing the OnChange and DefaultValue parameters you can have another way to access the content of the editor.

- +
                     @onChangeValue
                 
@@ -81,7 +80,7 @@

- +