Tighten tables layout and grid behavior

This commit is contained in:
2026-04-11 23:45:09 +02:00
parent c892a6d07a
commit 6719967907
9 changed files with 221 additions and 419 deletions

View File

@@ -74,6 +74,7 @@ It is intentionally implementation-focused:
| 2026-03-21 | P3.11 | Completed | Moved the live editor and curation entry points into the shared inspector content and removed the last remaining grid-owned action buttons. | | 2026-03-21 | P3.11 | Completed | Moved the live editor and curation entry points into the shared inspector content and removed the last remaining grid-owned action buttons. |
| 2026-03-21 | P3.12 | Completed | Added keyboard-selectable cells, visible focus treatment, and selection normalization so changing filters or modes cannot leave the inspector pointing at hidden cells. | | 2026-03-21 | P3.12 | Completed | Added keyboard-selectable cells, visible focus treatment, and selection normalization so changing filters or modes cannot leave the inspector pointing at hidden cells. |
| 2026-03-21 | Post-P3 fix 1 | Completed | Added a defensive visible-column fallback in the table canvas and tightened view-state normalization so a stale severity filter cannot collapse the grid to roll bands only. | | 2026-03-21 | Post-P3 fix 1 | Completed | Added a defensive visible-column fallback in the table canvas and tightened view-state normalization so a stale severity filter cannot collapse the grid to roll bands only. |
| 2026-04-11 | Post-P3 fix 2 | Completed | Simplified `/tables` by removing static prose and context controls, dropped the redundant selected-result inspector in favor of a floating action menu, and moved the canvas onto its own scroll region so sticky headers layer correctly beneath the context bar. |
### Lessons Learned ### Lessons Learned

View File

