@seyuna/postcss 1.0.0-canary.26 → 1.0.0-canary.27

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 CHANGED
@@ -1,3 +1,10 @@
1
+ # [1.0.0-canary.27](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.26...v1.0.0-canary.27) (2026-01-20)
2
+
3
+
4
+ ### Features
5
+
6
+ * implement [@import](https://github.com/import) 'seyuna' to replicate CLI behavior ([db3a3ff](https://github.com/seyuna-corp/seyuna-postcss/commit/db3a3fff3664b435840e7421d9b99262dfd31953))
7
+
1
8
  # [1.0.0-canary.26](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.25...v1.0.0-canary.26) (2026-01-11)
2
9
 
3
10
 
@@ -0,0 +1,6 @@
1
+ import { PluginContext } from "../types.js";
2
+ /**
3
+ * Handler for @import "seyuna"
4
+ * Injects the core Seyuna Design System variables and base styles
5
+ */
6
+ export declare function handleImport(atRule: any, context: PluginContext): void;
@@ -0,0 +1,120 @@
1
+ import Rule from "postcss/lib/rule";
2
+ import Declaration from "postcss/lib/declaration";
3
+ import AtRule from "postcss/lib/at-rule";
4
+ import fs from "fs";
5
+ import path from "path";
6
+ import { fileURLToPath } from "url";
7
+ import postcss from "postcss";
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ const STANDARD_HUES = [
11
+ 'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta', 'eta', 'theta', 'iota', 'kappa', 'lambda', 'mu', 'nu', 'xi', 'omicron', 'pi', 'rho', 'sigma', 'tau', 'upsilon', 'phi', 'chi', 'psi', 'omega'
12
+ ];
13
+ /**
14
+ * Handler for @import "seyuna"
15
+ * Injects the core Seyuna Design System variables and base styles
16
+ */
17
+ export function handleImport(atRule, context) {
18
+ const params = atRule.params.replace(/['"]/g, "");
19
+ if (params === "seyuna") {
20
+ const { config, options } = context;
21
+ if (!config.ui) {
22
+ atRule.remove();
23
+ return;
24
+ }
25
+ const { theme } = config.ui;
26
+ const modeAttribute = options.modeAttribute;
27
+ const nodes = [];
28
+ // 1. Ingest Global Base Styles (Reset, Layers, etc.)
29
+ try {
30
+ const globalStylesPath = path.resolve(__dirname, "../styles/seyuna-global.css");
31
+ if (fs.existsSync(globalStylesPath)) {
32
+ const globalCss = fs.readFileSync(globalStylesPath, "utf-8");
33
+ const cleanCss = globalCss.split('$')[0].trim();
34
+ const parsed = postcss.parse(cleanCss);
35
+ // Only keep layers and reset styles, avoid re-injecting variables
36
+ parsed.each((node) => {
37
+ if (node.type === 'atrule' && (node.name === 'layer' || node.name === 'media')) {
38
+ nodes.push(node.clone());
39
+ }
40
+ });
41
+ }
42
+ else {
43
+ nodes.push(new AtRule({ name: "layer", params: "reset, base, components, utilities" }));
44
+ }
45
+ }
46
+ catch (e) {
47
+ console.warn("[Seyuna PostCSS] Could not load global base styles:", e);
48
+ }
49
+ // 2. Global Hues and Base Colors (:root)
50
+ const rootRule = new Rule({ selector: ":root", source: atRule.source });
51
+ // Process all hues from config plus defaults
52
+ const allHues = new Set([...STANDARD_HUES, ...Object.keys(theme.hues || {})]);
53
+ allHues.forEach((name) => {
54
+ const i = STANDARD_HUES.indexOf(name);
55
+ const defaultValue = i !== -1 ? i * 15 : 0;
56
+ const value = (theme.hues && theme.hues[name] !== undefined) ? theme.hues[name] : defaultValue;
57
+ rootRule.append(new Declaration({ prop: `--${name}-hue`, value: String(value) }));
58
+ });
59
+ // Add shared colors from theme.colors
60
+ if (theme.colors) {
61
+ for (const [name, color] of Object.entries(theme.colors)) {
62
+ rootRule.append(new Declaration({ prop: `--${name}-lightness`, value: String(color.lightness) }));
63
+ rootRule.append(new Declaration({ prop: `--${name}-chroma`, value: String(color.chroma) }));
64
+ rootRule.append(new Declaration({ prop: `--${name}-hue`, value: String(color.hue) }));
65
+ }
66
+ }
67
+ nodes.push(rootRule);
68
+ // Helper to generate palette declarations for a specific mode
69
+ const getPaletteDecls = (palette) => {
70
+ const decls = [];
71
+ if (palette.lightness !== undefined) {
72
+ decls.push(new Declaration({ prop: "--lightness", value: String(palette.lightness) }));
73
+ }
74
+ if (palette.chroma !== undefined) {
75
+ decls.push(new Declaration({ prop: "--chroma", value: String(palette.chroma) }));
76
+ }
77
+ // Standard background/text if they exist
78
+ if (palette.background) {
79
+ decls.push(new Declaration({ prop: "--background", value: `oklch(${palette.background.lightness} ${palette.background.chroma} ${palette.background.hue})` }));
80
+ }
81
+ if (palette.text) {
82
+ decls.push(new Declaration({ prop: "--text", value: `oklch(${palette.text.lightness} ${palette.text.chroma} ${palette.text.hue})` }));
83
+ }
84
+ if (palette.colors) {
85
+ for (const [name, color] of Object.entries(palette.colors)) {
86
+ const c = color;
87
+ decls.push(new Declaration({ prop: `--${name}-lightness`, value: String(c.lightness) }));
88
+ decls.push(new Declaration({ prop: `--${name}-chroma`, value: String(c.chroma) }));
89
+ decls.push(new Declaration({ prop: `--${name}-hue`, value: String(c.hue) }));
90
+ }
91
+ }
92
+ return decls;
93
+ };
94
+ // 3. Explicit Mode Selectors ([data-mode="..."])
95
+ if (theme.light) {
96
+ const lightRule = new Rule({ selector: `[${modeAttribute}="light"]`, source: atRule.source });
97
+ lightRule.append(...getPaletteDecls(theme.light));
98
+ nodes.push(lightRule);
99
+ }
100
+ if (theme.dark) {
101
+ const darkRule = new Rule({ selector: `[${modeAttribute}="dark"]`, source: atRule.source });
102
+ darkRule.append(...getPaletteDecls(theme.dark));
103
+ nodes.push(darkRule);
104
+ }
105
+ // 4. System Preference Support
106
+ const createSystemMedia = (scheme, palette) => {
107
+ const media = new AtRule({ name: "media", params: `(prefers-color-scheme: ${scheme})`, source: atRule.source });
108
+ const rule = new Rule({ selector: `[${modeAttribute}="system"]`, source: atRule.source });
109
+ rule.append(...getPaletteDecls(palette));
110
+ media.append(rule);
111
+ return media;
112
+ };
113
+ if (theme.light)
114
+ nodes.push(createSystemMedia('light', theme.light));
115
+ if (theme.dark)
116
+ nodes.push(createSystemMedia('dark', theme.dark));
117
+ // Replace the @import rule with our generated CSS
118
+ atRule.replaceWith(...nodes);
119
+ }
120
+ }
@@ -2,8 +2,10 @@
2
2
  import { eachStandardColor, eachFixedColor } from "./color.js";
3
3
  import container from "./container.js";
4
4
  import { light, dark } from "./color-scheme.js";
5
+ import { handleImport } from "./import.js";
5
6
  // Ordered array ensures execution order
6
7
  export const atRuleHandlers = [
8
+ { name: "import", handler: handleImport },
7
9
  { name: "each-standard-color", handler: eachStandardColor },
8
10
  { name: "each-fixed-color", handler: eachFixedColor },
9
11
  { name: "light", handler: light },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seyuna/postcss",
3
- "version": "1.0.0-canary.26",
3
+ "version": "1.0.0-canary.27",
4
4
  "description": "Seyuna UI's postcss plugin",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,136 @@
1
+ import Rule from "postcss/lib/rule";
2
+ import Declaration from "postcss/lib/declaration";
3
+ import AtRule from "postcss/lib/at-rule";
4
+ import { PluginContext } from "../types.js";
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import { fileURLToPath } from "url";
8
+ import postcss from "postcss";
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+
13
+ const STANDARD_HUES = [
14
+ 'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta', 'eta', 'theta', 'iota', 'kappa', 'lambda', 'mu', 'nu', 'xi', 'omicron', 'pi', 'rho', 'sigma', 'tau', 'upsilon', 'phi', 'chi', 'psi', 'omega'
15
+ ];
16
+
17
+ /**
18
+ * Handler for @import "seyuna"
19
+ * Injects the core Seyuna Design System variables and base styles
20
+ */
21
+ export function handleImport(atRule: any, context: PluginContext) {
22
+ const params = atRule.params.replace(/['"]/g, "");
23
+
24
+ if (params === "seyuna") {
25
+ const { config, options } = context;
26
+ if (!config.ui) {
27
+ atRule.remove();
28
+ return;
29
+ }
30
+
31
+ const { theme } = config.ui;
32
+ const modeAttribute = options.modeAttribute;
33
+ const nodes: any[] = [];
34
+
35
+ // 1. Ingest Global Base Styles (Reset, Layers, etc.)
36
+ try {
37
+ const globalStylesPath = path.resolve(__dirname, "../styles/seyuna-global.css");
38
+ if (fs.existsSync(globalStylesPath)) {
39
+ const globalCss = fs.readFileSync(globalStylesPath, "utf-8");
40
+ const cleanCss = globalCss.split('$')[0].trim();
41
+ const parsed = postcss.parse(cleanCss);
42
+
43
+ // Only keep layers and reset styles, avoid re-injecting variables
44
+ parsed.each((node) => {
45
+ if (node.type === 'atrule' && (node.name === 'layer' || node.name === 'media')) {
46
+ nodes.push(node.clone());
47
+ }
48
+ });
49
+ } else {
50
+ nodes.push(new AtRule({ name: "layer", params: "reset, base, components, utilities" }));
51
+ }
52
+ } catch (e) {
53
+ console.warn("[Seyuna PostCSS] Could not load global base styles:", e);
54
+ }
55
+
56
+ // 2. Global Hues and Base Colors (:root)
57
+ const rootRule = new Rule({ selector: ":root", source: atRule.source });
58
+
59
+ // Process all hues from config plus defaults
60
+ const allHues = new Set([...STANDARD_HUES, ...Object.keys(theme.hues || {})]);
61
+ allHues.forEach((name) => {
62
+ const i = STANDARD_HUES.indexOf(name);
63
+ const defaultValue = i !== -1 ? i * 15 : 0;
64
+ const value = (theme.hues && theme.hues[name] !== undefined) ? theme.hues[name] : defaultValue;
65
+ rootRule.append(new Declaration({ prop: `--${name}-hue`, value: String(value) }));
66
+ });
67
+
68
+ // Add shared colors from theme.colors
69
+ if (theme.colors) {
70
+ for (const [name, color] of Object.entries(theme.colors)) {
71
+ rootRule.append(new Declaration({ prop: `--${name}-lightness`, value: String(color.lightness) }));
72
+ rootRule.append(new Declaration({ prop: `--${name}-chroma`, value: String(color.chroma) }));
73
+ rootRule.append(new Declaration({ prop: `--${name}-hue`, value: String(color.hue) }));
74
+ }
75
+ }
76
+ nodes.push(rootRule);
77
+
78
+ // Helper to generate palette declarations for a specific mode
79
+ const getPaletteDecls = (palette: any) => {
80
+ const decls: Declaration[] = [];
81
+
82
+ if (palette.lightness !== undefined) {
83
+ decls.push(new Declaration({ prop: "--lightness", value: String(palette.lightness) }));
84
+ }
85
+ if (palette.chroma !== undefined) {
86
+ decls.push(new Declaration({ prop: "--chroma", value: String(palette.chroma) }));
87
+ }
88
+
89
+ // Standard background/text if they exist
90
+ if (palette.background) {
91
+ decls.push(new Declaration({ prop: "--background", value: `oklch(${palette.background.lightness} ${palette.background.chroma} ${palette.background.hue})` }));
92
+ }
93
+ if (palette.text) {
94
+ decls.push(new Declaration({ prop: "--text", value: `oklch(${palette.text.lightness} ${palette.text.chroma} ${palette.text.hue})` }));
95
+ }
96
+
97
+ if (palette.colors) {
98
+ for (const [name, color] of Object.entries(palette.colors)) {
99
+ const c = color as any;
100
+ decls.push(new Declaration({ prop: `--${name}-lightness`, value: String(c.lightness) }));
101
+ decls.push(new Declaration({ prop: `--${name}-chroma`, value: String(c.chroma) }));
102
+ decls.push(new Declaration({ prop: `--${name}-hue`, value: String(c.hue) }));
103
+ }
104
+ }
105
+ return decls;
106
+ };
107
+
108
+ // 3. Explicit Mode Selectors ([data-mode="..."])
109
+ if (theme.light) {
110
+ const lightRule = new Rule({ selector: `[${modeAttribute}="light"]`, source: atRule.source });
111
+ lightRule.append(...getPaletteDecls(theme.light));
112
+ nodes.push(lightRule);
113
+ }
114
+
115
+ if (theme.dark) {
116
+ const darkRule = new Rule({ selector: `[${modeAttribute}="dark"]`, source: atRule.source });
117
+ darkRule.append(...getPaletteDecls(theme.dark));
118
+ nodes.push(darkRule);
119
+ }
120
+
121
+ // 4. System Preference Support
122
+ const createSystemMedia = (scheme: 'light' | 'dark', palette: any) => {
123
+ const media = new AtRule({ name: "media", params: `(prefers-color-scheme: ${scheme})`, source: atRule.source });
124
+ const rule = new Rule({ selector: `[${modeAttribute}="system"]`, source: atRule.source });
125
+ rule.append(...getPaletteDecls(palette));
126
+ media.append(rule);
127
+ return media;
128
+ };
129
+
130
+ if (theme.light) nodes.push(createSystemMedia('light', theme.light));
131
+ if (theme.dark) nodes.push(createSystemMedia('dark', theme.dark));
132
+
133
+ // Replace the @import rule with our generated CSS
134
+ atRule.replaceWith(...nodes);
135
+ }
136
+ }
@@ -2,6 +2,7 @@
2
2
  import { eachStandardColor, eachFixedColor } from "./color.js";
3
3
  import container from "./container.js";
4
4
  import { light, dark } from "./color-scheme.js";
5
+ import { handleImport } from "./import.js";
5
6
  import { PluginContext } from "../types.js";
6
7
 
7
8
  // Each handler has a name (matches the at-rule) and the function
@@ -12,6 +13,7 @@ export interface AtRuleHandler {
12
13
 
13
14
  // Ordered array ensures execution order
14
15
  export const atRuleHandlers: AtRuleHandler[] = [
16
+ { name: "import", handler: handleImport },
15
17
  { name: "each-standard-color", handler: eachStandardColor },
16
18
  { name: "each-fixed-color", handler: eachFixedColor },
17
19
  { name: "light", handler: light },
@@ -140,4 +140,31 @@ describe('Seyuna PostCSS Plugin', () => {
140
140
  await expect(run(input, { config: mockConfig, strict: true }))
141
141
  .rejects.toThrow(/Color 'unknown' not found in seyuna.json/);
142
142
  });
143
+
144
+ it('processes @import "seyuna"', async () => {
145
+ const input = '@import "seyuna";';
146
+ const result = await run(input, { config: mockConfig });
147
+
148
+ // Check for root variables (hues)
149
+ expect(result.css).toContain(':root');
150
+ expect(result.css).toContain('--alpha-hue: 0');
151
+ expect(result.css).toContain('--omega-hue: 345');
152
+ expect(result.css).toContain('--primary-hue: 200'); // Override from mockConfig
153
+
154
+ // Check for mode selectors
155
+ expect(result.css).toContain('[data-mode="light"]');
156
+ expect(result.css).toContain('[data-mode="dark"]');
157
+ expect(result.css).toContain('--lightness: 0.66');
158
+
159
+ // Check for system preference media queries
160
+ expect(result.css).toContain('@media (prefers-color-scheme: light)');
161
+ expect(result.css).toContain('@media (prefers-color-scheme: dark)');
162
+
163
+ // Check for base styles (reset)
164
+ expect(result.css).toContain('@layer reset');
165
+ expect(result.css).toContain('-webkit-text-size-adjust: none');
166
+
167
+ // Check for palette colors
168
+ expect(result.css).toContain('--surface-lightness: 1');
169
+ });
143
170
  });