@siyahi/ui 0.1.9-next.31 → 0.1.9-next.32
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/DESIGN.md +4 -3
- package/agents/examples/data-table.tsx +4 -1
- package/agents/examples/metric-strip.tsx +2 -0
- package/dist/components/registry/dashboardcn/kpi-row-card.d.ts +2 -0
- package/dist/components/registry/dashboardcn/kpi-row-card.js +1 -0
- package/dist/components/registry/niko-table/core/data-table-structure.d.ts +8 -1
- package/dist/components/registry/niko-table/core/data-table-structure.js +9 -5
- package/dist/components/registry/niko-table/lib/styles.js +1 -1
- package/package.json +1 -1
- package/styles/siyahi.css +39 -6
package/DESIGN.md
CHANGED
|
@@ -51,9 +51,10 @@ a meaning:
|
|
|
51
51
|
colour, not separate hues.
|
|
52
52
|
- **Colour marks exceptions.** The one stat tile or table row that needs a look
|
|
53
53
|
is marked by the page: `tone: "negative"` or `"warning"` on a `SectionCards`
|
|
54
|
-
item, `data-tone="negative"` or `"warning"` on a `TableRow
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
or `MetricStrip` item, `data-tone="negative"` or `"warning"` on a `TableRow`,
|
|
55
|
+
and `getRowTone` on a `DataTableBody`, returning the tone for the row. It
|
|
56
|
+
takes a faint wash of the colour over the card (7% in light, 12% in dark),
|
|
57
|
+
and a tile or figure an outline of it. The page decides, never the sign: a falling cost is good news.
|
|
57
58
|
Mark one or two; a page where every row is washed marks nothing.
|
|
58
59
|
|
|
59
60
|
## Surfaces
|
|
@@ -36,6 +36,9 @@ const products: Product[] = [
|
|
|
36
36
|
{ product: "Desk lamp", category: "Lighting", units: 900, revenue: 255600, change: 0.009 },
|
|
37
37
|
];
|
|
38
38
|
|
|
39
|
+
// The page's own rule for a product that needs a look; a falling figure alone would not be one.
|
|
40
|
+
const tone = (product: Product): "warning" | undefined => (product.units < 50 ? "warning" : undefined);
|
|
41
|
+
|
|
39
42
|
// One click sorts largest (or Z) first; the title comes from meta.label.
|
|
40
43
|
const header = () => <DataTableSortHeader />;
|
|
41
44
|
|
|
@@ -87,7 +90,7 @@ export default function DataTableExample() {
|
|
|
87
90
|
</DataTableToolbarSection>
|
|
88
91
|
<DataTable aria-label="Products">
|
|
89
92
|
<DataTableHeader />
|
|
90
|
-
<DataTableBody />
|
|
93
|
+
<DataTableBody getRowTone={tone} />
|
|
91
94
|
</DataTable>
|
|
92
95
|
</DataTableRoot>
|
|
93
96
|
</CardContent>
|
|
@@ -25,6 +25,8 @@ export default function MetricStripExample() {
|
|
|
25
25
|
change: -61200,
|
|
26
26
|
changeFormat: "money",
|
|
27
27
|
trend: [14200, -3800, 9100, 2600, -18400, -61200],
|
|
28
|
+
// The one figure on the strip that needs a look, marked by the page: a falling margin alone would not be.
|
|
29
|
+
tone: "negative",
|
|
28
30
|
},
|
|
29
31
|
{ label: "Gift cards", value: moneyCompact(38400), change: 0.18, trend: [4100, 0, 12800, 3300, 9900, 8300] },
|
|
30
32
|
{ label: "Products", value: count(42), note: "3 added in Oct", trend: [38, 39, 39, 40, 39, 42] },
|
|
@@ -13,6 +13,8 @@ export interface MetricStripItem {
|
|
|
13
13
|
note?: React$1.ReactNode;
|
|
14
14
|
/** Recent values, oldest first, drawn as a sparkline beside the figure. */
|
|
15
15
|
trend?: number[];
|
|
16
|
+
/** Marks the figure as the exception on the page: a faint wash of the colour and an outline of it. */
|
|
17
|
+
tone?: "negative" | "warning";
|
|
16
18
|
}
|
|
17
19
|
export interface MetricStripProps extends Omit<React$1.ComponentProps<typeof Card>, "title"> {
|
|
18
20
|
title?: React$1.ReactNode;
|
|
@@ -39,6 +39,7 @@ function MetricStrip({ title, description, action, items, footer, className, ...
|
|
|
39
39
|
className: "-me-px -mb-px flex flex-wrap",
|
|
40
40
|
children: items.map((item) => /* @__PURE__ */ jsxs("div", {
|
|
41
41
|
"data-slot": "metric-strip-item",
|
|
42
|
+
"data-tone": item.tone,
|
|
42
43
|
className: cn("flex min-w-0 flex-col gap-1.5 border-e border-b px-4 py-4", columns),
|
|
43
44
|
children: [/* @__PURE__ */ jsx("div", {
|
|
44
45
|
className: "truncate font-mono text-2xs tracking-wider text-muted-foreground uppercase",
|
|
@@ -40,8 +40,15 @@ export interface DataTableBodyProps<TData extends RowData> {
|
|
|
40
40
|
* Wrap the callback in `useCallback` so memoized rows don't re-render.
|
|
41
41
|
*/
|
|
42
42
|
renderRowContextMenu?: (row: TData) => React.ReactNode;
|
|
43
|
+
/**
|
|
44
|
+
* Mark the row that needs a look, such as a holding in deep loss: return
|
|
45
|
+
* `"negative"` or `"warning"` and the row takes a faint wash of that
|
|
46
|
+
* colour, as a `TableRow` with `data-tone` does; `undefined` leaves it
|
|
47
|
+
* plain. The page decides, never the sign. Mark one or two rows.
|
|
48
|
+
*/
|
|
49
|
+
getRowTone?: (row: TData) => "negative" | "warning" | undefined;
|
|
43
50
|
}
|
|
44
|
-
export declare function DataTableBody<TData extends RowData>({ children, className, onScroll, onScrolledTop, onScrolledBottom, scrollThreshold, onRowClick, getRowMemoKey, renderRowContextMenu }: DataTableBodyProps<TData>): React.JSX.Element;
|
|
51
|
+
export declare function DataTableBody<TData extends RowData>({ children, className, onScroll, onScrolledTop, onScrolledBottom, scrollThreshold, onRowClick, getRowMemoKey, renderRowContextMenu, getRowTone }: DataTableBodyProps<TData>): React.JSX.Element;
|
|
45
52
|
export declare namespace DataTableBody {
|
|
46
53
|
var displayName: string;
|
|
47
54
|
}
|
|
@@ -27,7 +27,7 @@ const DataTableHeader = React.memo(function DataTableHeader({ className, sticky
|
|
|
27
27
|
const flexColumnIds = resolveFlexColumnIds(table, !isCompact);
|
|
28
28
|
const columnSizing = table.state.columnSizing;
|
|
29
29
|
return /* @__PURE__ */ jsx(TableHeader, {
|
|
30
|
-
className: cn(sticky && "sticky top-0 z-30 bg-
|
|
30
|
+
className: cn(sticky && "sticky top-0 z-30 bg-(--table-fill)", sticky && "after:absolute after:right-0 after:bottom-0 after:left-0 after:h-px after:bg-border", className),
|
|
31
31
|
children: headerGroups.map((headerGroup) => /* @__PURE__ */ jsx(TableRow, { children: headerGroup.headers.map((header) => {
|
|
32
32
|
const isFlex = flexColumnIds.has(header.column.id);
|
|
33
33
|
const headerStyle = {
|
|
@@ -62,7 +62,7 @@ const DataTableHeader = React.memo(function DataTableHeader({ className, sticky
|
|
|
62
62
|
DataTableHeader.displayName = "DataTableHeader";
|
|
63
63
|
/** Fade-pulse animation applied to a flashing cell (keyframe in the provider). */
|
|
64
64
|
const FLASH_ANIMATION = "niko-row-flash 1.2s ease-out";
|
|
65
|
-
const BodyRow = React.memo(function BodyRow({ row, displayIndex, expandColumnId, isCompact, isClickable, isExpanded, isSelected, columnWidths, isRowFlashing, flashingCellKeys, renderRowContextMenu }) {
|
|
65
|
+
const BodyRow = React.memo(function BodyRow({ row, displayIndex, expandColumnId, isCompact, isClickable, isExpanded, isSelected, columnWidths, isRowFlashing, flashingCellKeys, renderRowContextMenu, tone }) {
|
|
66
66
|
const expandCell = isExpanded && expandColumnId ? row.getAllCells().find((c) => c.column.id === expandColumnId) : void 0;
|
|
67
67
|
const visibleCells = row.getVisibleCells();
|
|
68
68
|
const rowElement = /* @__PURE__ */ jsx(TableRow, {
|
|
@@ -72,8 +72,10 @@ const BodyRow = React.memo(function BodyRow({ row, displayIndex, expandColumnId,
|
|
|
72
72
|
"data-parity": displayIndex % 2 === 0 ? "even" : "odd",
|
|
73
73
|
"data-expanded": isExpanded ? "true" : void 0,
|
|
74
74
|
"data-state": isSelected ? "selected" : void 0,
|
|
75
|
+
"data-tone": tone,
|
|
75
76
|
className: cn(isClickable && "cursor-pointer", "group data-[context-menu-open]:bg-muted/50"),
|
|
76
77
|
children: visibleCells.map((cell) => {
|
|
78
|
+
const pinned = !isCompact && cell.column.getIsPinned();
|
|
77
79
|
const flashing = isRowFlashing || flashingCellKeys.has(flashCellKey(row.id, cell.column.id));
|
|
78
80
|
const cellStyle = {
|
|
79
81
|
width: columnWidths.get(cell.column.id),
|
|
@@ -84,8 +86,9 @@ const BodyRow = React.memo(function BodyRow({ row, displayIndex, expandColumnId,
|
|
|
84
86
|
"data-flash": flashing ? "true" : void 0,
|
|
85
87
|
"data-col-id": cell.column.id,
|
|
86
88
|
"data-numeric": cell.column.columnDef.meta?.variant === "number" || void 0,
|
|
89
|
+
"data-pinned": pinned || void 0,
|
|
87
90
|
style: cellStyle,
|
|
88
|
-
className: cn(!cell.getIsGrouped() && "truncate",
|
|
91
|
+
className: cn(!cell.getIsGrouped() && "truncate", pinned && "bg-linear-to-r group-hover:from-muted/50 group-hover:to-muted/50 group-data-[context-menu-open]:from-muted/50 group-data-[context-menu-open]:to-muted/50 group-data-[state=selected]:from-muted group-data-[state=selected]:to-muted"),
|
|
89
92
|
children: renderCellContent(cell)
|
|
90
93
|
}, cell.id);
|
|
91
94
|
})
|
|
@@ -102,7 +105,7 @@ const BodyRow = React.memo(function BodyRow({ row, displayIndex, expandColumnId,
|
|
|
102
105
|
}) })] });
|
|
103
106
|
});
|
|
104
107
|
BodyRow.displayName = "BodyRow";
|
|
105
|
-
function DataTableBody({ children, className, onScroll, onScrolledTop, onScrolledBottom, scrollThreshold = 50, onRowClick, getRowMemoKey, renderRowContextMenu }) {
|
|
108
|
+
function DataTableBody({ children, className, onScroll, onScrolledTop, onScrolledBottom, scrollThreshold = 50, onRowClick, getRowMemoKey, renderRowContextMenu, getRowTone }) {
|
|
106
109
|
const { table, columns, isLoading, flashingRowIds, flashingCellKeys, registerRowScroller, headerMinWidths, isCompact } = useDataTable();
|
|
107
110
|
const { rows } = table.getRowModel();
|
|
108
111
|
const containerRef = React.useRef(null);
|
|
@@ -245,7 +248,8 @@ function DataTableBody({ children, className, onScroll, onScrolledTop, onScrolle
|
|
|
245
248
|
rowMemoKey: getRowMemoKey ? getRowMemoKey(row.original) : "",
|
|
246
249
|
isRowFlashing: flashingRowIds.has(row.id),
|
|
247
250
|
flashingCellKeys,
|
|
248
|
-
renderRowContextMenu: resolvedRenderRowContextMenu
|
|
251
|
+
renderRowContextMenu: resolvedRenderRowContextMenu,
|
|
252
|
+
tone: getRowTone?.(row.original)
|
|
249
253
|
}, row.id)) : null, children]
|
|
250
254
|
});
|
|
251
255
|
}
|
|
@@ -16,7 +16,7 @@ const getCommonPinningStyles = (column, isHeader = false, pinningEnabled = true)
|
|
|
16
16
|
maxWidth: columnSize,
|
|
17
17
|
flexShrink: 0,
|
|
18
18
|
zIndex: isHeader ? 20 : 10,
|
|
19
|
-
backgroundColor: "var(--
|
|
19
|
+
backgroundColor: "var(--table-fill)",
|
|
20
20
|
boxShadow: isLeft ? "inset -1px 0 0 0 var(--border)" : "inset 1px 0 0 0 var(--border)"
|
|
21
21
|
};
|
|
22
22
|
};
|
package/package.json
CHANGED
package/styles/siyahi.css
CHANGED
|
@@ -555,12 +555,13 @@ svg :is(rect, circle, path)[tabindex]:focus-visible,
|
|
|
555
555
|
}
|
|
556
556
|
|
|
557
557
|
/*
|
|
558
|
-
* An exception the page marks, a stat tile (SectionCard's tone)
|
|
559
|
-
* row (<TableRow data-tone="negative"
|
|
560
|
-
*
|
|
558
|
+
* An exception the page marks, a stat tile (SectionCard's tone), a MetricStrip
|
|
559
|
+
* figure (its item's tone) or a table row (<TableRow data-tone="negative">, a
|
|
560
|
+
* DataTableBody's getRowTone), takes a faint wash of its colour over the card,
|
|
561
|
+
* and a tile or figure an outline of it, so the one figure needing attention
|
|
561
562
|
* stands out of a run of neutral ones. Unlayered, to outrank a row's hover band.
|
|
562
563
|
*/
|
|
563
|
-
:is([data-slot="card"], [data-slot="table-row"]) {
|
|
564
|
+
:is([data-slot="card"], [data-slot="metric-strip-item"], [data-slot="table-row"]) {
|
|
564
565
|
&[data-tone="negative"] {
|
|
565
566
|
--exception: var(--negative);
|
|
566
567
|
}
|
|
@@ -568,12 +569,33 @@ svg :is(rect, circle, path)[tabindex]:focus-visible,
|
|
|
568
569
|
--exception: var(--warning);
|
|
569
570
|
}
|
|
570
571
|
}
|
|
571
|
-
[data-slot="card"]:is([data-tone="negative"], [data-tone="warning"]) {
|
|
572
|
+
:is([data-slot="card"], [data-slot="metric-strip-item"]):is([data-tone="negative"], [data-tone="warning"]) {
|
|
572
573
|
background-color: color-mix(in oklab, var(--exception) var(--wash), var(--card));
|
|
574
|
+
}
|
|
575
|
+
[data-slot="card"]:is([data-tone="negative"], [data-tone="warning"]) {
|
|
573
576
|
--tw-ring-color: color-mix(in oklab, var(--exception) var(--wash-edge), transparent);
|
|
574
577
|
}
|
|
578
|
+
/*
|
|
579
|
+
* A figure's outline lies on the four hairlines around it, as a tile's ring does: its neighbours' rules before and
|
|
580
|
+
* above it, and its own end and bottom rules, which sit outside the padding box the inset counts from.
|
|
581
|
+
*/
|
|
582
|
+
[data-slot="metric-strip-item"]:is([data-tone="negative"], [data-tone="warning"]) {
|
|
583
|
+
position: relative;
|
|
584
|
+
&::after {
|
|
585
|
+
content: "";
|
|
586
|
+
position: absolute;
|
|
587
|
+
inset: -1px;
|
|
588
|
+
border: 1px solid color-mix(in oklab, var(--exception) var(--wash-edge), transparent);
|
|
589
|
+
pointer-events: none;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
/* A pinned cell paints the card under itself (niko-table's lib/styles), so it lays the row's wash over that. */
|
|
575
593
|
[data-slot="table-row"]:is([data-tone="negative"], [data-tone="warning"]) {
|
|
576
|
-
|
|
594
|
+
--row-wash: color-mix(in oklab, var(--exception) var(--wash), transparent);
|
|
595
|
+
background-color: var(--row-wash);
|
|
596
|
+
& > [data-pinned] {
|
|
597
|
+
background-image: linear-gradient(var(--row-wash), var(--row-wash));
|
|
598
|
+
}
|
|
577
599
|
}
|
|
578
600
|
|
|
579
601
|
/*
|
|
@@ -615,6 +637,14 @@ svg :is(rect, circle, path)[tabindex]:focus-visible,
|
|
|
615
637
|
border-color: transparent;
|
|
616
638
|
}
|
|
617
639
|
}
|
|
640
|
+
/*
|
|
641
|
+
* The opaque fill a data table's sticky header and pinned cells paint, so rows
|
|
642
|
+
* scrolling under them stay hidden: the colour under the table, the card it
|
|
643
|
+
* sits in or, on an open surface, whose cards have no fill, the page.
|
|
644
|
+
*/
|
|
645
|
+
[data-slot="table-container"] {
|
|
646
|
+
--table-fill: var(--card);
|
|
647
|
+
}
|
|
618
648
|
|
|
619
649
|
/*
|
|
620
650
|
* A series in a forecast is drawn twice (LineChartCard, AreaChartCard): solid
|
|
@@ -804,6 +834,9 @@ div[data-slot="sidebar"]:not([data-side], [data-mobile]) {
|
|
|
804
834
|
}
|
|
805
835
|
|
|
806
836
|
@scope ([data-surface="open"]) to ([data-surface="ruled"], [data-surface="card"]) {
|
|
837
|
+
[data-slot="table-container"] {
|
|
838
|
+
--table-fill: var(--background);
|
|
839
|
+
}
|
|
807
840
|
[data-slot="card"] {
|
|
808
841
|
background: none;
|
|
809
842
|
--tw-ring-color: transparent;
|