mirror of
https://github.com/goauthentik/authentik.git
synced 2026-06-17 19:09:11 +03:00
Initial check-in. Working, but incomplete.
This commit is contained in:
@@ -1,56 +0,0 @@
|
||||
/**
|
||||
* @file Color tokens — semantic surface, text, state, and brand colors.
|
||||
*
|
||||
* Light values are declared via `variable()`. Dark values are declared inside
|
||||
* the `dark` theme block so they emit under `html[data-theme="dark"]`.
|
||||
*
|
||||
* Link tokens are wired through `ref()` so brand overrides to `color.primary`
|
||||
* cascade to links without separate overrides. The dark theme intentionally
|
||||
* re-points links to their own oklab values rather than chaining through
|
||||
* primary because dark mode links need higher luminance than primary buttons.
|
||||
*
|
||||
* `warning` and `danger` deliberately stay on light values in dark mode — state
|
||||
* colors keep consistent intensity across themes so warnings read as urgent.
|
||||
*/
|
||||
|
||||
import { ref, theme, variable } from "../shared.js";
|
||||
|
||||
export const colorAccent = variable("color.accent", "#fd4b2d");
|
||||
export const colorPrimary = variable("color.primary", "oklab(0.522 -0.0434 -0.1717)");
|
||||
export const colorPrimaryHover = variable("color.primary-hover", "oklab(0.3763 -0.0324 -0.1182)");
|
||||
export const colorText = variable("color.text", "oklab(0.1957 -0 0)");
|
||||
export const colorTextMuted = variable("color.text-muted", "oklab(0.5364 -0.0026 -0.0089)");
|
||||
|
||||
export const colorLink = variable("color.link", ref(colorPrimary));
|
||||
export const colorLinkHover = variable("color.link-hover", ref(colorPrimaryHover));
|
||||
export const colorLinkVisited = variable("color.link-visited", "oklab(0.3679 0.0535 -0.1797)");
|
||||
|
||||
export const colorSurface = variable("color.surface", "oklab(1 -0 0)");
|
||||
export const colorSurfaceMuted = variable("color.surface-muted", "oklab(0.9551 -0 0)");
|
||||
export const colorSurfaceRaised = variable("color.surface-raised", "oklab(0.9851 -0 0)");
|
||||
|
||||
export const colorBorder = variable("color.border", "#d2d2d2");
|
||||
export const colorBorderStrong = variable("color.border-strong", "#8a8d90");
|
||||
|
||||
export const colorInfo = variable("color.info", "oklab(0.6689 -0.0608 -0.1513)");
|
||||
export const colorSuccess = variable("color.success", "oklab(0.5549 -0.1071 0.0852)");
|
||||
export const colorWarning = variable("color.warning", "oklab(0.7864 0.0298 0.1604)");
|
||||
export const colorDanger = variable("color.danger", "oklab(0.5331 0.1798 0.1034)");
|
||||
|
||||
// Dark theme overrides. Surface values are pinned near PatternFly 4's
|
||||
// BackgroundColor--100 (#151515) and the legacy drawer surface (#18191a) — the
|
||||
// earlier oklab(0.23) value visibly brightened every PF-backed dark panel.
|
||||
theme("dark", (ctx) => {
|
||||
ctx.variable(colorText, "oklab(0.9067 -0 0)");
|
||||
ctx.variable(colorTextMuted, "oklab(0.7407 -0.0007 -0.0017)");
|
||||
ctx.variable(colorLink, "oklab(70.367% -0.07498 -0.139)");
|
||||
ctx.variable(colorLinkHover, "oklab(0.7706 -0.0485 -0.1012)");
|
||||
ctx.variable(colorLinkVisited, "oklab(0.7137 0.0522 -0.151)");
|
||||
ctx.variable(colorSurface, "oklab(0.183 -0 0)");
|
||||
ctx.variable(colorSurfaceMuted, "oklab(0.14 -0 0)");
|
||||
ctx.variable(colorSurfaceRaised, "oklab(0.225 -0 0)");
|
||||
ctx.variable(colorBorder, "oklab(0.3906 0.0001 -0.0052)");
|
||||
ctx.variable(colorBorderStrong, "oklab(0.4602 -0.0003 -0.0034)");
|
||||
ctx.variable(colorInfo, "oklab(0.7706 -0.0485 -0.1012)");
|
||||
ctx.variable(colorSuccess, "oklab(0.6488 -0.1066 0.0846)");
|
||||
});
|
||||
Generated
+151
@@ -21,7 +21,10 @@
|
||||
"@goauthentik/prettier-config": "../prettier-config",
|
||||
"@goauthentik/tsconfig": "../tsconfig",
|
||||
"@styleframe/cli": "^4.0.0",
|
||||
"@types/culori": "^4.0.1",
|
||||
"@types/node": "^25.7.0",
|
||||
"@typescript/native-preview": "^7.0.0-dev.20260616.1",
|
||||
"culori": "^4.0.2",
|
||||
"eslint": "^9.39.3",
|
||||
"pino": "^10.3.1",
|
||||
"pino-pretty": "^13.1.2",
|
||||
@@ -638,6 +641,13 @@
|
||||
"@styleframe/license": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/culori": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/culori/-/culori-4.0.1.tgz",
|
||||
"integrity": "sha512-43M51r/22CjhbOXyGT361GZ9vncSVQ39u62x5eJdBQFviI8zWp2X5jzqg7k4M6PVgDQAClpy2bUe2dtwEgEDVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
||||
@@ -944,6 +954,147 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/native-preview": {
|
||||
"version": "7.0.0-dev.20260616.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20260616.1.tgz",
|
||||
"integrity": "sha512-+AuZUl7nkLPXL1rwsyZZF7iasu0HkrL5O6aWwUC0cObD5EvNgxz3hXbBhsSvuJ8tb2JRpVwFsE0BHPcYVe5GkA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsgo": "bin/tsgo.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260616.1",
|
||||
"@typescript/native-preview-darwin-x64": "7.0.0-dev.20260616.1",
|
||||
"@typescript/native-preview-linux-arm": "7.0.0-dev.20260616.1",
|
||||
"@typescript/native-preview-linux-arm64": "7.0.0-dev.20260616.1",
|
||||
"@typescript/native-preview-linux-x64": "7.0.0-dev.20260616.1",
|
||||
"@typescript/native-preview-win32-arm64": "7.0.0-dev.20260616.1",
|
||||
"@typescript/native-preview-win32-x64": "7.0.0-dev.20260616.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/native-preview-darwin-arm64": {
|
||||
"version": "7.0.0-dev.20260616.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20260616.1.tgz",
|
||||
"integrity": "sha512-9SwegwwE7fYutnZJjTi2PeUXhKGFg82MGjSpCFD2cj5v9YQcs5oO5QsmeeMit4fMG8Z83Jy8knOF97d9NaQ7Bg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/native-preview-darwin-x64": {
|
||||
"version": "7.0.0-dev.20260616.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20260616.1.tgz",
|
||||
"integrity": "sha512-gl7R8OiwEBNxzs5wjbM9XOibTs5b6/gAgu0+En9pLpYuWR/EITs8Eh0mNQui4JYTp1SkoHvn09aRZKTQf21XTg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/native-preview-linux-arm": {
|
||||
"version": "7.0.0-dev.20260616.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20260616.1.tgz",
|
||||
"integrity": "sha512-QWXQS2CrhSpXbng7vBtCVDszFgwVBuJU8MCFhxZL0hH6s+XjQDSNNkGO1oHueBr+7HbSkkyqfNYVNXvgVMUJLQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/native-preview-linux-arm64": {
|
||||
"version": "7.0.0-dev.20260616.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20260616.1.tgz",
|
||||
"integrity": "sha512-CJjplWoE+EeYtbyNeP4fyuh0QcPiQBZJSLqPS07E3ugo3d9M/IG8WnL+3GFQ0g1p4c6QC/+OC0oTTQnGcNdd2Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/native-preview-linux-x64": {
|
||||
"version": "7.0.0-dev.20260616.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20260616.1.tgz",
|
||||
"integrity": "sha512-UVySBNnGTAnul2kO2/EhlVZl0CeTDcd6Lzi+WkvgyCgapTcU0BX1selQ4MEPtbVWKUbt9HBhxNku2dyaCcmKVw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/native-preview-win32-arm64": {
|
||||
"version": "7.0.0-dev.20260616.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20260616.1.tgz",
|
||||
"integrity": "sha512-kdX0QcDXiESH0o5DFdSWw15Hth0EtQobT9tX28ofqBUwGU1FFhwTKzA6qo7chaYUSW5MMd6XIME9rBwk+WizLw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/native-preview-win32-x64": {
|
||||
"version": "7.0.0-dev.20260616.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20260616.1.tgz",
|
||||
"integrity": "sha512-EB0Pj/0+nXifS3+wN0HdR1mKu7IieSpjMXoDjdXtJAdiPGlmKazBgHj97qn6CBvXisgLx0Eyb7tLCEQNDG53cA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
|
||||
|
||||
@@ -55,11 +55,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.3",
|
||||
"@goauthentik/prettier-config": "../prettier-config",
|
||||
"@goauthentik/eslint-config": "../eslint-config",
|
||||
"@goauthentik/prettier-config": "../prettier-config",
|
||||
"@goauthentik/tsconfig": "../tsconfig",
|
||||
"@styleframe/cli": "^4.0.0",
|
||||
"@types/culori": "^4.0.1",
|
||||
"@types/node": "^25.7.0",
|
||||
"@typescript/native-preview": "^7.0.0-dev.20260616.1",
|
||||
"culori": "^4.0.2",
|
||||
"eslint": "^9.39.3",
|
||||
"pino": "^10.3.1",
|
||||
"pino-pretty": "^13.1.2",
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
/**
|
||||
* @file Build script for `@goauthentik/theme`.
|
||||
*
|
||||
* Runs the styleframe transpiler against the configured token tree, then
|
||||
* fans out the single emitted CSS string into one file per category
|
||||
* (color, typography, spacing, shape, shadow, motion, z-index) and a top-
|
||||
* level `index.css` that `@import`s them in deterministic order.
|
||||
*
|
||||
* Consumers can import the whole surface
|
||||
* (`@import "@goauthentik/theme/index.css"`) or cherry-pick a category
|
||||
* (`@import "@goauthentik/theme/color.css"`).
|
||||
*
|
||||
* Invoked by `npm run build:assets` and chained from `npm run build`.
|
||||
*/
|
||||
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { build } from "../lib/node.js";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const PACKAGE_ROOT = resolve(__dirname, "..");
|
||||
const OUT_DIR = resolve(PACKAGE_ROOT, "dist");
|
||||
|
||||
/**
|
||||
* @typedef {object} Category
|
||||
* @property {string} name Slug used for the output filename.
|
||||
* @property {string[]} prefixes
|
||||
* Token-name prefixes (after the `--ak-` strip) that belong to this category.
|
||||
*/
|
||||
|
||||
/** @type {Category[]} */
|
||||
const CATEGORIES = [
|
||||
{ name: "color", prefixes: ["color-"] },
|
||||
{
|
||||
name: "typography",
|
||||
prefixes: ["font-family-", "font-size-", "font-weight-", "line-height-"],
|
||||
},
|
||||
{ name: "spacing", prefixes: ["space-"] },
|
||||
{ name: "shape", prefixes: ["radius-", "border-width-"] },
|
||||
{ name: "shadow", prefixes: ["shadow-"] },
|
||||
{ name: "motion", prefixes: ["duration-", "easing-"] },
|
||||
{ name: "z-index", prefixes: ["z-index-"] },
|
||||
];
|
||||
|
||||
const HEADER = [
|
||||
"/*",
|
||||
" * ⚠️ GENERATED FILE — do not edit directly.",
|
||||
" *",
|
||||
" * Source: packages/theme/lib/tokens/*.js",
|
||||
" * Built: packages/theme/scripts/build.mjs",
|
||||
" */",
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
/**
|
||||
* One emitted block within the styleframe CSS output. `header` is the line
|
||||
* that opens the block (`:root {`, `@media (...) {`, `html[data-theme="..."] {`).
|
||||
* `prefix` is whitespace that should precede declarations inside the block,
|
||||
* preserved verbatim from the source. `closer` is the closing brace(s).
|
||||
*
|
||||
* @typedef {object} ParsedBlock
|
||||
* @property {string} header
|
||||
* @property {string[]} declarations
|
||||
* @property {string} closer
|
||||
*/
|
||||
|
||||
/**
|
||||
* Split the styleframe CSS output into top-level blocks. Each block is one of:
|
||||
*
|
||||
* :root { … }
|
||||
* html[data-theme="…"] { … }
|
||||
* @media (…) { :root { … } }
|
||||
*
|
||||
* Nested `:root` inside `@media` is preserved as part of the block — the
|
||||
* inner declarations are kept as a flat list and re-wrapped on emit.
|
||||
*
|
||||
* @param {string} css
|
||||
* @returns {ParsedBlock[]}
|
||||
*/
|
||||
function parseBlocks(css) {
|
||||
/** @type {ParsedBlock[]} */
|
||||
const blocks = [];
|
||||
|
||||
// Top-level blocks separated by blank lines in styleframe's output.
|
||||
// Each block ends with a balanced closing brace at column zero. We don't
|
||||
// need a real CSS parser — the styleframe emitter is regular enough that
|
||||
// a small line-based state machine handles it.
|
||||
const lines = css.split("\n");
|
||||
let i = 0;
|
||||
|
||||
while (i < lines.length) {
|
||||
while (i < lines.length && (lines[i] ?? "").trim() === "") i++;
|
||||
if (i >= lines.length) break;
|
||||
|
||||
const opener = lines[i] ?? "";
|
||||
const header = opener.trim();
|
||||
if (!header.endsWith("{")) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const openerIndent = opener.match(/^\s*/)?.[0] ?? "";
|
||||
i++;
|
||||
/** @type {string[]} */
|
||||
const innerLines = [];
|
||||
while (i < lines.length) {
|
||||
const line = lines[i] ?? "";
|
||||
const trimmed = line.trim();
|
||||
const indent = line.match(/^\s*/)?.[0] ?? "";
|
||||
if (trimmed === "}" && indent === openerIndent) {
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
innerLines.push(line);
|
||||
i++;
|
||||
}
|
||||
|
||||
blocks.push({
|
||||
header,
|
||||
declarations: innerLines,
|
||||
closer: "}",
|
||||
});
|
||||
}
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract every `--ak-*: …;` declaration from a flat list of lines (which may
|
||||
* include a nested `:root { … }` wrapper from an `@media` block).
|
||||
*
|
||||
* @param {string[]} lines
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function flattenDeclarations(lines) {
|
||||
return lines.filter((line) => /^\s*--ak-[a-z0-9-]+\s*:/.test(line));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the CSS for one category by filtering each parsed block to that
|
||||
* category's declarations and re-wrapping them.
|
||||
*
|
||||
* @param {Category} category
|
||||
* @param {ParsedBlock[]} blocks
|
||||
*/
|
||||
function buildCategoryFile(category, blocks) {
|
||||
/** @param {string} line */
|
||||
const matches = (line) => {
|
||||
const declaration = line.match(/--ak-([a-z0-9-]+):/);
|
||||
if (!declaration || !declaration[1]) return false;
|
||||
const name = declaration[1];
|
||||
return category.prefixes.some((prefix) => name.startsWith(prefix));
|
||||
};
|
||||
|
||||
/** @type {string[]} */
|
||||
const sections = [];
|
||||
|
||||
for (const block of blocks) {
|
||||
if (block.header.startsWith("@media")) {
|
||||
// Drill into the inner `:root { … }` wrapper.
|
||||
const inner = flattenDeclarations(block.declarations).filter(matches);
|
||||
if (inner.length === 0) continue;
|
||||
sections.push(
|
||||
`${block.header}\n\t:root {\n${inner
|
||||
.map((line) => "\t\t" + line.trim())
|
||||
.join("\n")}\n\t}\n}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const filtered = block.declarations.filter(matches);
|
||||
if (filtered.length === 0) continue;
|
||||
sections.push(
|
||||
`${block.header}\n${filtered.map((line) => "\t" + line.trim()).join("\n")}\n}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (sections.length === 0) return null;
|
||||
return HEADER + sections.join("\n\n") + "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the index.css that `@import`s every emitted category file in order.
|
||||
*
|
||||
* @param {string[]} emittedNames
|
||||
*/
|
||||
function buildIndex(emittedNames) {
|
||||
const imports = emittedNames.map((name) => `@import "./${name}.css";`).join("\n");
|
||||
return HEADER + imports + "\n";
|
||||
}
|
||||
|
||||
await mkdir(OUT_DIR, { recursive: true });
|
||||
|
||||
const { css } = await build();
|
||||
const blocks = parseBlocks(css);
|
||||
|
||||
const emitted = [];
|
||||
for (const category of CATEGORIES) {
|
||||
const content = buildCategoryFile(category, blocks);
|
||||
if (content === null) {
|
||||
console.warn(`⚠️ No declarations found for category ${category.name}; skipping.`);
|
||||
continue;
|
||||
}
|
||||
await writeFile(resolve(OUT_DIR, `${category.name}.css`), content, "utf-8");
|
||||
emitted.push(category.name);
|
||||
}
|
||||
|
||||
await writeFile(resolve(OUT_DIR, "index.css"), buildIndex(emitted), "utf-8");
|
||||
|
||||
console.log(`✅ Wrote dist/index.css + ${emitted.length} category files (${emitted.join(", ")})`);
|
||||
@@ -9,5 +9,5 @@
|
||||
* filesystem live in `./lib/node.js` (exposed via the `./build` subpath).
|
||||
*/
|
||||
|
||||
export { instance, ref, selector, theme, variable } from "./lib/shared.js";
|
||||
export * from "./lib/tokens/index.js";
|
||||
export { instance, ref, selector, theme, variable } from "./shared.js";
|
||||
export * from "./tokens/index.js";
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*
|
||||
* @file Node-only build helpers for `@goauthentik/theme`.
|
||||
*
|
||||
* Re-exports everything the browser entry exports, plus filesystem-touching
|
||||
@@ -7,7 +7,8 @@
|
||||
* @import { OutputFile } from "@styleframe/transpiler";
|
||||
*/
|
||||
|
||||
/// <reference types="../types/node.js" />
|
||||
|
||||
import type { OutputFile } from "@styleframe/transpiler";
|
||||
|
||||
import { writeFile } from "node:fs/promises";
|
||||
|
||||
@@ -28,6 +29,10 @@ export * from "./tokens/index.js";
|
||||
* this absolute path in addition to being returned.
|
||||
*/
|
||||
|
||||
interface BuildOptions {
|
||||
outFile?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} BuildResult
|
||||
* @property {string} css The full CSS string emitted by styleframe.
|
||||
@@ -35,6 +40,11 @@ export * from "./tokens/index.js";
|
||||
* to inspect every file styleframe produced.
|
||||
*/
|
||||
|
||||
interface BuildResult {
|
||||
css: string;
|
||||
files: OutputFile[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Transpile the configured token tree to CSS.
|
||||
*
|
||||
@@ -44,7 +54,7 @@ export * from "./tokens/index.js";
|
||||
* @param {BuildOptions} [options]
|
||||
* @returns {Promise<BuildResult>}
|
||||
*/
|
||||
export async function build(options = {}) {
|
||||
export async function build(options: BuildOptions = {}): Promise<BuildResult> {
|
||||
const output = await transpile(instance, { type: "css" });
|
||||
const cssFile = output.files.find((file) => file.name.endsWith(".css"));
|
||||
const css = cssFile?.content ?? "";
|
||||
@@ -25,11 +25,12 @@ import { styleframe } from "styleframe";
|
||||
* @type {StyleframeOptions}
|
||||
*/
|
||||
export const authentikStyleframeOptions = {
|
||||
indent: " ",
|
||||
variables: {
|
||||
name: ({ name }) => "ak-" + name.replace(/\./g, "-"),
|
||||
name: ({ name }: { name: string }) => `ak-${name.replace(/\./g, "-")}`
|
||||
},
|
||||
themes: {
|
||||
selector: ({ name }) => `html[data-theme="${name}"]`,
|
||||
selector: ({ name }: { name: string}) => `html[data-theme="${name}"]`,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* @file Color tokens — semantic surface, text, state, and brand colors.
|
||||
*
|
||||
* Light values are declared via `variable()`. Dark values are declared inside
|
||||
* the `dark` theme block so they emit under `html[data-theme="dark"]`.
|
||||
*
|
||||
* Link tokens are wired through `ref()` so brand overrides to `color.primary`
|
||||
* cascade to links without separate overrides. The dark theme intentionally
|
||||
* re-points links to their own oklab values rather than chaining through
|
||||
* primary because dark mode links need higher luminance than primary buttons.
|
||||
*
|
||||
* `warning` and `danger` deliberately stay on light values in dark mode — state
|
||||
* colors keep consistent intensity across themes so warnings read as urgent.
|
||||
*/
|
||||
|
||||
import { instance, theme } from "../shared.js";
|
||||
|
||||
import { createVariableFunction } from "styleframe";
|
||||
import { createUseVariable } from "@styleframe/theme";
|
||||
import { formatHex, oklch as toOklch, toGamut } from "culori";
|
||||
|
||||
/*
|
||||
* Restrict the OKLCH values to six decimal places; Javascript will give it to you accurate to 16
|
||||
* places, but OKLCH doesn't much care past six, and 16 is just unreadable.
|
||||
*
|
||||
* Includes in each OKLCH output line a comment containing the RGB value, to help IDEs show the
|
||||
* color accurately.
|
||||
*/
|
||||
|
||||
|
||||
const toSrgb = toGamut("rgb", "oklch");
|
||||
const round = (v: number, precision = 6) => Number(v.toFixed(precision)).toString();
|
||||
const oklchTransform = (value: string) => {
|
||||
const c = toOklch(value);
|
||||
if (!c || c.l == null || c.c == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const result = `oklch(${round(c.l)} ${round(c.c)} ${round(c.h ?? 0)} / ${round(c.alpha ?? 1)})`;
|
||||
return `${result} /* ${formatHex(toSrgb(c))} */`;
|
||||
};
|
||||
const useColorDesignTokens = createUseVariable("color", { transform: oklchTransform });
|
||||
|
||||
export const {
|
||||
colorAccent,
|
||||
colorPrimary,
|
||||
colorPrimaryHover,
|
||||
colorText,
|
||||
colorTextMuted,
|
||||
colorLink,
|
||||
colorLinkHover,
|
||||
colorLinkVisited,
|
||||
colorSurface,
|
||||
colorSurfaceRaised,
|
||||
colorSurfaceMuted,
|
||||
colorBorder,
|
||||
colorBorderStrong,
|
||||
colorInfo,
|
||||
colorSuccess,
|
||||
colorWarning,
|
||||
colorDanger,
|
||||
} = useColorDesignTokens(instance, {
|
||||
"accent": "#fd4b2d",
|
||||
"primary": "#0066cc",
|
||||
"primary-hover": "#004080",
|
||||
"text": "#151515",
|
||||
"text-muted": "#6a6e73",
|
||||
"link": "@color.primary",
|
||||
"link-hover": "@color.primary-hover",
|
||||
"link-visited": "#40199a",
|
||||
"surface": "#ffffff",
|
||||
"surface-raised": "#fafafa",
|
||||
"surface-muted": "#f0f0f0",
|
||||
"border": "#d2d2d2",
|
||||
"border-strong": "#8a8d90",
|
||||
"info": "#2b9af3",
|
||||
"success": "#3e8635",
|
||||
"warning": "#f0ab00",
|
||||
"danger": "#c9190b",
|
||||
});
|
||||
|
||||
type Variable = ReturnType<ReturnType<typeof createVariableFunction>>;
|
||||
type VPPair = [Variable, string];
|
||||
|
||||
// Dark theme overrides. Surface values are pinned near PatternFly 4's
|
||||
// BackgroundColor--100 (#151515) and the legacy drawer surface (#18191a) — the
|
||||
// earlier oklab(0.23) value visibly brightened every PF-backed dark panel.
|
||||
theme("dark", (ctx) => {
|
||||
|
||||
const darkColors: VPPair[] = [
|
||||
[colorText, "#e0e0e0"],
|
||||
[colorTextMuted, "#aaabac"],
|
||||
[colorLink, "#20a9f8"],
|
||||
[colorLinkHover, "#73bcf7"],
|
||||
[colorLinkVisited, "#a18fff"],
|
||||
[colorSurface, "#121212"],
|
||||
[colorSurfaceMuted, "#090909"],
|
||||
[colorSurfaceRaised, "#1c1c1c"],
|
||||
[colorBorder, "#444548"],
|
||||
[colorBorderStrong, "#57585a"],
|
||||
[colorInfo, "#73bcf7"],
|
||||
[colorSuccess, "#5ba352"]
|
||||
];
|
||||
|
||||
darkColors.forEach(([v, p]) => ctx.variable(v, oklchTransform(p)));
|
||||
});
|
||||
@@ -11,8 +11,8 @@
|
||||
* the same tree the rest of the package uses.
|
||||
*/
|
||||
|
||||
import "./lib/tokens/index.js";
|
||||
import "./src/tokens/index.js";
|
||||
|
||||
import { instance } from "./lib/shared.js";
|
||||
import { instance } from "./src/shared.js";
|
||||
|
||||
export default instance;
|
||||
Vendored
-27
@@ -1,27 +0,0 @@
|
||||
declare module "process" {
|
||||
import { Level } from "pino";
|
||||
|
||||
global {
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
/**
|
||||
* An environment variable used to determine
|
||||
* whether Node.js is running in production mode.
|
||||
*
|
||||
* @see {@link https://nodejs.org/en/learn/getting-started/nodejs-the-difference-between-development-and-production | The difference between development and production}
|
||||
*/
|
||||
readonly NODE_ENV?: "development" | "production";
|
||||
|
||||
/**
|
||||
* Whether or not we are running on a CI server.
|
||||
*/
|
||||
readonly CI?: string;
|
||||
|
||||
/**
|
||||
* The application log level.
|
||||
*/
|
||||
readonly AK_LOG_LEVEL?: Level;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user