@tangle-network/brand 1.4.0 → 1.5.0
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 +8 -0
- package/package.json +1 -1
- package/src/styles/theme-parity.test.ts +86 -0
- package/src/styles/theme.css +6 -0
- package/src/styles/tokens.css +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 532a6d8: Add `--shadow-overlay`, the elevation step above `--shadow-dropdown` for a surface that covers the page rather than sitting beside it — a drawer, a modal, a floating dock.
|
|
8
|
+
|
|
9
|
+
It is derived from `--hsl-foreground` rather than a fixed `rgba`, so it inverts with the theme: dark ink in light, a soft halo in dark. That is what makes it work on a dark canvas, where a black shadow renders as nothing. One declaration serves both themes, and the token is declared in the `@theme` block as well so Tailwind emits a `shadow-overlay` utility.
|
|
10
|
+
|
|
3
11
|
## 1.4.0
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { blockIn } from "./css-test-utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A token declared in both files has to carry the same value in both.
|
|
8
|
+
*
|
|
9
|
+
* `tokens.css` is what a consumer reads at runtime through `var(--x)`.
|
|
10
|
+
* `theme.css` is the Tailwind v4 `@theme` block, and it exists because Tailwind
|
|
11
|
+
* only emits a utility for a token it can see declared there — so the shared
|
|
12
|
+
* ones are necessarily written twice.
|
|
13
|
+
*
|
|
14
|
+
* That duplication is structural and cannot be factored out: `@theme` needs a
|
|
15
|
+
* literal, and a self-reference would be circular. What it can be is GATED. An
|
|
16
|
+
* edit to one file that misses the other would leave `shadow-overlay` (the
|
|
17
|
+
* utility, from theme.css) rendering a different shadow from
|
|
18
|
+
* `shadow-[var(--shadow-overlay)]` (the variable, from tokens.css), and the
|
|
19
|
+
* mismatch would show up as an inconsistency no one could reproduce from
|
|
20
|
+
* either file alone.
|
|
21
|
+
*/
|
|
22
|
+
const read = (name: string) =>
|
|
23
|
+
readFileSync(path.resolve(import.meta.dirname, name), "utf8");
|
|
24
|
+
|
|
25
|
+
/** `--token: value;` pairs in one CSS block, whitespace collapsed. */
|
|
26
|
+
function declarationsIn(css: string): Map<string, string> {
|
|
27
|
+
const out = new Map<string, string>();
|
|
28
|
+
for (const m of css.matchAll(/(--[a-z0-9-]+)\s*:\s*([^;]+);/g)) {
|
|
29
|
+
out.set(m[1] as string, (m[2] as string).split(/\s+/).join(" ").trim());
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// The default spine, which is the one `@theme` mirrors. Anchor on the last
|
|
35
|
+
// selector of the multi-selector header, as the other suites here do.
|
|
36
|
+
const tokensCss = read("tokens.css");
|
|
37
|
+
const runtime = declarationsIn(blockIn(tokensCss, ".dark"));
|
|
38
|
+
const light = declarationsIn(blockIn(tokensCss, ".light"));
|
|
39
|
+
const theme = declarationsIn(blockIn(read("theme.css"), "@theme"));
|
|
40
|
+
|
|
41
|
+
const shared = [...theme.keys()].filter((t) => runtime.has(t)).sort();
|
|
42
|
+
|
|
43
|
+
describe("tokens.css and theme.css agree on every token they both declare", () => {
|
|
44
|
+
it("finds the shared tokens to check", () => {
|
|
45
|
+
// Guards the parse itself: a selector rename that silently emptied one
|
|
46
|
+
// block would otherwise make every assertion below vacuously pass.
|
|
47
|
+
expect(shared.length).toBeGreaterThan(5);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
for (const token of shared) {
|
|
51
|
+
it(`${token} carries one value`, () => {
|
|
52
|
+
expect(theme.get(token)).toBe(runtime.get(token));
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A token written in terms of `--hsl-foreground` already inverts, because that
|
|
59
|
+
* ramp is what each spine redefines. Restating it on the light spine would pin
|
|
60
|
+
* it to one theme's ink and defeat the derivation — the failure would be silent,
|
|
61
|
+
* since both declarations are individually valid CSS.
|
|
62
|
+
*
|
|
63
|
+
* So the omission is the contract, and this asserts it rather than trusting the
|
|
64
|
+
* comment that states it.
|
|
65
|
+
*/
|
|
66
|
+
const selfInverting = [...runtime.entries()]
|
|
67
|
+
.filter(([, value]) => value.includes("var(--hsl-foreground)"))
|
|
68
|
+
.map(([token]) => token)
|
|
69
|
+
.sort();
|
|
70
|
+
|
|
71
|
+
describe("a self-inverting token is declared once, not per spine", () => {
|
|
72
|
+
it("finds the self-inverting tokens to check", () => {
|
|
73
|
+
expect(selfInverting.length).toBeGreaterThan(0);
|
|
74
|
+
// The assertions below are absence checks, so they hold trivially against
|
|
75
|
+
// an empty map. A selector rename that stopped matching the light spine
|
|
76
|
+
// would otherwise read as "nothing is restated" — the strongest possible
|
|
77
|
+
// pass from the weakest possible parse.
|
|
78
|
+
expect(light.size).toBeGreaterThan(5);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
for (const token of selfInverting) {
|
|
82
|
+
it(`${token} is not restated on the light spine`, () => {
|
|
83
|
+
expect(light.has(token)).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
});
|
package/src/styles/theme.css
CHANGED
|
@@ -104,4 +104,10 @@
|
|
|
104
104
|
--shadow-card: 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
105
105
|
--shadow-dropdown: 0 8px 30px rgba(0, 0, 0, 0.45);
|
|
106
106
|
--shadow-glow: none;
|
|
107
|
+
/* Theme-derived, so it inverts — see tokens.css. Declared here as well so
|
|
108
|
+
Tailwind emits the `shadow-overlay` utility, which is how a consumer
|
|
109
|
+
reaches it without writing `shadow-[var(--shadow-overlay)]`. */
|
|
110
|
+
--shadow-overlay:
|
|
111
|
+
0 2px 6px hsl(var(--hsl-foreground) / 0.1),
|
|
112
|
+
0 16px 40px hsl(var(--hsl-foreground) / 0.16);
|
|
107
113
|
}
|
package/src/styles/tokens.css
CHANGED
|
@@ -319,6 +319,18 @@
|
|
|
319
319
|
--shadow-dropdown: 0 8px 30px rgba(0, 0, 0, 0.45);
|
|
320
320
|
--shadow-glow: none;
|
|
321
321
|
--shadow-accent: 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
322
|
+
/* The lift above --shadow-dropdown, for a surface that covers the page
|
|
323
|
+
rather than sitting beside it: a drawer, a modal, a floating dock.
|
|
324
|
+
Two layers — a tight contact shadow plus a wide ambient one.
|
|
325
|
+
Alone among the shadows this is derived from --hsl-foreground instead of
|
|
326
|
+
a fixed rgba, so it INVERTS with the theme. That is what makes it work in
|
|
327
|
+
dark: a black shadow on a dark canvas renders as nothing (see the ladder
|
|
328
|
+
note at the top of this file), while the foreground is near-white there,
|
|
329
|
+
so the same declaration reads as a soft halo. One definition therefore
|
|
330
|
+
serves both themes and the light block does not restate it. */
|
|
331
|
+
--shadow-overlay:
|
|
332
|
+
0 2px 6px hsl(var(--hsl-foreground) / 0.1),
|
|
333
|
+
0 16px 40px hsl(var(--hsl-foreground) / 0.16);
|
|
322
334
|
--shadow-status-running: none;
|
|
323
335
|
--shadow-status-creating: none;
|
|
324
336
|
--shadow-status-error: none;
|