@lostgradient/cinder 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/components.json +3 -2
- package/dist/components/accordion/index.js +14 -11
- package/dist/components/accordion-item/accordion-item.css +20 -7
- package/dist/components/accordion-item/accordion-item.schema.js +5 -1
- package/dist/components/accordion-item/accordion-item.types.d.ts +4 -0
- package/dist/components/accordion-item/accordion-item.variables.js +13 -2
- package/dist/components/accordion-item/index.js +14 -11
- package/dist/components/action-row/action-row.css +20 -9
- package/dist/components/action-row/action-row.schema.js +5 -1
- package/dist/components/action-row/action-row.types.d.ts +43 -0
- package/dist/components/action-row/action-row.variables.js +12 -2
- package/dist/components/chat/chat.svelte.d.ts +1 -0
- package/dist/components/code-block/code-block.css +15 -9
- package/dist/components/code-block/code-block.variables.js +8 -2
- package/dist/components/confirm-dialog/confirm-dialog.css +2 -0
- package/dist/components/confirm-dialog/confirm-dialog.schema.js +10 -1
- package/dist/components/confirm-dialog/confirm-dialog.types.d.ts +7 -0
- package/dist/components/confirm-dialog/index.js +478 -106
- package/dist/components/faceted-filter-bar/faceted-filter-bar.schema.js +5 -1
- package/dist/components/faceted-filter-bar/faceted-filter-bar.types.d.ts +2 -0
- package/dist/components/faceted-filter-bar/index.js +40 -31
- package/dist/components/navigation-bar/index.js +11 -17
- package/dist/components/run-step-timeline/index.js +65 -6
- package/dist/components/run-step-timeline/run-step-timeline.utilities.d.ts +9 -1
- package/dist/components/side-navigation/side-navigation.css +2 -1
- package/dist/components/side-navigation/side-navigation.schema.js +9 -6
- package/dist/components/side-navigation/side-navigation.types.d.ts +11 -0
- package/dist/components/side-navigation/side-navigation.variables.js +2 -2
- package/dist/components/stat/stat.css +16 -8
- package/dist/components/stat/stat.schema.js +5 -1
- package/dist/components/stat/stat.types.d.ts +19 -0
- package/dist/components/stat/stat.variables.js +11 -2
- package/dist/components/stat-group/stat-group.css +7 -4
- package/dist/components/stat-group/stat-group.schema.js +5 -1
- package/dist/components/stat-group/stat-group.types.d.ts +27 -0
- package/dist/components/stat-group/stat-group.variables.js +6 -2
- package/dist/index.js +365 -256
- package/dist/server/components/accordion/index.js +2 -2
- package/dist/server/components/accordion-item/index.js +2 -2
- package/dist/server/components/color-field/index.js +2 -2
- package/dist/server/components/confirm-dialog/index.js +7 -2
- package/dist/server/components/faceted-filter-bar/index.js +1 -1
- package/dist/server/components/navigation-bar/index.js +1 -1
- package/dist/server/components/run-step-timeline/index.js +1 -1
- package/dist/server/index.js +10 -10
- package/dist/server/{index.server-cf6pxeyg.js → index.server-0rzd71e0.js} +1 -1
- package/dist/server/{index.server-5tf22d6e.js → index.server-4wx0qven.js} +3 -3
- package/dist/server/{index.server-894t97kv.js → index.server-7yvpb6z7.js} +4 -4
- package/dist/server/{index.server-btgb3d56.js → index.server-d5a5ccz7.js} +18 -12
- package/dist/server/{index.server-z38bxms8.js → index.server-g5jgq3jp.js} +65 -6
- package/dist/server/{index.server-y83kqayt.js → index.server-h3nzp8g8.js} +1 -1
- package/dist/server/index.server-jwvxxjvc.js +50 -0
- package/dist/server/{index.server-39ne5cd1.js → index.server-pxq277t5.js} +32 -1
- package/package.json +22 -95
- package/src/components/accordion/accordion.examples.json +6 -0
- package/src/components/accordion-item/accordion-item.css +20 -7
- package/src/components/accordion-item/accordion-item.schema.json +4 -0
- package/src/components/accordion-item/accordion-item.schema.ts +4 -0
- package/src/components/accordion-item/accordion-item.svelte +9 -7
- package/src/components/accordion-item/accordion-item.types.ts +4 -0
- package/src/components/accordion-item/accordion-item.variables.json +12 -1
- package/src/components/accordion-item/accordion-item.variables.ts +12 -1
- package/src/components/action-row/action-row.css +20 -9
- package/src/components/action-row/action-row.examples.json +6 -0
- package/src/components/action-row/action-row.schema.json +4 -0
- package/src/components/action-row/action-row.schema.ts +4 -0
- package/src/components/action-row/action-row.types.ts +44 -0
- package/src/components/action-row/action-row.variables.json +11 -1
- package/src/components/action-row/action-row.variables.ts +11 -1
- package/src/components/chat/chat.examples.json +6 -0
- package/src/components/code-block/code-block.css +15 -9
- package/src/components/code-block/code-block.variables.json +7 -1
- package/src/components/code-block/code-block.variables.ts +7 -1
- package/src/components/confirm-dialog/confirm-dialog.css +2 -0
- package/src/components/confirm-dialog/confirm-dialog.schema.json +8 -0
- package/src/components/confirm-dialog/confirm-dialog.schema.ts +10 -0
- package/src/components/confirm-dialog/confirm-dialog.svelte +31 -3
- package/src/components/confirm-dialog/confirm-dialog.types.ts +7 -0
- package/src/components/faceted-filter-bar/faceted-filter-bar.schema.json +4 -0
- package/src/components/faceted-filter-bar/faceted-filter-bar.schema.ts +4 -0
- package/src/components/faceted-filter-bar/faceted-filter-bar.svelte +13 -11
- package/src/components/faceted-filter-bar/faceted-filter-bar.types.ts +2 -0
- package/src/components/navigation-bar/navigation-bar.svelte +2 -2
- package/src/components/run-step-timeline/run-step-timeline.svelte +4 -4
- package/src/components/run-step-timeline/run-step-timeline.utilities.ts +68 -0
- package/src/components/side-navigation/side-navigation.css +2 -1
- package/src/components/side-navigation/side-navigation.examples.json +6 -0
- package/src/components/side-navigation/side-navigation.schema.json +8 -5
- package/src/components/side-navigation/side-navigation.schema.ts +8 -5
- package/src/components/side-navigation/side-navigation.types.ts +12 -0
- package/src/components/side-navigation/side-navigation.variables.json +1 -1
- package/src/components/side-navigation/side-navigation.variables.ts +1 -1
- package/src/components/stat/stat.css +16 -8
- package/src/components/stat/stat.schema.json +4 -0
- package/src/components/stat/stat.schema.ts +4 -0
- package/src/components/stat/stat.types.ts +20 -0
- package/src/components/stat/stat.variables.json +10 -1
- package/src/components/stat/stat.variables.ts +10 -1
- package/src/components/stat-group/stat-group.css +7 -4
- package/src/components/stat-group/stat-group.examples.json +6 -0
- package/src/components/stat-group/stat-group.schema.json +4 -0
- package/src/components/stat-group/stat-group.schema.ts +4 -0
- package/src/components/stat-group/stat-group.types.ts +28 -0
- package/src/components/stat-group/stat-group.variables.json +5 -1
- package/src/components/stat-group/stat-group.variables.ts +5 -1
- package/dist/server/index.server-hfx487zj.js +0 -42
|
@@ -26,6 +26,14 @@
|
|
|
26
26
|
"type": "boolean",
|
|
27
27
|
"description": "When true, the confirm button uses variant=\"danger\". The cancel button still\nreceives default focus regardless — color is never the sole destructive signal."
|
|
28
28
|
},
|
|
29
|
+
"typeToConfirm": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "When set, renders a labelled text input and disables the confirm button until the\ntrimmed input matches this value case-insensitively."
|
|
32
|
+
},
|
|
33
|
+
"typeToConfirmLabel": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "Visible label for the typed-confirmation input. Defaults to `Type \"<value>\" to confirm`."
|
|
36
|
+
},
|
|
29
37
|
"class": {
|
|
30
38
|
"type": "string",
|
|
31
39
|
"description": "Optional extra class on the underlying <Modal>. Destructured as `class: className` per repo convention."
|
|
@@ -31,6 +31,16 @@ const schema = {
|
|
|
31
31
|
description:
|
|
32
32
|
'When true, the confirm button uses variant="danger". The cancel button still\nreceives default focus regardless — color is never the sole destructive signal.',
|
|
33
33
|
},
|
|
34
|
+
typeToConfirm: {
|
|
35
|
+
type: 'string',
|
|
36
|
+
description:
|
|
37
|
+
'When set, renders a labelled text input and disables the confirm button until the\ntrimmed input matches this value case-insensitively.',
|
|
38
|
+
},
|
|
39
|
+
typeToConfirmLabel: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
description:
|
|
42
|
+
'Visible label for the typed-confirmation input. Defaults to `Type "<value>" to confirm`.',
|
|
43
|
+
},
|
|
34
44
|
class: {
|
|
35
45
|
type: 'string',
|
|
36
46
|
description:
|
|
@@ -4,11 +4,25 @@ export {};
|
|
|
4
4
|
|
|
5
5
|
<script lang="ts">
|
|
6
6
|
import Button from '../button/button.svelte';
|
|
7
|
+
import Input from '../input/input.svelte';
|
|
7
8
|
import Modal from '../modal/modal.svelte';
|
|
8
9
|
import { classNames } from '../../utilities/class-names.ts';
|
|
9
10
|
const descriptionId = $props.id();
|
|
10
|
-
|
|
11
|
+
const typedConfirmationId = `${descriptionId}-typed-confirmation`;
|
|
12
|
+
let { open = $bindable(false), title, description, cancelLabel = 'Cancel', confirmLabel, destructive = false, typeToConfirm, typeToConfirmLabel, onconfirm, oncancel, triggerRef = null, class: className, } = $props();
|
|
11
13
|
const describedById = $derived(description ? descriptionId : undefined);
|
|
14
|
+
const normalizedTypeToConfirm = $derived(typeToConfirm?.trim() || undefined);
|
|
15
|
+
const normalizedTypeToConfirmLabel = $derived(typeToConfirmLabel?.trim() || undefined);
|
|
16
|
+
let typedConfirmation = $state('');
|
|
17
|
+
let previousOpen = open;
|
|
18
|
+
const typedConfirmationMatches = $derived(normalizedTypeToConfirm === undefined ||
|
|
19
|
+
typedConfirmation.trim().toLowerCase() === normalizedTypeToConfirm.toLowerCase());
|
|
20
|
+
$effect(() => {
|
|
21
|
+
if (open === previousOpen)
|
|
22
|
+
return;
|
|
23
|
+
previousOpen = open;
|
|
24
|
+
typedConfirmation = '';
|
|
25
|
+
});
|
|
12
26
|
function handleCancel() {
|
|
13
27
|
// Mirror Modal's dismiss() ordering: state first, then callback. No try/catch —
|
|
14
28
|
// consumer errors must propagate so tests, error boundaries, and observability see them.
|
|
@@ -34,10 +48,24 @@ function handleConfirm() {
|
|
|
34
48
|
<p id={descriptionId} class="cinder-confirm-dialog__description">{description}</p>
|
|
35
49
|
{/if}
|
|
36
50
|
|
|
51
|
+
{#if normalizedTypeToConfirm !== undefined}
|
|
52
|
+
<Input
|
|
53
|
+
id={typedConfirmationId}
|
|
54
|
+
class="cinder-confirm-dialog__typed-confirmation"
|
|
55
|
+
bind:value={typedConfirmation}
|
|
56
|
+
label={normalizedTypeToConfirmLabel ?? `Type "${normalizedTypeToConfirm}" to confirm`}
|
|
57
|
+
autocomplete="off"
|
|
58
|
+
/>
|
|
59
|
+
{/if}
|
|
60
|
+
|
|
37
61
|
{#snippet footer()}
|
|
38
62
|
<Button variant="secondary" autofocus onclick={handleCancel}>{cancelLabel}</Button>
|
|
39
|
-
<Button
|
|
40
|
-
|
|
63
|
+
<Button
|
|
64
|
+
variant={destructive ? 'danger' : 'primary'}
|
|
65
|
+
disabled={!typedConfirmationMatches}
|
|
66
|
+
onclick={handleConfirm}
|
|
41
67
|
>
|
|
68
|
+
{confirmLabel}
|
|
69
|
+
</Button>
|
|
42
70
|
{/snippet}
|
|
43
71
|
</Modal>
|
|
@@ -36,6 +36,13 @@ export type ConfirmDialogProps = {
|
|
|
36
36
|
* receives default focus regardless — color is never the sole destructive signal.
|
|
37
37
|
*/
|
|
38
38
|
destructive?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* When set, renders a labelled text input and disables the confirm button until the
|
|
41
|
+
* trimmed input matches this value case-insensitively.
|
|
42
|
+
*/
|
|
43
|
+
typeToConfirm?: string;
|
|
44
|
+
/** Visible label for the typed-confirmation input. Defaults to `Type "<value>" to confirm`. */
|
|
45
|
+
typeToConfirmLabel?: string;
|
|
39
46
|
/** Fired when the user activates the confirm button. Required. Component closes itself after. */
|
|
40
47
|
onconfirm: () => void;
|
|
41
48
|
/**
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
"type": "string",
|
|
11
11
|
"description": "Current text search query. When provided, the search field is controlled."
|
|
12
12
|
},
|
|
13
|
+
"showSearch": {
|
|
14
|
+
"type": "boolean",
|
|
15
|
+
"description": "Whether to render the leading search field. Defaults to `true`."
|
|
16
|
+
},
|
|
13
17
|
"searchPlaceholder": {
|
|
14
18
|
"type": "string",
|
|
15
19
|
"description": "Placeholder text shown in the leading search field."
|
|
@@ -12,6 +12,10 @@ const schema = {
|
|
|
12
12
|
type: 'string',
|
|
13
13
|
description: 'Current text search query. When provided, the search field is controlled.',
|
|
14
14
|
},
|
|
15
|
+
showSearch: {
|
|
16
|
+
type: 'boolean',
|
|
17
|
+
description: 'Whether to render the leading search field. Defaults to `true`.',
|
|
18
|
+
},
|
|
15
19
|
searchPlaceholder: {
|
|
16
20
|
type: 'string',
|
|
17
21
|
description: 'Placeholder text shown in the leading search field.',
|
|
@@ -10,7 +10,7 @@ import Chip from '../chip/chip.svelte';
|
|
|
10
10
|
import Button from '../button/button.svelte';
|
|
11
11
|
import { classNames } from '../../utilities/class-names.ts';
|
|
12
12
|
const generatedId = $props.id();
|
|
13
|
-
let { 'aria-label': ariaLabel = 'Filters', searchQuery, searchPlaceholder = 'Search…', searchAriaLabel = 'Search', facets = [], appliedFilters = [], disabled = false, class: className, onsearchchange, onfacetchange, onfilterremove, onclearall, ...rest } = $props();
|
|
13
|
+
let { 'aria-label': ariaLabel = 'Filters', searchQuery, showSearch = true, searchPlaceholder = 'Search…', searchAriaLabel = 'Search', facets = [], appliedFilters = [], disabled = false, class: className, onsearchchange, onfacetchange, onfilterremove, onclearall, ...rest } = $props();
|
|
14
14
|
const searchId = $derived(`${generatedId}-search`);
|
|
15
15
|
let rootElement = $state();
|
|
16
16
|
// Internal uncontrolled search value when searchQuery is not provided.
|
|
@@ -32,7 +32,7 @@ function resolveFilterDisplayValue(key, value) {
|
|
|
32
32
|
return value;
|
|
33
33
|
return facet.options.find((option) => option.value === value)?.label ?? value;
|
|
34
34
|
}
|
|
35
|
-
const hasAppliedFilters = $derived(appliedFilters.length > 0 || currentSearchQuery.length > 0);
|
|
35
|
+
const hasAppliedFilters = $derived(appliedFilters.length > 0 || (showSearch && currentSearchQuery.length > 0));
|
|
36
36
|
const totalActiveCount = $derived(appliedFilters.length);
|
|
37
37
|
// Live region summary message — always in DOM, content changes when filters change.
|
|
38
38
|
const summaryMessage = $derived.by(() => {
|
|
@@ -81,15 +81,17 @@ function getFacetCurrentValue(key) {
|
|
|
81
81
|
>
|
|
82
82
|
<!-- Controls row: search field + facet selects -->
|
|
83
83
|
<div class="cinder-faceted-filter-bar__controls">
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
84
|
+
{#if showSearch}
|
|
85
|
+
<SearchField
|
|
86
|
+
id={searchId}
|
|
87
|
+
class="cinder-faceted-filter-bar__search"
|
|
88
|
+
value={currentSearchQuery}
|
|
89
|
+
placeholder={searchPlaceholder}
|
|
90
|
+
aria-label={searchAriaLabel}
|
|
91
|
+
{disabled}
|
|
92
|
+
oninput={handleSearchInput}
|
|
93
|
+
/>
|
|
94
|
+
{/if}
|
|
93
95
|
|
|
94
96
|
{#each facets as facet (facet.key)}
|
|
95
97
|
{#if facet.type === 'select'}
|
|
@@ -69,6 +69,8 @@ export type FacetedFilterBarProps = Omit<HTMLAttributes<HTMLDivElement>, 'class'
|
|
|
69
69
|
'aria-label'?: string;
|
|
70
70
|
/** Current text search query. When provided, the search field is controlled. */
|
|
71
71
|
searchQuery?: string;
|
|
72
|
+
/** Whether to render the leading search field. Defaults to `true`. */
|
|
73
|
+
showSearch?: boolean;
|
|
72
74
|
/** Placeholder text shown in the leading search field. */
|
|
73
75
|
searchPlaceholder?: string;
|
|
74
76
|
/** Accessible label for the search input. Defaults to 'Search'. */
|
|
@@ -208,7 +208,7 @@ function handleKeyDown(event) {
|
|
|
208
208
|
{@render menuToggle({
|
|
209
209
|
'aria-expanded': (mobileMenuOpen ? 'true' : 'false') as 'true' | 'false',
|
|
210
210
|
'aria-controls': regionId,
|
|
211
|
-
|
|
211
|
+
onclick: handleToggle,
|
|
212
212
|
})}
|
|
213
213
|
</div>
|
|
214
214
|
{/if}
|
|
@@ -224,7 +224,7 @@ function handleKeyDown(event) {
|
|
|
224
224
|
{@render menuToggle({
|
|
225
225
|
'aria-expanded': (mobileMenuOpen ? 'true' : 'false') as 'true' | 'false',
|
|
226
226
|
'aria-controls': regionId,
|
|
227
|
-
|
|
227
|
+
onclick: handleToggle,
|
|
228
228
|
})}
|
|
229
229
|
</div>
|
|
230
230
|
{/if}
|
|
@@ -10,11 +10,11 @@ import RunStepBranchDisclosure from './run-step-branch-disclosure.svelte';
|
|
|
10
10
|
import Link from '../link/link.svelte';
|
|
11
11
|
import Progress from '../progress/progress.svelte';
|
|
12
12
|
import StatusDot from '../status-dot/status-dot.svelte';
|
|
13
|
-
import { actionsCountLabel, badgeVariant, branchGroupHasCurrentStep, branchOutcomeSummary, branchStartsCollapsed, hasProgress, hiddenNestedStepLabel, isCurrent, isTerminal, laneOutcomeBadgeVariant, laneOutcomeLabel, metadataItems, safeStepLinkHref, statusDotStatus, statusLabel, } from './run-step-timeline.utilities.ts';
|
|
13
|
+
import { actionsCountLabel, badgeVariant, branchGroupHasCurrentStep, branchOutcomeSummary, branchStartsCollapsed, hasProgress, hiddenNestedStepLabel, isCurrent, isTerminal, laneOutcomeBadgeVariant, laneOutcomeLabel, metadataItems, relocateCompensationEntries, relocateCompensationSteps, safeStepLinkHref, statusDotStatus, statusLabel, } from './run-step-timeline.utilities.ts';
|
|
14
14
|
const MAX_NESTED_STEP_DEPTH = 3;
|
|
15
15
|
let { steps, label, class: className, children, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, ...rest } = $props();
|
|
16
16
|
const resolvedAriaLabel = $derived(ariaLabelledby === undefined && ariaLabel === undefined ? label : ariaLabel);
|
|
17
|
-
const renderedEntries = $derived(flattenEntries(steps));
|
|
17
|
+
const renderedEntries = $derived(flattenEntries(relocateCompensationEntries(steps)));
|
|
18
18
|
function isBranchGroup(entry) {
|
|
19
19
|
return 'kind' in entry && entry.kind === 'branch';
|
|
20
20
|
}
|
|
@@ -128,7 +128,7 @@ function applyGlobalRailAriaCurrent(rows) {
|
|
|
128
128
|
// compensation labels. Reused for the main rail and for each branch lane.
|
|
129
129
|
function flattenSteps(list, pathPrefix) {
|
|
130
130
|
const rows = [];
|
|
131
|
-
appendRunStepRows(rows, list, 0, pathPrefix);
|
|
131
|
+
appendRunStepRows(rows, relocateCompensationSteps(list), 0, pathPrefix);
|
|
132
132
|
const currentRowIndex = deepestCurrentStepIndex(rows);
|
|
133
133
|
// Index every step row by its unique path key (built from the escaped id
|
|
134
134
|
// chain). A `compensates` id then resolves to a true SIBLING — a step under
|
|
@@ -174,7 +174,7 @@ function appendRunStepRows(rows, list, depth, pathPrefix) {
|
|
|
174
174
|
rows.push({ kind: 'step', step, depth, pathKey });
|
|
175
175
|
if (step.children && step.children.length > 0) {
|
|
176
176
|
if (depth < MAX_NESTED_STEP_DEPTH) {
|
|
177
|
-
appendRunStepRows(rows, step.children, depth + 1, pathKey);
|
|
177
|
+
appendRunStepRows(rows, relocateCompensationSteps(step.children), depth + 1, pathKey);
|
|
178
178
|
}
|
|
179
179
|
else {
|
|
180
180
|
const hiddenSummary = summarizeNestedRunSteps(step.children);
|
|
@@ -5,8 +5,76 @@ import type {
|
|
|
5
5
|
RunStepBranchLane,
|
|
6
6
|
RunStepBranchLaneOutcome,
|
|
7
7
|
RunStepStatus,
|
|
8
|
+
RunStepTimelineEntry,
|
|
8
9
|
} from './run-step-timeline.types.ts';
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Relocate resolved compensation steps immediately after the subtree of the
|
|
13
|
+
* sibling they reverse. Unresolved, self-referential, and cyclic links retain
|
|
14
|
+
* consumer order. Multiple compensations retain their relative input order.
|
|
15
|
+
*/
|
|
16
|
+
export function relocateCompensationSteps(steps: RunStep[]): RunStep[] {
|
|
17
|
+
return relocateSiblingItems(steps, (step) => step);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function relocateSiblingItems<T>(items: T[], getStep: (item: T) => RunStep | undefined): T[] {
|
|
21
|
+
if (!items.some((item) => getStep(item)?.compensates !== undefined)) return items;
|
|
22
|
+
|
|
23
|
+
const stepById = new Map<string, RunStep>();
|
|
24
|
+
for (const item of items) {
|
|
25
|
+
const step = getStep(item);
|
|
26
|
+
if (step !== undefined) {
|
|
27
|
+
stepById.set(step.id, step);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const canRelocate = (step: RunStep): boolean => {
|
|
31
|
+
const visited = new Set<string>([step.id]);
|
|
32
|
+
let targetId = step.compensates;
|
|
33
|
+
while (targetId !== undefined) {
|
|
34
|
+
const target = stepById.get(targetId);
|
|
35
|
+
if (target === undefined || visited.has(targetId)) return false;
|
|
36
|
+
visited.add(targetId);
|
|
37
|
+
targetId = target.compensates;
|
|
38
|
+
}
|
|
39
|
+
return true;
|
|
40
|
+
};
|
|
41
|
+
const relocatable = new Set<RunStep>();
|
|
42
|
+
const compensationsByTarget = new Map<string, T[]>();
|
|
43
|
+
for (const item of items) {
|
|
44
|
+
const step = getStep(item);
|
|
45
|
+
if (step === undefined || step.compensates === undefined || !canRelocate(step)) continue;
|
|
46
|
+
relocatable.add(step);
|
|
47
|
+
const compensations = compensationsByTarget.get(step.compensates) ?? [];
|
|
48
|
+
compensations.push(item);
|
|
49
|
+
compensationsByTarget.set(step.compensates, compensations);
|
|
50
|
+
}
|
|
51
|
+
const result: T[] = [];
|
|
52
|
+
const appendWithCompensations = (item: T): void => {
|
|
53
|
+
result.push(item);
|
|
54
|
+
const step = getStep(item);
|
|
55
|
+
if (step === undefined) return;
|
|
56
|
+
for (const compensation of compensationsByTarget.get(step.id) ?? []) {
|
|
57
|
+
appendWithCompensations(compensation);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
for (const item of items) {
|
|
61
|
+
const step = getStep(item);
|
|
62
|
+
if (step === undefined || !relocatable.has(step)) appendWithCompensations(item);
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Relocate top-level compensations while preserving branch rows as rail entries. */
|
|
68
|
+
export function relocateCompensationEntries(
|
|
69
|
+
entries: RunStepTimelineEntry[],
|
|
70
|
+
): RunStepTimelineEntry[] {
|
|
71
|
+
return relocateSiblingItems(entries, (entry) => (isBranchGroup(entry) ? undefined : entry));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isBranchGroup(entry: RunStepTimelineEntry): entry is RunStepBranchGroup {
|
|
75
|
+
return 'kind' in entry && entry.kind === 'branch';
|
|
76
|
+
}
|
|
77
|
+
|
|
10
78
|
/** Badge variants used for the per-step status chip. */
|
|
11
79
|
export type RunStepBadgeVariant = 'neutral' | 'success' | 'warning' | 'danger' | 'info' | 'accent';
|
|
12
80
|
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* ======================================== */
|
|
14
14
|
|
|
15
15
|
.cinder-side-navigation {
|
|
16
|
+
--cinder-side-navigation-list-gap: var(--cinder-space-1);
|
|
16
17
|
display: block;
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
padding: 0;
|
|
23
24
|
display: flex;
|
|
24
25
|
flex-direction: column;
|
|
25
|
-
gap: var(--cinder-space-1);
|
|
26
|
+
gap: var(--cinder-side-navigation-list-gap, var(--cinder-space-1));
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
/* ----------------------------------------
|
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
"description": "A grouped sidebar nav. Uses the SideNavigation namespace API: SideNavigation.Group wraps a collapsible section and SideNavigation.Item renders each link.",
|
|
10
10
|
"code": "<script lang=\"ts\">\n import { SideNavigation } from '@lostgradient/cinder/side-navigation';\n</script>\n\n<SideNavigation ariaLabel=\"Workspace\">\n <SideNavigation.Item href=\"/dashboard\">Dashboard</SideNavigation.Item>\n <SideNavigation.Group label=\"Projects\">\n <SideNavigation.Item href=\"/projects/phoenix\" active>Phoenix</SideNavigation.Item>\n <SideNavigation.Item href=\"/projects/atlas\">Atlas</SideNavigation.Item>\n <SideNavigation.Item href=\"/projects/beacon\">Beacon</SideNavigation.Item>\n </SideNavigation.Group>\n <SideNavigation.Group label=\"Settings\">\n <SideNavigation.Item href=\"/settings/team\">Team</SideNavigation.Item>\n <SideNavigation.Item href=\"/settings/billing\">Billing</SideNavigation.Item>\n </SideNavigation.Group>\n</SideNavigation>\n"
|
|
11
11
|
},
|
|
12
|
+
{
|
|
13
|
+
"id": "compact-sidebar",
|
|
14
|
+
"title": "Compact sidebar spacing",
|
|
15
|
+
"description": "Dense sidebars can tune the SideNavigation list gap through a public CSS variable.",
|
|
16
|
+
"code": "<script lang=\"ts\">\n import { SideNavigation } from '@lostgradient/cinder/side-navigation';\n</script>\n\n<div style=\"max-inline-size: 18rem;\">\n <SideNavigation ariaLabel=\"Inspector sections\" style=\"--cinder-side-navigation-list-gap: 0;\">\n <SideNavigation.Item href=\"#overview\" active>Overview</SideNavigation.Item>\n <SideNavigation.Item href=\"#timeline\">Timeline</SideNavigation.Item>\n <SideNavigation.Item href=\"#payload\">Payload</SideNavigation.Item>\n <SideNavigation.Group label=\"Artifacts\">\n <SideNavigation.Item href=\"#source\">Source</SideNavigation.Item>\n <SideNavigation.Item href=\"#snapshot\">Snapshot</SideNavigation.Item>\n </SideNavigation.Group>\n </SideNavigation>\n</div>\n"
|
|
17
|
+
},
|
|
12
18
|
{
|
|
13
19
|
"id": "docs-sidebar",
|
|
14
20
|
"title": "Docs sidebar",
|
|
@@ -5,6 +5,14 @@
|
|
|
5
5
|
"ariaLabel": {
|
|
6
6
|
"type": "string",
|
|
7
7
|
"description": "Accessible name for the <nav> landmark. Required, non-empty, distinct from other navs on the page."
|
|
8
|
+
},
|
|
9
|
+
"class": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Additional CSS class merged with `.cinder-side-navigation`."
|
|
12
|
+
},
|
|
13
|
+
"style": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Inline style string applied to the `.cinder-side-navigation` root."
|
|
8
16
|
}
|
|
9
17
|
},
|
|
10
18
|
"additionalProperties": false,
|
|
@@ -16,11 +24,6 @@
|
|
|
16
24
|
"reason": "function-or-snippet",
|
|
17
25
|
"required": true,
|
|
18
26
|
"description": "Must be <li> elements containing NavigationItem and/or SideNavigationGroup."
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"name": "class",
|
|
22
|
-
"reason": "unknown-shape",
|
|
23
|
-
"description": "Additional CSS class merged with `.cinder-side-navigation`."
|
|
24
27
|
}
|
|
25
28
|
]
|
|
26
29
|
}
|
|
@@ -9,6 +9,14 @@ const schema = {
|
|
|
9
9
|
description:
|
|
10
10
|
'Accessible name for the <nav> landmark. Required, non-empty, distinct from other navs on the page.',
|
|
11
11
|
},
|
|
12
|
+
class: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
description: 'Additional CSS class merged with `.cinder-side-navigation`.',
|
|
15
|
+
},
|
|
16
|
+
style: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
description: 'Inline style string applied to the `.cinder-side-navigation` root.',
|
|
19
|
+
},
|
|
12
20
|
},
|
|
13
21
|
additionalProperties: false,
|
|
14
22
|
required: ['ariaLabel'],
|
|
@@ -20,11 +28,6 @@ const schema = {
|
|
|
20
28
|
required: true,
|
|
21
29
|
description: 'Must be <li> elements containing NavigationItem and/or SideNavigationGroup.',
|
|
22
30
|
},
|
|
23
|
-
{
|
|
24
|
-
name: 'class',
|
|
25
|
-
reason: 'unknown-shape',
|
|
26
|
-
description: 'Additional CSS class merged with `.cinder-side-navigation`.',
|
|
27
|
-
},
|
|
28
31
|
],
|
|
29
32
|
},
|
|
30
33
|
} satisfies ComponentSchema;
|
|
@@ -12,3 +12,15 @@ export type SideNavigationProps = Omit<
|
|
|
12
12
|
/** Must be <li> elements containing NavigationItem and/or SideNavigationGroup. */
|
|
13
13
|
children: Snippet;
|
|
14
14
|
};
|
|
15
|
+
|
|
16
|
+
/** Schema generator surface for SideNavigation — excludes native attributes except supported styling hooks. */
|
|
17
|
+
export interface SideNavigationSchemaProps {
|
|
18
|
+
/** Accessible name for the <nav> landmark. Required, non-empty, distinct from other navs on the page. */
|
|
19
|
+
ariaLabel: string;
|
|
20
|
+
/** Additional CSS class merged with `.cinder-side-navigation`. */
|
|
21
|
+
class?: string;
|
|
22
|
+
/** Inline style string applied to the `.cinder-side-navigation` root. */
|
|
23
|
+
style?: string;
|
|
24
|
+
/** Must be <li> elements containing NavigationItem and/or SideNavigationGroup. */
|
|
25
|
+
children: Snippet;
|
|
26
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
[]
|
|
1
|
+
["--cinder-side-navigation-list-gap"]
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
@layer cinder.tokens, cinder.foundation, cinder.components, cinder.utilities;
|
|
2
2
|
@layer cinder.components {
|
|
3
3
|
.cinder-stat {
|
|
4
|
+
--cinder-stat-row-gap: var(--cinder-space-1, 0.25rem);
|
|
5
|
+
--cinder-stat-column-gap: var(--cinder-space-3, 0.75rem);
|
|
6
|
+
--cinder-stat-label-font-size: var(--cinder-text-sm);
|
|
7
|
+
--cinder-stat-value-font-size: var(--cinder-text-4xl);
|
|
8
|
+
--cinder-stat-value-font-weight: var(--cinder-font-semibold);
|
|
9
|
+
--cinder-stat-value-line-height: 1.1;
|
|
10
|
+
--cinder-stat-change-gap: var(--cinder-space-1, 0.25rem);
|
|
11
|
+
--cinder-stat-change-font-size: var(--cinder-text-sm);
|
|
4
12
|
display: grid;
|
|
5
13
|
grid-template-columns: 1fr;
|
|
6
14
|
grid-template-rows: auto auto auto;
|
|
7
|
-
row-gap: var(--cinder-space-1, 0.25rem);
|
|
15
|
+
row-gap: var(--cinder-stat-row-gap, var(--cinder-space-1, 0.25rem));
|
|
8
16
|
align-items: start;
|
|
9
17
|
color: var(--cinder-text);
|
|
10
18
|
}
|
|
11
19
|
|
|
12
20
|
.cinder-stat[data-cinder-has-icon] {
|
|
13
21
|
grid-template-columns: auto 1fr;
|
|
14
|
-
column-gap: var(--cinder-space-3, 0.75rem);
|
|
22
|
+
column-gap: var(--cinder-stat-column-gap, var(--cinder-space-3, 0.75rem));
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
.cinder-stat__icon {
|
|
@@ -25,7 +33,7 @@
|
|
|
25
33
|
}
|
|
26
34
|
|
|
27
35
|
.cinder-stat__label {
|
|
28
|
-
font-size: var(--cinder-text-sm);
|
|
36
|
+
font-size: var(--cinder-stat-label-font-size, var(--cinder-text-sm));
|
|
29
37
|
color: var(--cinder-text-muted);
|
|
30
38
|
}
|
|
31
39
|
|
|
@@ -34,10 +42,10 @@
|
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
.cinder-stat__value {
|
|
37
|
-
font-size: var(--cinder-text-4xl);
|
|
38
|
-
font-weight: var(--cinder-font-semibold);
|
|
45
|
+
font-size: var(--cinder-stat-value-font-size, var(--cinder-text-4xl));
|
|
46
|
+
font-weight: var(--cinder-stat-value-font-weight, var(--cinder-font-semibold));
|
|
39
47
|
font-variant-numeric: tabular-nums;
|
|
40
|
-
line-height: 1.1;
|
|
48
|
+
line-height: var(--cinder-stat-value-line-height, 1.1);
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
.cinder-stat[data-cinder-has-icon] .cinder-stat__value {
|
|
@@ -47,8 +55,8 @@
|
|
|
47
55
|
.cinder-stat__change {
|
|
48
56
|
display: inline-flex;
|
|
49
57
|
align-items: center;
|
|
50
|
-
gap: var(--cinder-space-1, 0.25rem);
|
|
51
|
-
font-size: var(--cinder-text-sm);
|
|
58
|
+
gap: var(--cinder-stat-change-gap, var(--cinder-space-1, 0.25rem));
|
|
59
|
+
font-size: var(--cinder-stat-change-font-size, var(--cinder-text-sm));
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
.cinder-stat[data-cinder-has-icon] .cinder-stat__change {
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"class": {
|
|
25
25
|
"type": "string",
|
|
26
26
|
"description": "Additional class names merged with `.cinder-stat`."
|
|
27
|
+
},
|
|
28
|
+
"style": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"description": "Inline style string applied to the `.cinder-stat` root."
|
|
27
31
|
}
|
|
28
32
|
},
|
|
29
33
|
"additionalProperties": false,
|
|
@@ -28,6 +28,10 @@ const schema = {
|
|
|
28
28
|
type: 'string',
|
|
29
29
|
description: 'Additional class names merged with `.cinder-stat`.',
|
|
30
30
|
},
|
|
31
|
+
style: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: 'Inline style string applied to the `.cinder-stat` root.',
|
|
34
|
+
},
|
|
31
35
|
},
|
|
32
36
|
additionalProperties: false,
|
|
33
37
|
required: ['label', 'value'],
|
|
@@ -33,3 +33,23 @@ export type StatProps = Omit<HTMLAttributes<HTMLDivElement>, 'class'> & {
|
|
|
33
33
|
/** Additional class names merged with `.cinder-stat`. */
|
|
34
34
|
class?: string;
|
|
35
35
|
};
|
|
36
|
+
|
|
37
|
+
/** Schema generator surface for Stat — excludes native attributes except supported styling hooks. */
|
|
38
|
+
export interface StatSchemaProps {
|
|
39
|
+
/** Short label describing the metric, e.g. "Monthly Revenue". */
|
|
40
|
+
label: string;
|
|
41
|
+
/** The statistic. Strings rendered verbatim; numbers formatted via formatNumber. */
|
|
42
|
+
value: string | number;
|
|
43
|
+
/** Optional change indicator with direction and accessible wording. */
|
|
44
|
+
change?: StatChange;
|
|
45
|
+
/** Optional leading icon snippet (decorative — wrapper is aria-hidden). */
|
|
46
|
+
icon?: Snippet;
|
|
47
|
+
/** Intl.NumberFormat options applied only when `value` is a number. */
|
|
48
|
+
valueFormatOptions?: Intl.NumberFormatOptions;
|
|
49
|
+
/** Locale forwarded to formatNumber. Defaults to the nearest LocaleProvider locale, then en-US. */
|
|
50
|
+
valueLocale?: string;
|
|
51
|
+
/** Additional class names merged with `.cinder-stat`. */
|
|
52
|
+
class?: string;
|
|
53
|
+
/** Inline style string applied to the `.cinder-stat` root. */
|
|
54
|
+
style?: string;
|
|
55
|
+
}
|
|
@@ -1 +1,10 @@
|
|
|
1
|
-
[
|
|
1
|
+
[
|
|
2
|
+
"--cinder-stat-change-font-size",
|
|
3
|
+
"--cinder-stat-change-gap",
|
|
4
|
+
"--cinder-stat-column-gap",
|
|
5
|
+
"--cinder-stat-label-font-size",
|
|
6
|
+
"--cinder-stat-row-gap",
|
|
7
|
+
"--cinder-stat-value-font-size",
|
|
8
|
+
"--cinder-stat-value-font-weight",
|
|
9
|
+
"--cinder-stat-value-line-height"
|
|
10
|
+
]
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
-
const variables: readonly string[] = [
|
|
1
|
+
const variables: readonly string[] = [
|
|
2
|
+
'--cinder-stat-change-font-size',
|
|
3
|
+
'--cinder-stat-change-gap',
|
|
4
|
+
'--cinder-stat-column-gap',
|
|
5
|
+
'--cinder-stat-label-font-size',
|
|
6
|
+
'--cinder-stat-row-gap',
|
|
7
|
+
'--cinder-stat-value-font-size',
|
|
8
|
+
'--cinder-stat-value-font-weight',
|
|
9
|
+
'--cinder-stat-value-line-height',
|
|
10
|
+
];
|
|
2
11
|
|
|
3
12
|
export default variables;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
@layer cinder.tokens, cinder.foundation, cinder.components, cinder.utilities;
|
|
2
2
|
@layer cinder.components {
|
|
3
3
|
.cinder-stat-group {
|
|
4
|
+
--cinder-stat-group-gap: var(--cinder-space-4, 1rem);
|
|
5
|
+
--cinder-stat-group-card-padding: var(--cinder-space-4, 1rem);
|
|
6
|
+
--cinder-stat-group-shared-cell-padding: var(--cinder-space-4, 1rem);
|
|
4
7
|
display: grid;
|
|
5
|
-
gap: var(--cinder-space-4, 1rem);
|
|
8
|
+
gap: var(--cinder-stat-group-gap, var(--cinder-space-4, 1rem));
|
|
6
9
|
/* Query the group's own inline size so fixed column counts can collapse when
|
|
7
10
|
the group is narrow — even inside a wide viewport (RESPONSIVE-POLICY.md). */
|
|
8
11
|
container-type: inline-size;
|
|
@@ -50,7 +53,7 @@
|
|
|
50
53
|
|
|
51
54
|
/* variant: cards — each stat gets a card-style border, background, and shadow */
|
|
52
55
|
.cinder-stat-group[data-cinder-variant='cards'] > .cinder-stat {
|
|
53
|
-
padding: var(--cinder-space-4, 1rem);
|
|
56
|
+
padding: var(--cinder-stat-group-card-padding, var(--cinder-space-4, 1rem));
|
|
54
57
|
background: var(--cinder-surface-raised);
|
|
55
58
|
border: 1px solid var(--cinder-border);
|
|
56
59
|
border-radius: var(--cinder-radius-md, 0.5rem);
|
|
@@ -63,15 +66,15 @@
|
|
|
63
66
|
* gap with the surface color, avoiding nth-child math for any column count.
|
|
64
67
|
*/
|
|
65
68
|
.cinder-stat-group[data-cinder-variant='shared-borders'] {
|
|
69
|
+
--cinder-stat-group-gap: 1px;
|
|
66
70
|
border: 1px solid var(--cinder-border);
|
|
67
71
|
border-radius: var(--cinder-radius-md, 0.5rem);
|
|
68
72
|
overflow: hidden;
|
|
69
73
|
background: var(--cinder-border);
|
|
70
|
-
gap: 1px;
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
.cinder-stat-group[data-cinder-variant='shared-borders'] > .cinder-stat {
|
|
74
|
-
padding: var(--cinder-space-4, 1rem);
|
|
77
|
+
padding: var(--cinder-stat-group-shared-cell-padding, var(--cinder-space-4, 1rem));
|
|
75
78
|
background: var(--cinder-surface-raised);
|
|
76
79
|
}
|
|
77
80
|
}
|
|
@@ -21,6 +21,12 @@
|
|
|
21
21
|
"description": "The `cards` variant renders each metric as a discrete bordered card, separating the tiles instead of laying them out edge-to-edge in a shared grid.",
|
|
22
22
|
"code": "<script lang=\"ts\">\n import { StatGroup } from '@lostgradient/cinder/stat-group';\n</script>\n\n<StatGroup label=\"This quarter\" columns={3} variant=\"cards\">\n <StatGroup.Stat label=\"Projects\" value={42} />\n <StatGroup.Stat label=\"Members\" value={18} />\n <StatGroup.Stat label=\"Storage\" value=\"64 GB\" />\n</StatGroup>\n"
|
|
23
23
|
},
|
|
24
|
+
{
|
|
25
|
+
"id": "compact-summary",
|
|
26
|
+
"title": "Compact summary strip",
|
|
27
|
+
"description": "Dense summaries can tune StatGroup spacing and Stat typography through public CSS variables.",
|
|
28
|
+
"code": "<script lang=\"ts\">\n import { StatGroup } from '@lostgradient/cinder/stat-group';\n</script>\n\n<StatGroup\n label=\"Workflow summary\"\n columns={4}\n variant=\"shared-borders\"\n style=\"--cinder-stat-group-gap: 1px; --cinder-stat-group-shared-cell-padding: var(--cinder-space-2);\"\n>\n <StatGroup.Stat\n label=\"Events\"\n value={128}\n style=\"--cinder-stat-row-gap: 0; --cinder-stat-label-font-size: var(--cinder-text-xs); --cinder-stat-value-font-size: var(--cinder-text-lg); --cinder-stat-change-font-size: var(--cinder-text-xs);\"\n />\n <StatGroup.Stat\n label=\"Matched\"\n value={42}\n style=\"--cinder-stat-row-gap: 0; --cinder-stat-label-font-size: var(--cinder-text-xs); --cinder-stat-value-font-size: var(--cinder-text-lg); --cinder-stat-change-font-size: var(--cinder-text-xs);\"\n />\n <StatGroup.Stat\n label=\"Retries\"\n value={3}\n style=\"--cinder-stat-row-gap: 0; --cinder-stat-label-font-size: var(--cinder-text-xs); --cinder-stat-value-font-size: var(--cinder-text-lg); --cinder-stat-change-font-size: var(--cinder-text-xs);\"\n />\n <StatGroup.Stat\n label=\"Latency\"\n value=\"184 ms\"\n style=\"--cinder-stat-row-gap: 0; --cinder-stat-label-font-size: var(--cinder-text-xs); --cinder-stat-value-font-size: var(--cinder-text-lg); --cinder-stat-change-font-size: var(--cinder-text-xs);\"\n />\n</StatGroup>\n"
|
|
29
|
+
},
|
|
24
30
|
{
|
|
25
31
|
"id": "shared-borders-variant",
|
|
26
32
|
"title": "Shared-borders variant",
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"class": {
|
|
20
20
|
"type": "string",
|
|
21
21
|
"description": "Additional class names merged with `.cinder-stat-group`."
|
|
22
|
+
},
|
|
23
|
+
"style": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Inline style string applied to the `.cinder-stat-group` root."
|
|
22
26
|
}
|
|
23
27
|
},
|
|
24
28
|
"additionalProperties": false,
|