@siyahi/tools 0.1.9-next.2 → 0.1.9-next.20
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/README.md +25 -2
- package/THIRD_PARTY_NOTICES.md +11 -11
- package/package.json +1 -1
- package/shots.mjs +37 -11
- package/types/shots.d.mts +1 -0
package/README.md
CHANGED
|
@@ -8,6 +8,10 @@ Kept apart from the UI package so installing the UI does not pull in Playwright.
|
|
|
8
8
|
An [oxlint](https://oxc.rs) JS plugin, `siyahi`. Every rule runs only in files
|
|
9
9
|
that import `@siyahi/ui`.
|
|
10
10
|
|
|
11
|
+
```sh
|
|
12
|
+
npm install --save-dev @siyahi/tools@next oxlint
|
|
13
|
+
```
|
|
14
|
+
|
|
11
15
|
| Rule | Rejects |
|
|
12
16
|
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
13
17
|
| `siyahi/no-color-literal` | hex, `rgb()`, `hsl()`, `oklch()` and other colour literals in strings |
|
|
@@ -21,7 +25,14 @@ that import `@siyahi/ui`.
|
|
|
21
25
|
{
|
|
22
26
|
"jsPlugins": ["@siyahi/tools/lint"],
|
|
23
27
|
"settings": { "shadcn": { "ui": "@siyahi/ui", "themeImports": ["^@siyahi/ui/"] } },
|
|
24
|
-
"rules": {
|
|
28
|
+
"rules": {
|
|
29
|
+
"siyahi/no-color-literal": "error",
|
|
30
|
+
"siyahi/no-scheme-variant": "error",
|
|
31
|
+
"siyahi/no-numeric-font-size": "error",
|
|
32
|
+
"siyahi/no-chart-theme-map": "error",
|
|
33
|
+
"siyahi/no-raw-colors": "error",
|
|
34
|
+
"siyahi/no-arbitrary-values": "error"
|
|
35
|
+
}
|
|
25
36
|
}
|
|
26
37
|
```
|
|
27
38
|
|
|
@@ -45,6 +56,12 @@ export default defineConfig({ plugins: [react(), tailwindcss(), preloadFonts()]
|
|
|
45
56
|
It runs in `vite build` only. The fonts stay self-hosted: the tags point at
|
|
46
57
|
the files Vite emitted from `@siyahi/ui`'s fontsource imports.
|
|
47
58
|
|
|
59
|
+
The theme's fallback fonts match Geist's metrics until it loads: Arial or its
|
|
60
|
+
Linux equivalents for body text, Courier New for mono. If an app renders its
|
|
61
|
+
own HTML, add preload tags for the build's hashed URLs of
|
|
62
|
+
`geist-latin-wght-normal.woff2`, `geist-latin-ext-wght-normal.woff2` and
|
|
63
|
+
`geist-mono-latin-wght-normal.woff2`.
|
|
64
|
+
|
|
48
65
|
## siyahi-cls
|
|
49
66
|
|
|
50
67
|
```sh
|
|
@@ -70,7 +87,7 @@ counted (`moved=` against `in-view=`) and listed, but do not fail it. Run `npx p
|
|
|
70
87
|
## siyahi-shots
|
|
71
88
|
|
|
72
89
|
```sh
|
|
73
|
-
npx siyahi-shots http://localhost:5173/#/page [more URLs] [--out shots] [--gutter 16] [--font Geist] [--chrome '[data-slot="sidebar"]'] [--wait-for h1] [--report]
|
|
90
|
+
npx siyahi-shots http://localhost:5173/#/page [more URLs] [--out shots] [--gutter 16] [--font Geist] [--chrome '[data-slot="sidebar"]'] [--wait-for h1] [--report] [--widths 1920,390]
|
|
74
91
|
```
|
|
75
92
|
|
|
76
93
|
Renders each URL in Chromium at 1920 and 390 pixels wide, light and dark, and
|
|
@@ -100,6 +117,12 @@ anything inside `--chrome` (by default the shadcn sidebar, which sits flush to
|
|
|
100
117
|
the edge by design). The font default, Geist, is Siyahi's; pass another family
|
|
101
118
|
for other pages. Run `npx playwright install chromium` once.
|
|
102
119
|
|
|
120
|
+
Pass `--widths 320,390,768,1366,1920` to include small phones, tablets and laptops;
|
|
121
|
+
each width runs in light and dark. The check also rejects a scrolling table whose
|
|
122
|
+
frozen columns occupy more than half its available width. These captures
|
|
123
|
+
complement interaction checks for scrolling, state retention, keyboard navigation
|
|
124
|
+
and touch readouts.
|
|
125
|
+
|
|
103
126
|
## siyahi-contrast
|
|
104
127
|
|
|
105
128
|
```sh
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -8,17 +8,17 @@ file as copied; `packages/ui/registry.lock.json` lists them all and
|
|
|
8
8
|
|
|
9
9
|
## Copied into `@siyahi/ui`
|
|
10
10
|
|
|
11
|
-
| What
|
|
12
|
-
|
|
|
13
|
-
| shadcn/ui core components (installed with `shadcn add`, never edited)
|
|
14
|
-
| shadcn `tailwind.css` (variants and utilities the components use)
|
|
15
|
-
| shadcn block `sidebar-07`
|
|
16
|
-
| shadcn block `dashboard-01`, `SectionCards` only
|
|
17
|
-
| Heatmaps: calendar, weekday by hour, status timeline
|
|
18
|
-
| niko-table data table on TanStack Table
|
|
19
|
-
| shadcn chart blocks `chart-bar-multiple`, `chart-area-stacked`, `chart-line-multiple`
|
|
20
|
-
| dashboardcn ranked list (`BarList`)
|
|
21
|
-
| MiniDev copy button (`CopyButton`)
|
|
11
|
+
| What | Where in the package | Origin | Licence |
|
|
12
|
+
| --------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
13
|
+
| shadcn/ui core components (installed with `shadcn add`, never edited) | `src/components/ui/*`, `src/hooks/use-mobile.ts` | https://github.com/shadcn-ui/ui | MIT, Copyright (c) 2023 shadcn |
|
|
14
|
+
| shadcn `tailwind.css` (variants and utilities the components use) | `styles/shadcn.css` | https://github.com/shadcn-ui/ui/tree/main/packages/shadcn | MIT, Copyright (c) 2023 shadcn |
|
|
15
|
+
| shadcn block `sidebar-07` | `src/components/registry/sidebar-07/*` | https://ui.shadcn.com/r/styles/base-vega/sidebar-07.json | MIT, Copyright (c) 2023 shadcn |
|
|
16
|
+
| shadcn block `dashboard-01`, `SectionCards` only | `src/components/registry/dashboard-01/section-cards.tsx` | https://ui.shadcn.com/r/styles/base-vega/dashboard-01.json | MIT, Copyright (c) 2023 shadcn |
|
|
17
|
+
| Heatmaps: calendar, weekday by hour, status timeline | `src/components/registry/heatmap/*` | https://shadcn-heatmap.pages.dev/r/calendar-heatmap.json, https://shadcn-heatmap.pages.dev/r/weekday-heatmap.json, https://shadcn-heatmap.pages.dev/r/status-heatmap.json (source: https://github.com/rutopio/shadcn-heatmap) | MIT, Copyright (c) 2026 ChingRu |
|
|
18
|
+
| niko-table data table on TanStack Table | `src/components/registry/niko-table/*` | https://niko-table.com/r/data-table.json, https://niko-table.com/r/data-table-core.json, https://niko-table.com/r/data-table-ui.json, https://niko-table.com/r/data-table-faceted-filter.json, https://niko-table.com/r/data-table-column-sort.json, https://niko-table.com/r/data-table-search-filter.json (source: https://github.com/Semkoo/niko-table-registry) | MIT, Copyright (c) 2025 Semir N.; original work Copyright (c) 2025 WebDevSimplified |
|
|
19
|
+
| shadcn chart blocks `chart-bar-multiple`, `chart-area-stacked`, `chart-line-multiple` | `src/components/registry/charts/*` | https://ui.shadcn.com/r/styles/new-york-v4/chart-bar-multiple.json, https://ui.shadcn.com/r/styles/new-york-v4/chart-area-stacked.json, https://ui.shadcn.com/r/styles/new-york-v4/chart-line-multiple.json | MIT, Copyright (c) 2023 shadcn |
|
|
20
|
+
| dashboardcn ranked list (`BarList`), stacked bar (`DistributionBar`), `Sparkline` and `MetricStrip` | `src/components/registry/dashboardcn/*` | https://dashboardcn.com/r/bar-list.json, https://dashboardcn.com/r/distribution-bar.json, https://dashboardcn.com/r/sparkline.json, https://dashboardcn.com/r/kpi-row-card.json (source: https://github.com/NoahGdev/dashboardcn) | MIT, Copyright (c) 2026 dashboardcn contributors |
|
|
21
|
+
| MiniDev copy button (`CopyButton`) | `src/components/registry/minidev/copy-button.tsx` | https://ui.minidev.pro/r/copy-button.json (source: https://github.com/amankaushik481/minidev-ui) | MIT, Copyright (c) 2026 MiniDev |
|
|
22
22
|
|
|
23
23
|
## Runtime dependencies with attribution terms
|
|
24
24
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siyahi/tools",
|
|
3
|
-
"version": "0.1.9-next.
|
|
3
|
+
"version": "0.1.9-next.20",
|
|
4
4
|
"description": "Checks and build helpers for Siyahi pages: a lint plugin, a font preload plugin, a theme contrast check, and screenshot and layout shift scripts.",
|
|
5
5
|
"homepage": "https://siyahi.jatinbansal.com",
|
|
6
6
|
"license": "MIT",
|
package/shots.mjs
CHANGED
|
@@ -37,15 +37,7 @@ import { parseArgs } from "node:util";
|
|
|
37
37
|
import { chromium, errors } from "playwright";
|
|
38
38
|
|
|
39
39
|
/** @typedef {{ name: string, width: number, height: number, dark: boolean }} Mode */
|
|
40
|
-
/** @typedef {{ gutterLeft: number | null, gutterRight: number | null, overflow: number, overflowWithScrollbar: number, clipped: string[], font: string, fontLoaded: boolean, docHeight: number, h1: string | null }} Probe */
|
|
41
|
-
|
|
42
|
-
/** @type {Mode[]} */
|
|
43
|
-
const MODES = [
|
|
44
|
-
{ name: "1920-light", width: 1920, height: 1080, dark: false },
|
|
45
|
-
{ name: "1920-dark", width: 1920, height: 1080, dark: true },
|
|
46
|
-
{ name: "390-light", width: 390, height: 844, dark: false },
|
|
47
|
-
{ name: "390-dark", width: 390, height: 844, dark: true },
|
|
48
|
-
];
|
|
40
|
+
/** @typedef {{ gutterLeft: number | null, gutterRight: number | null, overflow: number, overflowWithScrollbar: number, clipped: string[], frozen: string[], font: string, fontLoaded: boolean, docHeight: number, h1: string | null }} Probe */
|
|
49
41
|
// How long --wait-for may take to appear before the mode fails.
|
|
50
42
|
const WAIT_FOR_TIMEOUT = 15_000;
|
|
51
43
|
// Tallest PNG written. Headless Chromium renders taller pages, but many image
|
|
@@ -61,15 +53,30 @@ const { values, positionals } = parseArgs({
|
|
|
61
53
|
chrome: { type: "string", default: '[data-slot="sidebar"]' },
|
|
62
54
|
"wait-for": { type: "string", default: "h1" },
|
|
63
55
|
report: { type: "boolean", default: false },
|
|
56
|
+
widths: { type: "string", default: "1920,390" },
|
|
64
57
|
},
|
|
65
58
|
});
|
|
66
59
|
if (positionals.length === 0) {
|
|
67
60
|
console.error(
|
|
68
|
-
"usage: siyahi-shots <url>... [--out shots] [--gutter 16] [--font Geist] [--chrome <selector>] [--wait-for <selector>] [--report]",
|
|
61
|
+
"usage: siyahi-shots <url>... [--out shots] [--gutter 16] [--font Geist] [--chrome <selector>] [--wait-for <selector>] [--report] [--widths 1920,390]",
|
|
69
62
|
);
|
|
70
63
|
process.exit(2);
|
|
71
64
|
}
|
|
72
65
|
const outDir = path.resolve(values.out);
|
|
66
|
+
const widths = values.widths.split(",").map(Number);
|
|
67
|
+
if (widths.some((width) => !Number.isInteger(width) || width < 240 || width > 3840)) {
|
|
68
|
+
console.error("--widths must be comma-separated pixel widths between 240 and 3840");
|
|
69
|
+
process.exit(2);
|
|
70
|
+
}
|
|
71
|
+
/** @type {Mode[]} */
|
|
72
|
+
const MODES = [...new Set(widths)].flatMap((width) =>
|
|
73
|
+
[false, true].map((dark) => ({
|
|
74
|
+
name: `${width}-${dark ? "dark" : "light"}`,
|
|
75
|
+
width,
|
|
76
|
+
height: width < 768 ? 844 : 1080,
|
|
77
|
+
dark,
|
|
78
|
+
})),
|
|
79
|
+
);
|
|
73
80
|
const minGutter = Number(values.gutter);
|
|
74
81
|
const waitFor = values["wait-for"];
|
|
75
82
|
mkdirSync(outDir, { recursive: true });
|
|
@@ -93,7 +100,12 @@ const PROBE = `((chrome) => {
|
|
|
93
100
|
};
|
|
94
101
|
const shown = (el) => {
|
|
95
102
|
const r = el.getBoundingClientRect();
|
|
96
|
-
|
|
103
|
+
const style = getComputedStyle(el);
|
|
104
|
+
// Base UI keeps clipped controls for form submission; aria-hidden alone
|
|
105
|
+
// does not mean a control (or its descendants) is visually hidden.
|
|
106
|
+
const hiddenControl = el.matches('input[aria-hidden="true"],select[aria-hidden="true"],textarea[aria-hidden="true"]') &&
|
|
107
|
+
(style.clipPath === "inset(50%)" || style.clip === "rect(0px, 0px, 0px, 0px)");
|
|
108
|
+
return r.width > 0 && r.height > 0 && !el.closest(chrome) && style.visibility !== "hidden" && !hiddenControl;
|
|
97
109
|
};
|
|
98
110
|
// Pixels of el cut off at the side by an ancestor that hides its overflow,
|
|
99
111
|
// and how it hides it, up to the nearest one that scrolls, which shows the
|
|
@@ -129,6 +141,18 @@ const PROBE = `((chrome) => {
|
|
|
129
141
|
const overflowWithScrollbar = de.scrollWidth - de.clientWidth;
|
|
130
142
|
de.style.overflowY = prev;
|
|
131
143
|
const font = getComputedStyle(document.body).fontFamily;
|
|
144
|
+
const frozen = [];
|
|
145
|
+
for (const table of document.querySelectorAll("table")) {
|
|
146
|
+
let container = table.parentElement;
|
|
147
|
+
while (container && !["auto", "scroll"].includes(getComputedStyle(container).overflowX)) container = container.parentElement;
|
|
148
|
+
if (!container || container.scrollWidth <= container.clientWidth) continue;
|
|
149
|
+
const cells = [...(table.querySelector("tbody tr")?.children ?? [])];
|
|
150
|
+
const pinned = cells.filter((cell) => getComputedStyle(cell).position === "sticky");
|
|
151
|
+
const occupied = pinned.reduce((sum, cell) => sum + cell.getBoundingClientRect().width, 0);
|
|
152
|
+
if (pinned.length < cells.length && occupied > container.clientWidth / 2) {
|
|
153
|
+
frozen.push("frozen columns occupy " + Math.round(occupied) + " of " + container.clientWidth + "px; unpin them in narrow space");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
132
156
|
const first = font.split(",")[0].trim().replace(/^["']|["']$/g, "");
|
|
133
157
|
const h1 = document.querySelector("h1");
|
|
134
158
|
return {
|
|
@@ -137,6 +161,7 @@ const PROBE = `((chrome) => {
|
|
|
137
161
|
overflow,
|
|
138
162
|
overflowWithScrollbar,
|
|
139
163
|
clipped,
|
|
164
|
+
frozen,
|
|
140
165
|
font: first,
|
|
141
166
|
fontLoaded: document.fonts.check('16px "' + first + '"') && [...document.fonts].some((f) => f.family.replace(/["']/g, "") === first && f.status === "loaded"),
|
|
142
167
|
docHeight: de.scrollHeight,
|
|
@@ -219,6 +244,7 @@ function problemsOf(p, ready) {
|
|
|
219
244
|
if (p.overflowWithScrollbar > 0)
|
|
220
245
|
problems.push(`scrolls sideways by ${p.overflowWithScrollbar}px once a scrollbar takes its width`);
|
|
221
246
|
for (const what of p.clipped.slice(0, 5)) problems.push(what);
|
|
247
|
+
problems.push(...p.frozen);
|
|
222
248
|
if (p.clipped.length > 5) problems.push(`and ${p.clipped.length - 5} more cut off`);
|
|
223
249
|
if (!p.font.startsWith(values.font)) problems.push(`body font is ${p.font}, not ${values.font}`);
|
|
224
250
|
else if (!p.fontLoaded && !values.report) problems.push(`${p.font} is named but never loaded`);
|