@@ -11,7 +11,7 @@
<PageTitle>Critical Tables</PageTitle> <PageTitle>Critical Tables</PageTitle>
<section class="panel tables-page"> <section class="panel tables-page">
<TablesPageHeader /> <TablesPageHeader/>
@if (referenceData is null) @if (referenceData is null)
{ {
@@ -31,7 +31,7 @@
PinnedTableSlugs="PinnedTablesState.Items.Select(item => item.Slug).ToArray()" PinnedTableSlugs="PinnedTablesState.Items.Select(item => item.Slug).ToArray()"
RecentTableSlugs="RecentTablesState.Items.Select(item => item.Slug).ToArray()" RecentTableSlugs="RecentTablesState.Items.Select(item => item.Slug).ToArray()"
IsPinned="PinnedTablesState.IsPinned" IsPinned="PinnedTablesState.IsPinned"
OnSelectTable="SelectTableAsync" /> OnSelectTable="SelectTableAsync"/>
</aside> </aside>
<div class="tables-reference-main"> <div class="tables-reference-main">
@@ -57,19 +57,9 @@
<TablesContextBar <TablesContextBar
Detail="detail" Detail="detail"
IsPinned="PinnedTablesState.IsPinned(detail.Slug)" IsPinned="PinnedTablesState.IsPinned(detail.Slug)"
CurrentMode="referenceMode"
SelectedGroupKey="selectedGroupKey"
SelectedColumnKey="selectedColumnKey"
RollJumpValue="rollJumpValue"
DensityMode="densityMode"
OnTogglePin="TogglePinnedTableAsync" OnTogglePin="TogglePinnedTableAsync"
IsLegendOpen="isLegendOpen" IsLegendOpen="isLegendOpen"
OnToggleLegend="ToggleLegend" OnToggleLegend="ToggleLegend"/>
OnModeChanged="UpdateReferenceModeAsync"
OnGroupChanged="UpdateSelectedGroupAsync"
OnColumnChanged="UpdateSelectedColumnAsync"
OnRollJumpChanged="UpdateRollJumpAsync"
OnDensityChanged="UpdateDensityModeAsync" />
<TablesCanvas <TablesCanvas
Detail="detail" Detail="detail"
@@ -79,32 +69,21 @@
RollJumpValue="rollJumpValue" RollJumpValue="rollJumpValue"
DensityMode="densityMode" DensityMode="densityMode"
SelectedCell="selectedCell" SelectedCell="selectedCell"
OnSelectCell="SelectCell" /> OnSelectCell="SelectCell"/>
@if (isLegendOpen) @if (isLegendOpen)
{ {
<TablesLegend LegendEntries="@(detail.Legend ?? Array.Empty<CriticalTableLegendEntry>())" /> <TablesLegend LegendEntries="@(detail.Legend ?? Array.Empty<CriticalTableLegendEntry>())"/>
} }
</div> </div>
} }
</div> </div>
@if (tableDetail is not null)
{
<aside class="tables-reference-inspector-shell">
<TablesInspector
SelectedCellDetail="SelectedCellDetail"
OnEdit="OpenSelectedCellEditorAsync"
OnCurate="OpenSelectedCellCurationAsync" />
</aside>
}
</div> </div>
<TablesInspectorSheet <TablesSelectionMenu
SelectedCellDetail="SelectedCellDetail" SelectedCellDetail="SelectedCellDetail"
OnClose="ClearSelectedCell"
OnEdit="OpenSelectedCellEditorAsync" OnEdit="OpenSelectedCellEditorAsync"
OnCurate="OpenSelectedCellCurationAsync" /> OnCurate="OpenSelectedCellCurationAsync"/>
} }
</section> </section>
@@ -125,7 +104,7 @@
OnEdit="OpenEditorFromCurationAsync" OnEdit="OpenEditorFromCurationAsync"
OnEnterQuickParse="EnterCurationQuickParseMode" OnEnterQuickParse="EnterCurationQuickParseMode"
OnCancelQuickParse="CancelCurationQuickParseMode" OnCancelQuickParse="CancelCurationQuickParseMode"
OnReparse="ReparseCurationCellAsync" /> OnReparse="ReparseCurationCellAsync"/>
} }
@if (isEditorOpen) @if (isEditorOpen)
@@ -142,7 +121,7 @@
SaveErrorMessage="@editorSaveError" SaveErrorMessage="@editorSaveError"
OnClose="CloseCellEditorAsync" OnClose="CloseCellEditorAsync"
OnReparse="ReparseCellEditorAsync" OnReparse="ReparseCellEditorAsync"
OnSave="SaveCellEditorAsync" /> OnSave="SaveCellEditorAsync"/>
} }
@code { @code {
@@ -151,9 +130,7 @@
private CriticalTableDetail? tableDetail; private CriticalTableDetail? tableDetail;
private string selectedTableSlug = string.Empty; private string selectedTableSlug = string.Empty;
private bool isDetailLoading; private bool isDetailLoading;
private bool isReferenceDataLoading = true;
private string? detailError; private string? detailError;
private bool IsTableSelectionDisabled => isReferenceDataLoading || (referenceData?.CriticalTables.Count ?? 0) == 0;
private bool isEditorOpen; private bool isEditorOpen;
private bool isEditorLoading; private bool isEditorLoading;
private bool isEditorReparsing; private bool isEditorReparsing;
@@ -181,17 +158,16 @@
private TablesCellSelection? selectedCell; private TablesCellSelection? selectedCell;
private bool isLegendOpen; private bool isLegendOpen;
private bool hasResolvedStoredTableSelection; private bool hasResolvedStoredTableSelection;
private CriticalTableReference? SelectedTableReference => private CriticalTableReference? SelectedTableReference =>
referenceData?.CriticalTables.FirstOrDefault(item => string.Equals(item.Key, selectedTableSlug, StringComparison.OrdinalIgnoreCase)); referenceData?.CriticalTables.FirstOrDefault(item => string.Equals(item.Key, selectedTableSlug, StringComparison.OrdinalIgnoreCase));
private CriticalTableCellDetail? SelectedCellDetail => private CriticalTableCellDetail? SelectedCellDetail =>
selectedCell is null selectedCell is null ? null : tableDetail?.Cells.FirstOrDefault(cell => cell.ResultId == selectedCell.ResultId);
? null
: tableDetail?.Cells.FirstOrDefault(cell => cell.ResultId == selectedCell.ResultId);
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
referenceData = await LookupService.GetReferenceDataAsync(); referenceData = await LookupService.GetReferenceDataAsync();
isReferenceDataLoading = false;
} }
private async Task SelectTableAsync(string tableSlug) private async Task SelectTableAsync(string tableSlug)
@@ -247,17 +223,12 @@
if (!hasResolvedStoredTableSelection && referenceData?.CriticalTables.Count > 0) if (!hasResolvedStoredTableSelection && referenceData?.CriticalTables.Count > 0)
{ {
var initialContext = await TableContextState.RestoreAsync( var initialContext = await TableContextState.RestoreAsync(NavigationManager.Uri, ContextDestination, referenceData.CriticalTables, RolemasterDb.App.Frontend.AppState.TableContextMode.Reference);
NavigationManager.Uri,
ContextDestination,
referenceData.CriticalTables,
RolemasterDb.App.Frontend.AppState.TableContextMode.Reference);
hasResolvedStoredTableSelection = true; hasResolvedStoredTableSelection = true;
var resolvedTableSlug = initialContext.TableSlug ?? string.Empty; var resolvedTableSlug = initialContext.TableSlug ?? string.Empty;
if (string.IsNullOrWhiteSpace(selectedTableSlug) || if (string.IsNullOrWhiteSpace(selectedTableSlug) || !string.Equals(resolvedTableSlug, selectedTableSlug, StringComparison.OrdinalIgnoreCase))
!string.Equals(resolvedTableSlug, selectedTableSlug, StringComparison.OrdinalIgnoreCase))
{ {
selectedTableSlug = resolvedTableSlug; selectedTableSlug = resolvedTableSlug;
await LoadTableDetailAsync(); await LoadTableDetailAsync();
@@ -489,11 +460,7 @@
return null; return null;
} }
var orderedCells = tableDetail.Cells var orderedCells = tableDetail.Cells.OrderBy(cell => GetGroupSortOrder(cell.GroupKey)).ThenBy(cell => GetColumnSortOrder(cell.ColumnKey)).ThenBy(cell => GetRollBandSortOrder(cell.RollBand)).ToList();
.OrderBy(cell => GetGroupSortOrder(cell.GroupKey))
.ThenBy(cell => GetColumnSortOrder(cell.ColumnKey))
.ThenBy(cell => GetRollBandSortOrder(cell.RollBand))
.ToList();
var currentIndex = orderedCells.FindIndex(cell => cell.ResultId == currentResultId); var currentIndex = orderedCells.FindIndex(cell => cell.ResultId == currentResultId);
for (var index = currentIndex + 1; index < orderedCells.Count; index++) for (var index = currentIndex + 1; index < orderedCells.Count; index++)
@@ -514,9 +481,7 @@
return 0; return 0;
} }
return tableDetail.Groups return tableDetail.Groups.FirstOrDefault(group => string.Equals(group.Key, groupKey, StringComparison.OrdinalIgnoreCase))?.SortOrder ?? int.MaxValue;
.FirstOrDefault(group => string.Equals(group.Key, groupKey, StringComparison.OrdinalIgnoreCase))
?.SortOrder ?? int.MaxValue;
} }
private int GetColumnSortOrder(string columnKey) private int GetColumnSortOrder(string columnKey)
@@ -526,9 +491,7 @@
return int.MaxValue; return int.MaxValue;
} }
return tableDetail.Columns return tableDetail.Columns.FirstOrDefault(column => string.Equals(column.Key, columnKey, StringComparison.OrdinalIgnoreCase))?.SortOrder ?? int.MaxValue;
.FirstOrDefault(column => string.Equals(column.Key, columnKey, StringComparison.OrdinalIgnoreCase))
?.SortOrder ?? int.MaxValue;
} }
private int GetRollBandSortOrder(string rollBandLabel) private int GetRollBandSortOrder(string rollBandLabel)
@@ -538,9 +501,7 @@
return int.MaxValue; return int.MaxValue;
} }
return tableDetail.RollBands return tableDetail.RollBands.FirstOrDefault(rollBand => string.Equals(rollBand.Label, rollBandLabel, StringComparison.OrdinalIgnoreCase))?.SortOrder ?? int.MaxValue;
.FirstOrDefault(rollBand => string.Equals(rollBand.Label, rollBandLabel, StringComparison.OrdinalIgnoreCase))
?.SortOrder ?? int.MaxValue;
} }
private async Task CloseCellEditorAsync() private async Task CloseCellEditorAsync()
@@ -634,11 +595,7 @@
return Task.CompletedTask; return Task.CompletedTask;
} }
return PinnedTablesState.ToggleAsync( return PinnedTablesState.ToggleAsync(selectedTable.Key, selectedTable.Label, selectedTable.Family, selectedTable.CurationPercentage);
selectedTable.Key,
selectedTable.Label,
selectedTable.Family,
selectedTable.CurationPercentage);
} }
private Task RecordRecentTableVisitAsync() private Task RecordRecentTableVisitAsync()
@@ -648,11 +605,7 @@
return Task.CompletedTask; return Task.CompletedTask;
} }
return RecentTablesState.RecordVisitAsync( return RecentTablesState.RecordVisitAsync(selectedTable.Key, selectedTable.Label, selectedTable.Family, selectedTable.CurationPercentage);
selectedTable.Key,
selectedTable.Label,
selectedTable.Family,
selectedTable.CurationPercentage);
} }
private async Task PersistAndSyncTableContextAsync() private async Task PersistAndSyncTableContextAsync()
@@ -670,42 +623,7 @@
} }
private RolemasterDb.App.Frontend.AppState.TableContextSnapshot BuildCurrentTableContext() => private RolemasterDb.App.Frontend.AppState.TableContextSnapshot BuildCurrentTableContext() =>
new( new(TableSlug: selectedTableSlug, Mode: RolemasterDb.App.Frontend.AppState.TableContextMode.Reference);
TableSlug: selectedTableSlug,
Mode: RolemasterDb.App.Frontend.AppState.TableContextMode.Reference);
private Task UpdateReferenceModeAsync(string mode)
{
referenceMode = NormalizeMode(mode);
NormalizeSelectedCellForCurrentView();
return Task.CompletedTask;
}
private Task UpdateSelectedGroupAsync(string groupKey)
{
selectedGroupKey = NormalizeOptionalFilter(groupKey);
NormalizeSelectedCellForCurrentView();
return Task.CompletedTask;
}
private Task UpdateSelectedColumnAsync(string columnKey)
{
selectedColumnKey = NormalizeOptionalFilter(columnKey);
NormalizeSelectedCellForCurrentView();
return Task.CompletedTask;
}
private Task UpdateRollJumpAsync(string rollValue)
{
rollJumpValue = NormalizeRollInput(rollValue);
return Task.CompletedTask;
}
private Task UpdateDensityModeAsync(string mode)
{
densityMode = NormalizeDensityMode(mode);
return Task.CompletedTask;
}
private void SelectCell(TablesCellSelection selection) private void SelectCell(TablesCellSelection selection)
{ {
@@ -718,21 +636,11 @@
selectedCell = selection; selectedCell = selection;
} }
private Task ClearSelectedCell()
{
selectedCell = null;
return Task.CompletedTask;
}
private Task OpenSelectedCellEditorAsync() => private Task OpenSelectedCellEditorAsync() =>
selectedCell is null selectedCell is null ? Task.CompletedTask : OpenCellEditorAsync(selectedCell.ResultId);
? Task.CompletedTask
: OpenCellEditorAsync(selectedCell.ResultId);
private Task OpenSelectedCellCurationAsync() => private Task OpenSelectedCellCurationAsync() =>
selectedCell is null selectedCell is null ? Task.CompletedTask : OpenCellCurationAsync(selectedCell.ResultId);
? Task.CompletedTask
: OpenCellCurationAsync(selectedCell.ResultId);
private Task ToggleLegend() private Task ToggleLegend()
{ {
@@ -759,10 +667,7 @@
selectedGroupKey = string.Empty; selectedGroupKey = string.Empty;
} }
var matchingColumns = tableDetail.Columns var matchingColumns = tableDetail.Columns.Where(column => string.IsNullOrWhiteSpace(selectedColumnKey) || string.Equals(column.Key, selectedColumnKey, StringComparison.OrdinalIgnoreCase)).ToList();
.Where(column => string.IsNullOrWhiteSpace(selectedColumnKey)
|| string.Equals(column.Key, selectedColumnKey, StringComparison.OrdinalIgnoreCase))
.ToList();
if (matchingColumns.Count == 0) if (matchingColumns.Count == 0)
{ {
@@ -784,17 +689,12 @@
mode switch mode switch
{ {
TablesReferenceMode.NeedsCuration => TablesReferenceMode.NeedsCuration, TablesReferenceMode.NeedsCuration => TablesReferenceMode.NeedsCuration,
TablesReferenceMode.Curated => TablesReferenceMode.Curated, TablesReferenceMode.Curated => TablesReferenceMode.Curated,
_ => TablesReferenceMode.Reference _ => TablesReferenceMode.Reference
}; };
private static string NormalizeOptionalFilter(string? value) =>
string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim();
private static string NormalizeDensityMode(string? mode) => private static string NormalizeDensityMode(string? mode) =>
string.Equals(mode, TablesDensityMode.Dense, StringComparison.Ordinal) string.Equals(mode, TablesDensityMode.Dense, StringComparison.Ordinal) ? TablesDensityMode.Dense : TablesDensityMode.Comfortable;
? TablesDensityMode.Dense
: TablesDensityMode.Comfortable;
private static string NormalizeRollInput(string? value) private static string NormalizeRollInput(string? value)
{ {
@@ -823,14 +723,12 @@
private bool MatchesCurrentView(CriticalTableCellDetail cell) private bool MatchesCurrentView(CriticalTableCellDetail cell)
{ {
if (!string.IsNullOrWhiteSpace(selectedGroupKey) && if (!string.IsNullOrWhiteSpace(selectedGroupKey) && !string.Equals(cell.GroupKey, selectedGroupKey, StringComparison.OrdinalIgnoreCase))
!string.Equals(cell.GroupKey, selectedGroupKey, StringComparison.OrdinalIgnoreCase))
{ {
return false; return false;
} }
if (!string.IsNullOrWhiteSpace(selectedColumnKey) && if (!string.IsNullOrWhiteSpace(selectedColumnKey) && !string.Equals(cell.ColumnKey, selectedColumnKey, StringComparison.OrdinalIgnoreCase))
!string.Equals(cell.ColumnKey, selectedColumnKey, StringComparison.OrdinalIgnoreCase))
{ {
return false; return false;
} }
@@ -838,8 +736,9 @@
return referenceMode switch return referenceMode switch
{ {
TablesReferenceMode.NeedsCuration => !cell.IsCurated, TablesReferenceMode.NeedsCuration => !cell.IsCurated,
TablesReferenceMode.Curated => cell.IsCurated, TablesReferenceMode.Curated => cell.IsCurated,
_ => true _ => true
}; };
} }
} }

View File

@@ -44,7 +44,7 @@
<div class="critical-table-cell-actions"> <div class="critical-table-cell-actions">
@if (string.Equals(CurrentMode, TablesReferenceMode.Reference, StringComparison.Ordinal)) @if (string.Equals(CurrentMode, TablesReferenceMode.Reference, StringComparison.Ordinal))
{ {
<StatusIndicator Tone="@(cell.IsCurated ? "success" : "warning")" CssClass="tables-cell-status-indicator" /> <StatusIndicator Tone="@(cell.IsCurated ? "success" : "warning")" CssClass="tables-cell-status-indicator"/>
} }
else if (cell.IsCurated) else if (cell.IsCurated)
{ {
@@ -64,7 +64,7 @@
<CompactCriticalCell <CompactCriticalCell
Description="@(cell.Description ?? string.Empty)" Description="@(cell.Description ?? string.Empty)"
Effects="@(cell.Effects ?? Array.Empty<CriticalEffectLookupResponse>())" Effects="@(cell.Effects ?? Array.Empty<CriticalEffectLookupResponse>())"
Branches="@(cell.Branches ?? Array.Empty<CriticalBranchLookupResponse>())" /> Branches="@(cell.Branches ?? Array.Empty<CriticalBranchLookupResponse>())"/>
</div> </div>
</div> </div>
} }
@@ -170,66 +170,60 @@
cellIndex.TryGetValue((rollBand, groupKey, columnKey), out cell); cellIndex.TryGetValue((rollBand, groupKey, columnKey), out cell);
private bool MatchesGroupFilter(CriticalGroupReference group) => private bool MatchesGroupFilter(CriticalGroupReference group) =>
string.IsNullOrWhiteSpace(SelectedGroupKey) string.IsNullOrWhiteSpace(SelectedGroupKey) || string.Equals(group.Key, SelectedGroupKey, StringComparison.OrdinalIgnoreCase);
|| string.Equals(group.Key, SelectedGroupKey, StringComparison.OrdinalIgnoreCase);
private bool MatchesColumnFilter(CriticalColumnReference column) => private bool MatchesColumnFilter(CriticalColumnReference column) =>
string.IsNullOrWhiteSpace(SelectedColumnKey) string.IsNullOrWhiteSpace(SelectedColumnKey) || string.Equals(column.Key, SelectedColumnKey, StringComparison.OrdinalIgnoreCase);
|| string.Equals(column.Key, SelectedColumnKey, StringComparison.OrdinalIgnoreCase);
private IReadOnlyList<CriticalGroupReference> ResolveVisibleGroups() private IReadOnlyList<CriticalGroupReference> ResolveVisibleGroups()
{ {
var filteredGroups = Detail.Groups var filteredGroups = Detail.Groups.Where(MatchesGroupFilter).ToList();
.Where(MatchesGroupFilter)
.ToList();
return filteredGroups.Count > 0 return filteredGroups.Count > 0 ? filteredGroups : Detail.Groups;
? filteredGroups
: Detail.Groups;
} }
private IReadOnlyList<CriticalColumnReference> ResolveVisibleColumns() private IReadOnlyList<CriticalColumnReference> ResolveVisibleColumns()
{ {
var filteredColumns = Detail.Columns var filteredColumns = Detail.Columns.Where(MatchesColumnFilter).ToList();
.Where(MatchesColumnFilter)
.ToList();
return filteredColumns.Count > 0 return filteredColumns.Count > 0 ? filteredColumns : Detail.Columns;
? filteredColumns
: Detail.Columns;
} }
private bool MatchesModeFilter(CriticalTableCellDetail cell) => private bool MatchesModeFilter(CriticalTableCellDetail cell) =>
CurrentMode switch CurrentMode switch
{ {
TablesReferenceMode.NeedsCuration => !cell.IsCurated, TablesReferenceMode.NeedsCuration => !cell.IsCurated,
TablesReferenceMode.Curated => cell.IsCurated, TablesReferenceMode.Curated => cell.IsCurated,
_ => true _ => true
}; };
private string? ActiveRollBand => private string? ActiveRollBand =>
!string.IsNullOrWhiteSpace(SelectedCell?.RollBand) !string.IsNullOrWhiteSpace(SelectedCell?.RollBand) ? SelectedCell.RollBand : ResolveRollJumpBandLabel();
? SelectedCell.RollBand
: ResolveRollJumpBandLabel();
private string? ActiveColumnKey => private string? ActiveColumnKey =>
!string.IsNullOrWhiteSpace(SelectedCell?.ColumnKey) !string.IsNullOrWhiteSpace(SelectedCell?.ColumnKey) ? SelectedCell.ColumnKey : (!string.IsNullOrWhiteSpace(SelectedColumnKey) ? SelectedColumnKey : null);
? SelectedCell.ColumnKey
: (!string.IsNullOrWhiteSpace(SelectedColumnKey) ? SelectedColumnKey : null);
private string? ActiveGroupKey => private string? ActiveGroupKey =>
!string.IsNullOrWhiteSpace(SelectedCell?.GroupKey) !string.IsNullOrWhiteSpace(SelectedCell?.GroupKey) ? SelectedCell.GroupKey : (!string.IsNullOrWhiteSpace(SelectedGroupKey) ? SelectedGroupKey : null);
? SelectedCell.GroupKey
: (!string.IsNullOrWhiteSpace(SelectedGroupKey) ? SelectedGroupKey : null);
private string BuildGridCssClass() => private string BuildGridCssClass()
string.Equals(DensityMode, TablesDensityMode.Dense, StringComparison.Ordinal) {
? "is-dense" var classes = new List<string>
: "is-comfortable"; {
string.Equals(DensityMode, TablesDensityMode.Dense, StringComparison.Ordinal) ? "is-dense" : "is-comfortable",
Detail.Groups.Count > 0 ? "has-groups" : "has-no-groups"
};
return string.Join(' ', classes);
}
private string BuildGroupHeaderCssClass(string groupKey) private string BuildGroupHeaderCssClass(string groupKey)
{ {
var classes = new List<string> { "critical-table-grid-header-cell", "critical-table-grid-group-header" }; var classes = new List<string>
{
"critical-table-grid-header-cell",
"critical-table-grid-group-header"
};
if (string.Equals(groupKey, ActiveGroupKey, StringComparison.OrdinalIgnoreCase)) if (string.Equals(groupKey, ActiveGroupKey, StringComparison.OrdinalIgnoreCase))
{ {
classes.Add("is-active-group"); classes.Add("is-active-group");
@@ -240,15 +234,18 @@
private string BuildColumnHeaderCssClass(string? groupKey, string columnKey) private string BuildColumnHeaderCssClass(string? groupKey, string columnKey)
{ {
var classes = new List<string> { "critical-table-grid-header-cell", "critical-table-grid-column-header" }; var classes = new List<string>
{
"critical-table-grid-header-cell",
"critical-table-grid-column-header"
};
if (string.Equals(columnKey, ActiveColumnKey, StringComparison.OrdinalIgnoreCase)) if (string.Equals(columnKey, ActiveColumnKey, StringComparison.OrdinalIgnoreCase))
{ {
classes.Add("is-active-column"); classes.Add("is-active-column");
} }
if (!string.IsNullOrWhiteSpace(groupKey) && if (!string.IsNullOrWhiteSpace(groupKey) && string.Equals(groupKey, ActiveGroupKey, StringComparison.OrdinalIgnoreCase))
string.Equals(groupKey, ActiveGroupKey, StringComparison.OrdinalIgnoreCase))
{ {
classes.Add("is-active-group"); classes.Add("is-active-group");
} }
@@ -258,7 +255,11 @@
private string BuildRollBandCssClass(string rollBandLabel) private string BuildRollBandCssClass(string rollBandLabel)
{ {
var classes = new List<string> { "critical-table-grid-header-cell", "critical-table-grid-roll-band" }; var classes = new List<string>
{
"critical-table-grid-header-cell",
"critical-table-grid-roll-band"
};
if (string.Equals(rollBandLabel, ActiveRollBand, StringComparison.OrdinalIgnoreCase)) if (string.Equals(rollBandLabel, ActiveRollBand, StringComparison.OrdinalIgnoreCase))
{ {
classes.Add("is-active-row"); classes.Add("is-active-row");
@@ -290,8 +291,7 @@
classes.Add("is-active-column"); classes.Add("is-active-column");
} }
if (!string.IsNullOrWhiteSpace(groupKey) && if (!string.IsNullOrWhiteSpace(groupKey) && string.Equals(groupKey, ActiveGroupKey, StringComparison.OrdinalIgnoreCase))
string.Equals(groupKey, ActiveGroupKey, StringComparison.OrdinalIgnoreCase))
{ {
classes.Add("is-active-group"); classes.Add("is-active-group");
} }
@@ -340,9 +340,7 @@
private Task HandleCellKeyDown(KeyboardEventArgs args, CriticalTableCellDetail cell) private Task HandleCellKeyDown(KeyboardEventArgs args, CriticalTableCellDetail cell)
{ {
if (string.Equals(args.Key, "Enter", StringComparison.Ordinal) || if (string.Equals(args.Key, "Enter", StringComparison.Ordinal) || string.Equals(args.Key, " ", StringComparison.Ordinal) || string.Equals(args.Key, "Spacebar", StringComparison.Ordinal))
string.Equals(args.Key, " ", StringComparison.Ordinal) ||
string.Equals(args.Key, "Spacebar", StringComparison.Ordinal))
{ {
return SelectCell(cell); return SelectCell(cell);
} }

View File

@@ -1,9 +1,6 @@
<header class="table-browser-header tables-context-bar"> <header class="table-browser-header tables-context-bar">
<div class="tables-context-primary"> <div class="tables-context-primary">
<div> <h2 class="panel-title">@Detail.DisplayName</h2>
<h2 class="panel-title">@Detail.DisplayName</h2>
<p class="table-browser-reading-hint">@GetReadingHint()</p>
</div>
<div class="action-row"> <div class="action-row">
<button type="button" class="btn btn-link" @onclick="() => OnTogglePin.InvokeAsync()"> <button type="button" class="btn btn-link" @onclick="() => OnTogglePin.InvokeAsync()">
@(IsPinned ? "Unpin table" : "Pin table") @(IsPinned ? "Unpin table" : "Pin table")
@@ -11,116 +8,11 @@
<button type="button" class="btn btn-link" @onclick="() => OnToggleLegend.InvokeAsync()"> <button type="button" class="btn btn-link" @onclick="() => OnToggleLegend.InvokeAsync()">
@(IsLegendOpen ? "Hide help" : "Reading help") @(IsLegendOpen ? "Hide help" : "Reading help")
</button> </button>
<p class="table-browser-edit-hint">Select a result to inspect it beside the table.</p>
</div> </div>
</div> </div>
<div class="tables-context-controls">
<div class="tables-context-tab-row">
<SegmentedTabs
Items="modeTabs"
SelectedValue="CurrentMode"
SelectedValueChanged="OnModeChanged"
AriaLabel="Reference mode"
CssClass="tables-context-mode-tabs" />
<SegmentedTabs
Items="densityTabs"
SelectedValue="DensityMode"
SelectedValueChanged="OnDensityChanged"
AriaLabel="Table density"
CssClass="tables-context-density-tabs" />
</div>
<div class="tables-context-fields">
@if (Detail.Groups.Count > 1)
{
<label class="tables-context-field">
<span>Variant</span>
<select class="input-shell" value="@SelectedGroupKey" @onchange="HandleGroupChanged">
<option value="">All variants</option>
@foreach (var group in Detail.Groups)
{
<option value="@group.Key">@group.Label</option>
}
</select>
</label>
}
@if (Detail.Columns.Count > 1)
{
<label class="tables-context-field">
<span>Severity focus</span>
<select class="input-shell" value="@SelectedColumnKey" @onchange="HandleColumnChanged">
<option value="">All severities</option>
@foreach (var column in Detail.Columns)
{
<option value="@column.Key">@column.Label</option>
}
</select>
</label>
}
<label class="tables-context-field">
<span>Roll jump</span>
<input
class="input-shell"
inputmode="numeric"
pattern="[0-9]*"
placeholder="e.g. 66"
value="@RollJumpValue"
@oninput="HandleRollJumpChanged" />
</label>
</div>
@if (HasActiveFilters())
{
<div class="tables-context-filter-chips" aria-label="Active table filters">
@if (!string.IsNullOrWhiteSpace(SelectedGroupLabel))
{
<button type="button" class="tables-context-filter-chip" @onclick="() => OnGroupChanged.InvokeAsync(string.Empty)">
Variant: @SelectedGroupLabel
</button>
}
@if (!string.IsNullOrWhiteSpace(SelectedColumnLabel))
{
<button type="button" class="tables-context-filter-chip" @onclick="() => OnColumnChanged.InvokeAsync(string.Empty)">
Severity: @SelectedColumnLabel
</button>
}
@if (!string.IsNullOrWhiteSpace(RollJumpValue))
{
<button type="button" class="tables-context-filter-chip" @onclick="() => OnRollJumpChanged.InvokeAsync(string.Empty)">
Roll: @RollJumpValue
</button>
}
@if (!string.Equals(CurrentMode, TablesReferenceMode.Reference, StringComparison.Ordinal))
{
<button type="button" class="tables-context-filter-chip" @onclick="() => OnModeChanged.InvokeAsync(TablesReferenceMode.Reference)">
Mode: @GetModeLabel(CurrentMode)
</button>
}
</div>
}
</div>
</header> </header>
@code { @code {
private readonly IReadOnlyList<SegmentedTabItem> modeTabs =
[
new(TablesReferenceMode.Reference, "Reference"),
new(TablesReferenceMode.NeedsCuration, "Needs Curation"),
new(TablesReferenceMode.Curated, "Curated")
];
private readonly IReadOnlyList<SegmentedTabItem> densityTabs =
[
new(TablesDensityMode.Comfortable, "Comfortable"),
new(TablesDensityMode.Dense, "Dense")
];
[Parameter, EditorRequired] [Parameter, EditorRequired]
public CriticalTableDetail Detail { get; set; } = default!; public CriticalTableDetail Detail { get; set; } = default!;
@@ -128,21 +20,6 @@
[Parameter] [Parameter]
public bool IsPinned { get; set; } public bool IsPinned { get; set; }
[Parameter]
public string CurrentMode { get; set; } = TablesReferenceMode.Reference;
[Parameter]
public string SelectedGroupKey { get; set; } = string.Empty;
[Parameter]
public string SelectedColumnKey { get; set; } = string.Empty;
[Parameter]
public string RollJumpValue { get; set; } = string.Empty;
[Parameter]
public string DensityMode { get; set; } = TablesDensityMode.Comfortable;
[Parameter] [Parameter]
public EventCallback OnTogglePin { get; set; } public EventCallback OnTogglePin { get; set; }
@@ -152,52 +29,4 @@
[Parameter] [Parameter]
public EventCallback OnToggleLegend { get; set; } public EventCallback OnToggleLegend { get; set; }
[Parameter]
public EventCallback<string> OnModeChanged { get; set; }
[Parameter]
public EventCallback<string> OnGroupChanged { get; set; }
[Parameter]
public EventCallback<string> OnColumnChanged { get; set; }
[Parameter]
public EventCallback<string> OnRollJumpChanged { get; set; }
[Parameter]
public EventCallback<string> OnDensityChanged { get; set; }
private string GetReadingHint() =>
Detail.Groups.Count > 0
? "Find the roll band on the left, then read across to the group and severity you need."
: "Find the roll band on the left, then read across to the severity you need.";
private string? SelectedGroupLabel =>
Detail.Groups.FirstOrDefault(group => string.Equals(group.Key, SelectedGroupKey, StringComparison.OrdinalIgnoreCase))?.Label;
private string? SelectedColumnLabel =>
Detail.Columns.FirstOrDefault(column => string.Equals(column.Key, SelectedColumnKey, StringComparison.OrdinalIgnoreCase))?.Label;
private bool HasActiveFilters() =>
!string.IsNullOrWhiteSpace(SelectedGroupKey)
|| !string.IsNullOrWhiteSpace(SelectedColumnKey)
|| !string.IsNullOrWhiteSpace(RollJumpValue)
|| !string.Equals(CurrentMode, TablesReferenceMode.Reference, StringComparison.Ordinal);
private Task HandleGroupChanged(ChangeEventArgs args) =>
OnGroupChanged.InvokeAsync(args.Value?.ToString() ?? string.Empty);
private Task HandleColumnChanged(ChangeEventArgs args) =>
OnColumnChanged.InvokeAsync(args.Value?.ToString() ?? string.Empty);
private Task HandleRollJumpChanged(ChangeEventArgs args) =>
OnRollJumpChanged.InvokeAsync(args.Value?.ToString() ?? string.Empty);
private static string GetModeLabel(string mode) =>
mode switch
{
TablesReferenceMode.NeedsCuration => "Needs Curation",
TablesReferenceMode.Curated => "Curated",
_ => "Reference"
};
} }

View File

@@ -1,7 +1,6 @@
<section class="tables-index-rail" aria-labelledby="tables-index-heading"> <section class="tables-index-rail" aria-labelledby="tables-index-heading">
<div class="tables-index-rail-header"> <div class="tables-index-rail-header">
<h2 id="tables-index-heading" class="tables-index-title">Table Index</h2> <h2 id="tables-index-heading" class="tables-index-title">Table Index</h2>
<p class="tables-index-copy">Choose a table, then read from the roll band across to the result you need.</p>
</div> </div>
<div class="tables-index-controls" @onkeydown="HandleRailKeyDown"> <div class="tables-index-controls" @onkeydown="HandleRailKeyDown">
@@ -12,7 +11,7 @@
type="search" type="search"
placeholder="Search tables" placeholder="Search tables"
value="@searchText" value="@searchText"
@oninput="HandleSearchInput" /> @oninput="HandleSearchInput"/>
@if (familyFilters.Count > 1) @if (familyFilters.Count > 1)
{ {
@@ -74,7 +73,7 @@
@if (filteredTables.Count == 0) @if (filteredTables.Count == 0)
{ {
<p class="tables-index-empty">No tables match the current search.</p> <div class="tables-index-empty">No tables match the current search.</div>
} }
else else
{ {
@@ -130,11 +129,7 @@
familyFilters.Clear(); familyFilters.Clear();
familyFilters.Add(string.Empty); familyFilters.Add(string.Empty);
foreach (var family in Tables foreach (var family in Tables.Select(table => table.Family).Where(family => !string.IsNullOrWhiteSpace(family)).Distinct(StringComparer.OrdinalIgnoreCase).OrderBy(family => family, StringComparer.OrdinalIgnoreCase))
.Select(table => table.Family)
.Where(family => !string.IsNullOrWhiteSpace(family))
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(family => family, StringComparer.OrdinalIgnoreCase))
{ {
familyFilters.Add(family); familyFilters.Add(family);
} }
@@ -203,8 +198,7 @@
private bool MatchesFilters(CriticalTableReference table) private bool MatchesFilters(CriticalTableReference table)
{ {
if (!string.IsNullOrEmpty(selectedFamily) && if (!string.IsNullOrEmpty(selectedFamily) && !string.Equals(table.Family, selectedFamily, StringComparison.OrdinalIgnoreCase))
!string.Equals(table.Family, selectedFamily, StringComparison.OrdinalIgnoreCase))
{ {
return false; return false;
} }
@@ -214,9 +208,7 @@
return true; return true;
} }
return table.Label.Contains(searchText, StringComparison.OrdinalIgnoreCase) return table.Label.Contains(searchText, StringComparison.OrdinalIgnoreCase) || table.Key.Contains(searchText, StringComparison.OrdinalIgnoreCase) || table.Family.Contains(searchText, StringComparison.OrdinalIgnoreCase);
|| table.Key.Contains(searchText, StringComparison.OrdinalIgnoreCase)
|| table.Family.Contains(searchText, StringComparison.OrdinalIgnoreCase);
} }
private void HandleSearchInput(ChangeEventArgs args) private void HandleSearchInput(ChangeEventArgs args)
@@ -237,9 +229,7 @@
string.Equals(selectedFamily, family, StringComparison.OrdinalIgnoreCase); string.Equals(selectedFamily, family, StringComparison.OrdinalIgnoreCase);
private string GetFamilyFilterCssClass(string family) => private string GetFamilyFilterCssClass(string family) =>
IsFamilyFilterSelected(family) IsFamilyFilterSelected(family) ? "tables-family-filter is-selected" : "tables-family-filter";
? "tables-family-filter is-selected"
: "tables-family-filter";
private void HandleRailKeyDown(KeyboardEventArgs args) private void HandleRailKeyDown(KeyboardEventArgs args)
{ {
@@ -280,9 +270,7 @@
currentIndex = keyboardOptions.FindIndex(item => string.Equals(item.Key, SelectedTableSlug, StringComparison.OrdinalIgnoreCase)); currentIndex = keyboardOptions.FindIndex(item => string.Equals(item.Key, SelectedTableSlug, StringComparison.OrdinalIgnoreCase));
} }
var nextIndex = currentIndex < 0 var nextIndex = currentIndex < 0 ? 0 : Math.Clamp(currentIndex + offset, 0, keyboardOptions.Count - 1);
? 0
: Math.Clamp(currentIndex + offset, 0, keyboardOptions.Count - 1);
activeOptionSlug = keyboardOptions[nextIndex].Key; activeOptionSlug = keyboardOptions[nextIndex].Key;
} }
@@ -295,8 +283,7 @@
return; return;
} }
if (!string.IsNullOrWhiteSpace(activeOptionSlug) && if (!string.IsNullOrWhiteSpace(activeOptionSlug) && keyboardOptions.Any(item => string.Equals(item.Key, activeOptionSlug, StringComparison.OrdinalIgnoreCase)))
keyboardOptions.Any(item => string.Equals(item.Key, activeOptionSlug, StringComparison.OrdinalIgnoreCase)))
{ {
return; return;
} }
@@ -338,22 +325,23 @@
private void SetActiveOption(string tableSlug) => activeOptionSlug = tableSlug; private void SetActiveOption(string tableSlug) => activeOptionSlug = tableSlug;
private RenderFragment RenderTableOption(CriticalTableReference table) => @<button private RenderFragment RenderTableOption(CriticalTableReference table) => @<button
type="button" type="button"
role="option" role="option"
aria-selected="@string.Equals(table.Key, SelectedTableSlug, StringComparison.OrdinalIgnoreCase)" aria-selected="@string.Equals(table.Key, SelectedTableSlug, StringComparison.OrdinalIgnoreCase)"
class="table-index-option @GetTableOptionCssClass(table)" class="table-index-option @GetTableOptionCssClass(table)"
@onfocus="() => SetActiveOption(table.Key)" @onfocus="() => SetActiveOption(table.Key)"
@onclick="() => OnSelectTable.InvokeAsync(table.Key)"> @onclick="() => OnSelectTable.InvokeAsync(table.Key)">
<span class="table-index-option-copy"> <span class="table-index-option-copy">
<strong class="table-index-option-title">@table.Label</strong> <strong class="table-index-option-title">@table.Label</strong>
<span class="table-index-option-meta">@table.Family</span> <span class="table-index-option-meta">@table.Family</span>
</span> </span>
<span class="table-index-option-chips"> <span class="table-index-option-chips">
@if (GetIsPinned(table.Key)) @if (GetIsPinned(table.Key))
{ {
<StatusChip Tone="accent">Pinned</StatusChip> <StatusChip Tone="accent">Pinned</StatusChip>
} }
<StatusChip Tone="@GetCurationTone(table)">@($"{table.CurationPercentage}%")</StatusChip> <StatusChip Tone="@GetCurationTone(table)">@($"{table.CurationPercentage}%")</StatusChip>
</span> </span>
</button>; </button>;
} }

View File

@@ -3,7 +3,6 @@
<div class="critical-legend"> <div class="critical-legend">
<div class="critical-legend-header"> <div class="critical-legend-header">
<h4>Reading help</h4> <h4>Reading help</h4>
<p class="muted">These symbols show the effects attached to a result at a glance.</p>
</div> </div>
<div class="legend-grid"> <div class="legend-grid">
@foreach (var entry in LegendEntries) @foreach (var entry in LegendEntries)
@@ -21,6 +20,8 @@
} }
@code { @code {
[Parameter] [Parameter]
public IReadOnlyList<CriticalTableLegendEntry> LegendEntries { get; set; } = Array.Empty<CriticalTableLegendEntry>(); public IReadOnlyList<CriticalTableLegendEntry> LegendEntries { get; set; } = Array.Empty<CriticalTableLegendEntry>();
} }

View File

@@ -1,7 +1,5 @@
<header class="tables-page-header"> <header class="tables-page-header">
<div class="tables-page-header-copy"> <div class="tables-page-header-copy">
<p class="tables-page-eyebrow">Reference</p>
<h1 class="panel-title">Critical Tables</h1> <h1 class="panel-title">Critical Tables</h1>
<p class="tables-page-intro">Browse the index, open a table, and read the result directly from the grid without leaving the page.</p>
</div> </div>
</header> </header>

View File

@@ -0,0 +1,24 @@
@if (SelectedCellDetail is not null)
{
<div class="tables-selection-menu" aria-label="Selected result actions">
@if (!SelectedCellDetail.IsCurated)
{
<button type="button" class="btn btn-secondary" @onclick="OnCurate">Open curation</button>
}
<button type="button" class="btn btn-primary" @onclick="OnEdit">Open editor</button>
</div>
}
@code {
[Parameter]
public CriticalTableCellDetail? SelectedCellDetail { get; set; }
[Parameter]
public EventCallback OnEdit { get; set; }
[Parameter]
public EventCallback OnCurate { get; set; }
}

View File

@@ -51,6 +51,7 @@
--button-secondary-bg-hover: rgba(250, 236, 210, 0.95); --button-secondary-bg-hover: rgba(250, 236, 210, 0.95);
--button-secondary-text: #6a4b28; --button-secondary-text: #6a4b28;
--button-secondary-border: rgba(127, 96, 55, 0.18); --button-secondary-border: rgba(127, 96, 55, 0.18);
--control-height: 3rem;
--font-display: "Fraunces", Georgia, serif; --font-display: "Fraunces", Georgia, serif;
--font-body: "IBM Plex Sans", "Segoe UI", sans-serif; --font-body: "IBM Plex Sans", "Segoe UI", sans-serif;
--font-ui: "IBM Plex Sans", "Segoe UI", sans-serif; --font-ui: "IBM Plex Sans", "Segoe UI", sans-serif;
@@ -315,11 +316,13 @@ pre,
display: grid; display: grid;
gap: 0.95rem; gap: 0.95rem;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
align-items: start;
} }
.field-shell { .field-shell {
display: grid; display: grid;
gap: 0.35rem; gap: 0.35rem;
align-content: start;
} }
.roll-input-row { .roll-input-row {
@@ -341,12 +344,19 @@ pre,
.input-shell { .input-shell {
width: 100%; width: 100%;
min-height: var(--control-height);
border-radius: 14px; border-radius: 14px;
border: 1px solid var(--button-secondary-border); border: 1px solid var(--button-secondary-border);
background: var(--surface-input); background: var(--surface-input);
padding: 0.8rem 0.9rem; padding: 0.8rem 0.9rem;
color: var(--text-primary); color: var(--text-primary);
box-sizing: border-box; box-sizing: border-box;
line-height: 1.25;
}
input.input-shell,
select.input-shell {
height: var(--control-height);
} }
.input-shell:focus { .input-shell:focus {
@@ -425,6 +435,7 @@ pre,
padding: 1rem; padding: 1rem;
background: var(--surface-card); background: var(--surface-card);
border: 1px solid rgba(127, 96, 55, 0.14); border: 1px solid rgba(127, 96, 55, 0.14);
min-width: 0;
} }
.result-card h3, .result-card h3,
@@ -510,6 +521,9 @@ pre,
flex-direction: column; flex-direction: column;
gap: 0.35rem; gap: 0.35rem;
min-height: 100%; min-height: 100%;
min-width: 0;
max-width: 100%;
box-sizing: border-box;
} }
.critical-cell-status-chip, .critical-cell-status-chip,
@@ -543,6 +557,7 @@ pre,
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.65rem; gap: 0.65rem;
min-width: 0;
} }
.critical-cell-description { .critical-cell-description {
@@ -550,12 +565,14 @@ pre,
color: #2c1a10; color: #2c1a10;
font-size: 1.2rem; font-size: 1.2rem;
line-height: 1.4; line-height: 1.4;
overflow-wrap: anywhere;
} }
.critical-branch-stack { .critical-branch-stack {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.4rem; gap: 0.4rem;
min-width: 0;
} }
.critical-branch-card { .critical-branch-card {
@@ -563,6 +580,7 @@ pre,
border-radius: 12px; border-radius: 12px;
border: 1px solid rgba(127, 96, 55, 0.12); border: 1px solid rgba(127, 96, 55, 0.12);
background: rgba(255, 255, 255, 0.85); background: rgba(255, 255, 255, 0.85);
min-width: 0;
} }
.critical-branch-header { .critical-branch-header {
@@ -571,6 +589,7 @@ pre,
justify-content: space-between; justify-content: space-between;
gap: 0.5rem; gap: 0.5rem;
flex-wrap: wrap; flex-wrap: wrap;
min-width: 0;
} }
.critical-branch-condition { .critical-branch-condition {
@@ -579,6 +598,7 @@ pre,
letter-spacing: 0.05em; letter-spacing: 0.05em;
text-transform: uppercase; text-transform: uppercase;
color: #6b4c29; color: #6b4c29;
overflow-wrap: anywhere;
} }
.critical-branch-description { .critical-branch-description {
@@ -586,6 +606,7 @@ pre,
font-size: 0.85rem; font-size: 0.85rem;
line-height: 1.35; line-height: 1.35;
color: #3b2a21; color: #3b2a21;
overflow-wrap: anywhere;
} }
.critical-branch-header .affix-badge-list { .critical-branch-header .affix-badge-list {
@@ -645,6 +666,8 @@ pre,
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.25rem; gap: 0.25rem;
margin-top: 0.5rem; margin-top: 0.5rem;
min-width: 0;
max-width: 100%;
} }
.affix-badge { .affix-badge {
@@ -1146,7 +1169,7 @@ pre,
.tables-reference-layout { .tables-reference-layout {
display: grid; display: grid;
grid-template-columns: minmax(17rem, 20rem) minmax(0, 1fr) minmax(19rem, 24rem); grid-template-columns: minmax(17rem, 20rem) minmax(0, 1fr);
gap: 1rem; gap: 1rem;
align-items: start; align-items: start;
} }
@@ -1341,6 +1364,23 @@ pre,
min-width: 0; min-width: 0;
} }
.tables-selection-menu {
position: fixed;
right: 1rem;
bottom: calc(var(--shell-mobile-nav-height, 0rem) + 1rem);
z-index: 40;
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
padding: 0.75rem;
border-radius: 18px;
background: color-mix(in srgb, var(--surface-card-strong) 96%, transparent);
border: 1px solid var(--border-default);
box-shadow: var(--shadow-2);
backdrop-filter: blur(12px);
}
.tables-reference-inspector-shell { .tables-reference-inspector-shell {
position: sticky; position: sticky;
top: calc(var(--shell-header-height) + 1rem); top: calc(var(--shell-header-height) + 1rem);
@@ -1382,6 +1422,9 @@ pre,
} }
.table-shell { .table-shell {
display: grid;
gap: 0.75rem;
min-height: 0;
border-radius: 20px; border-radius: 20px;
padding: 1.2rem; padding: 1.2rem;
background: var(--surface-card-strong); background: var(--surface-card-strong);
@@ -1394,15 +1437,14 @@ pre,
align-items: flex-start; align-items: flex-start;
justify-content: space-between; justify-content: space-between;
gap: 1rem; gap: 1rem;
margin-bottom: 1rem; margin-bottom: 0;
} }
.tables-context-bar { .tables-context-bar {
position: sticky; position: sticky;
top: calc(var(--shell-header-height) + 1rem); top: calc(var(--shell-header-height, 5.75rem) + 1rem);
z-index: 4; z-index: 8;
padding-bottom: 1rem; padding-bottom: 0.75rem;
margin-bottom: 1rem;
background: linear-gradient(180deg, rgba(255, 251, 245, 0.96), rgba(255, 251, 245, 0.92)); background: linear-gradient(180deg, rgba(255, 251, 245, 0.96), rgba(255, 251, 245, 0.92));
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(127, 96, 55, 0.14); border-bottom: 1px solid rgba(127, 96, 55, 0.14);
@@ -1414,14 +1456,21 @@ pre,
.tables-context-primary, .tables-context-primary,
.tables-context-controls { .tables-context-controls {
display: grid; display: flex;
align-items: center;
justify-content: space-between;
gap: 0.85rem; gap: 0.85rem;
flex-wrap: wrap;
} }
.tables-context-controls { .tables-context-controls {
margin-top: 0.85rem; margin-top: 0.85rem;
} }
.tables-context-primary .panel-title {
margin: 0;
}
.tables-context-tab-row { .tables-context-tab-row {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -1495,6 +1544,12 @@ pre,
display: none; display: none;
} }
.tables-selection-menu {
right: 0.75rem;
left: 0.75rem;
justify-content: flex-end;
}
.tables-inspector-sheet { .tables-inspector-sheet {
position: fixed; position: fixed;
inset: 0; inset: 0;
@@ -1544,19 +1599,30 @@ pre,
} }
.table-shell .table-scroll { .table-shell .table-scroll {
overflow-x: auto; overflow: auto;
min-width: 0;
max-height: min(72dvh, calc(100dvh - var(--shell-header-height, 5.75rem) - var(--shell-mobile-nav-height, 0rem) - 5rem));
border-radius: 18px;
overscroll-behavior: contain;
scrollbar-gutter: stable both-edges;
} }
.critical-table-grid { .critical-table-grid {
display: grid; display: grid;
align-items: stretch; align-items: stretch;
width: 100%; min-width: 100%;
width: max-content;
font-size: 1.5rem; font-size: 1.5rem;
border-top: 1px solid rgba(127, 96, 55, 0.2); border-top: 1px solid rgba(127, 96, 55, 0.2);
border-left: 1px solid rgba(127, 96, 55, 0.2); border-left: 1px solid rgba(127, 96, 55, 0.2);
isolation: isolate; isolation: isolate;
--tables-header-row-height: 3.2rem;
--tables-group-header-top: 0; --tables-group-header-top: 0;
--tables-column-header-top: 3.2rem; --tables-column-header-top: 0;
}
.critical-table-grid.has-groups {
--tables-column-header-top: var(--tables-header-row-height);
} }
.critical-table-grid-header-cell { .critical-table-grid-header-cell {
@@ -1564,10 +1630,11 @@ pre,
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-width: 0; min-width: 0;
min-height: var(--tables-header-row-height);
padding: 0.35rem; padding: 0.35rem;
border-right: 1px solid rgba(127, 96, 55, 0.2); border-right: 1px solid rgba(127, 96, 55, 0.2);
border-bottom: 1px solid rgba(127, 96, 55, 0.2); border-bottom: 1px solid rgba(127, 96, 55, 0.2);
background: rgba(238, 223, 193, 0.45); background: color-mix(in srgb, var(--surface-card-strong) 94%, var(--accent-1));
text-transform: uppercase; text-transform: uppercase;
text-align: center; text-align: center;
letter-spacing: 0.08em; letter-spacing: 0.08em;
@@ -1582,18 +1649,18 @@ pre,
.critical-table-grid-group-header { .critical-table-grid-group-header {
position: sticky; position: sticky;
top: var(--tables-group-header-top); top: var(--tables-group-header-top);
z-index: 3; z-index: 5;
} }
.critical-table-grid-column-header { .critical-table-grid-column-header {
position: sticky; position: sticky;
top: var(--tables-column-header-top); top: var(--tables-column-header-top);
z-index: 3; z-index: 4;
} }
.critical-table-grid.is-dense { .critical-table-grid.is-dense {
font-size: 1.2rem; font-size: 1.2rem;
--tables-column-header-top: 2.6rem; --tables-header-row-height: 2.6rem;
} }
.critical-table-grid.is-dense .critical-table-grid-header-cell { .critical-table-grid.is-dense .critical-table-grid-header-cell {
@@ -1606,34 +1673,35 @@ pre,
.critical-table-grid-corner, .critical-table-grid-corner,
.critical-table-grid-roll-band-header { .critical-table-grid-roll-band-header {
background: rgba(255, 247, 230, 0.52); background: color-mix(in srgb, var(--surface-card-strong) 94%, var(--accent-1));
} }
.critical-table-grid-corner { .critical-table-grid-corner {
position: sticky; position: sticky;
top: var(--tables-group-header-top); top: var(--tables-group-header-top);
left: 0; left: 0;
z-index: 5; z-index: 7;
} }
.critical-table-grid-roll-band-header { .critical-table-grid-roll-band-header {
position: sticky; position: sticky;
top: var(--tables-column-header-top); top: var(--tables-column-header-top);
left: 0; left: 0;
z-index: 5; z-index: 6;
} }
.critical-table-grid-roll-band { .critical-table-grid-roll-band {
background: rgba(255, 247, 230, 0.52); background: color-mix(in srgb, var(--surface-card-strong) 94%, var(--accent-1));
font-size: 1.5rem; font-size: 1.5rem;
position: sticky; position: sticky;
left: 0; left: 0;
z-index: 2; z-index: 3;
} }
.critical-table-cell { .critical-table-cell {
position: relative; position: relative;
display: flex; display: flex;
z-index: 1;
min-width: 0; min-width: 0;
padding: 0.55rem; padding: 0.55rem;
border-right: 1px solid rgba(127, 96, 55, 0.2); border-right: 1px solid rgba(127, 96, 55, 0.2);
@@ -2412,10 +2480,6 @@ pre,
flex-direction: column; flex-direction: column;
} }
.table-browser-edit-hint {
white-space: normal;
}
.tables-context-fields { .tables-context-fields {
flex-direction: column; flex-direction: column;
} }