@seyuna/postcss 0.0.1-dev.9 → 1.0.0-canary.10
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/.github/workflows/release.yml +19 -134
- package/CHANGELOG.md +79 -0
- package/dist/at-rules/color.d.ts +34 -0
- package/dist/at-rules/color.js +105 -0
- package/dist/at-rules/container.d.ts +18 -0
- package/dist/at-rules/container.js +44 -0
- package/dist/at-rules/dark.d.ts +22 -1
- package/dist/at-rules/dark.js +57 -5
- package/dist/at-rules/index.d.ts +5 -2
- package/dist/at-rules/index.js +16 -4
- package/dist/at-rules/light.d.ts +22 -1
- package/dist/at-rules/light.js +57 -5
- package/dist/functions/color.d.ts +2 -1
- package/dist/functions/color.js +19 -3
- package/dist/functions/index.js +3 -2
- package/dist/plugin.js +8 -2
- package/dist/types.d.ts +19 -0
- package/dist/types.js +2 -0
- package/package.json +16 -5
- package/release.config.mjs +32 -0
- package/src/at-rules/color.ts +118 -0
- package/src/at-rules/container.ts +47 -0
- package/src/at-rules/dark.ts +63 -6
- package/src/at-rules/index.ts +21 -5
- package/src/at-rules/light.ts +63 -6
- package/src/functions/color.ts +27 -2
- package/src/functions/index.ts +3 -2
- package/src/plugin.ts +8 -2
- package/src/types.ts +19 -0
|
@@ -1,156 +1,41 @@
|
|
|
1
|
-
name:
|
|
1
|
+
name: Release
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
5
|
branches:
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
- dev
|
|
9
|
-
workflow_dispatch:
|
|
6
|
+
- main
|
|
7
|
+
- canary
|
|
10
8
|
|
|
11
9
|
permissions:
|
|
12
10
|
contents: write
|
|
11
|
+
issues: write
|
|
12
|
+
pull-requests: write
|
|
13
13
|
id-token: write
|
|
14
14
|
|
|
15
15
|
jobs:
|
|
16
|
-
|
|
17
|
-
name:
|
|
16
|
+
release:
|
|
17
|
+
name: Semantic Release
|
|
18
18
|
runs-on: ubuntu-latest
|
|
19
|
-
outputs:
|
|
20
|
-
version: ${{ steps.version.outputs.version }}
|
|
21
|
-
|
|
22
19
|
steps:
|
|
23
|
-
-
|
|
24
|
-
uses: actions/checkout@v4
|
|
25
|
-
|
|
26
|
-
- name: Read version from package.json
|
|
27
|
-
id: version
|
|
28
|
-
run: |
|
|
29
|
-
VERSION=$(node -p "require('./package.json').version")
|
|
30
|
-
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
31
|
-
|
|
32
|
-
- name: Set up Node.js
|
|
33
|
-
uses: actions/setup-node@v4
|
|
34
|
-
with:
|
|
35
|
-
node-version: 20
|
|
36
|
-
registry-url: https://registry.npmjs.org
|
|
37
|
-
|
|
38
|
-
- name: Install dependencies
|
|
39
|
-
run: npm ci
|
|
40
|
-
|
|
41
|
-
- name: Build TypeScript
|
|
42
|
-
run: npm run build
|
|
43
|
-
|
|
44
|
-
- name: List dist contents before upload
|
|
45
|
-
run: ls -l dist
|
|
46
|
-
|
|
47
|
-
- name: Upload build artifact
|
|
48
|
-
uses: actions/upload-artifact@v4
|
|
20
|
+
- uses: actions/checkout@v4
|
|
49
21
|
with:
|
|
50
|
-
|
|
51
|
-
path: dist
|
|
22
|
+
fetch-depth: 0
|
|
52
23
|
|
|
53
|
-
|
|
54
|
-
name: Publish to npm
|
|
55
|
-
runs-on: ubuntu-latest
|
|
56
|
-
needs: build
|
|
57
|
-
|
|
58
|
-
steps:
|
|
59
|
-
- name: Checkout repository
|
|
60
|
-
uses: actions/checkout@v4
|
|
61
|
-
|
|
62
|
-
- name: Set up Node.js
|
|
24
|
+
- name: Setup Node.js with npm auth
|
|
63
25
|
uses: actions/setup-node@v4
|
|
64
26
|
with:
|
|
65
27
|
node-version: 20
|
|
66
|
-
registry-url: https://registry.npmjs.org
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
name: plugin-dist
|
|
72
|
-
path: dist
|
|
73
|
-
|
|
74
|
-
- name: Authenticate with npm
|
|
75
|
-
run: npm config set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}
|
|
76
|
-
|
|
77
|
-
- name: Determine npm tag
|
|
78
|
-
id: tag
|
|
79
|
-
run: |
|
|
80
|
-
BRANCH="${GITHUB_REF##*/}"
|
|
81
|
-
if [ "$BRANCH" = "stable" ]; then
|
|
82
|
-
echo "tag=latest" >> $GITHUB_OUTPUT
|
|
83
|
-
else
|
|
84
|
-
echo "tag=$BRANCH" >> $GITHUB_OUTPUT
|
|
85
|
-
fi
|
|
28
|
+
registry-url: https://registry.npmjs.org/
|
|
29
|
+
scope: "@seyuna"
|
|
30
|
+
always-auth: true
|
|
31
|
+
env:
|
|
32
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
86
33
|
|
|
87
34
|
- name: Install dependencies
|
|
88
35
|
run: npm ci
|
|
89
36
|
|
|
90
|
-
- name:
|
|
91
|
-
id: version_check
|
|
92
|
-
run: |
|
|
93
|
-
PKG_NAME=$(node -p "require('./package.json').name")
|
|
94
|
-
PKG_VERSION=$(node -p "require('./package.json').version")
|
|
95
|
-
if npm view "$PKG_NAME@$PKG_VERSION" > /dev/null 2>&1; then
|
|
96
|
-
echo "exists=true" >> $GITHUB_OUTPUT
|
|
97
|
-
else
|
|
98
|
-
echo "exists=false" >> $GITHUB_OUTPUT
|
|
99
|
-
fi
|
|
100
|
-
|
|
101
|
-
- name: Publish package
|
|
102
|
-
if: steps.version_check.outputs.exists != 'true'
|
|
103
|
-
run: npm publish --access public --tag ${{ steps.tag.outputs.tag }}
|
|
104
|
-
|
|
105
|
-
release:
|
|
106
|
-
name: Create GitHub Release
|
|
107
|
-
runs-on: ubuntu-latest
|
|
108
|
-
needs: [build, publish]
|
|
109
|
-
|
|
110
|
-
steps:
|
|
111
|
-
- name: Checkout repository
|
|
112
|
-
uses: actions/checkout@v4
|
|
113
|
-
|
|
114
|
-
- name: Download build artifact
|
|
115
|
-
uses: actions/download-artifact@v4
|
|
116
|
-
with:
|
|
117
|
-
name: plugin-dist
|
|
118
|
-
path: dist
|
|
119
|
-
|
|
120
|
-
- name: Generate changelog
|
|
121
|
-
run: |
|
|
122
|
-
last_tag=$(git tag --sort=-creatordate | grep '^v' | grep -v "v${{ needs.build.outputs.version }}" | head -n 1)
|
|
123
|
-
|
|
124
|
-
if [ -z "$last_tag" ]; then
|
|
125
|
-
changelog=$(git log --merges --pretty=format:"* %s (%h)")
|
|
126
|
-
else
|
|
127
|
-
changelog=$(git log "$last_tag"..HEAD --merges --pretty=format:"* %s (%h)")
|
|
128
|
-
fi
|
|
129
|
-
|
|
130
|
-
{
|
|
131
|
-
echo "Automated release for version v${{ needs.build.outputs.version }}"
|
|
132
|
-
echo ""
|
|
133
|
-
echo "## Changes"
|
|
134
|
-
echo ""
|
|
135
|
-
echo "$changelog"
|
|
136
|
-
} > RELEASE_NOTES.md
|
|
137
|
-
|
|
138
|
-
- name: Check if GitHub release exists
|
|
139
|
-
id: release_check
|
|
140
|
-
run: |
|
|
141
|
-
if gh release view "v${{ needs.build.outputs.version }}" >/dev/null 2>&1; then
|
|
142
|
-
echo "exists=true" >> $GITHUB_OUTPUT
|
|
143
|
-
else
|
|
144
|
-
echo "exists=false" >> $GITHUB_OUTPUT
|
|
145
|
-
fi
|
|
146
|
-
env:
|
|
147
|
-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
148
|
-
|
|
149
|
-
- name: Create GitHub Release
|
|
150
|
-
if: steps.release_check.outputs.exists != 'true'
|
|
151
|
-
run: |
|
|
152
|
-
gh release create "v${{ needs.build.outputs.version }}" \
|
|
153
|
-
--title "v${{ needs.build.outputs.version }}" \
|
|
154
|
-
--notes-file RELEASE_NOTES.md
|
|
37
|
+
- name: Run Semantic Release
|
|
155
38
|
env:
|
|
156
|
-
|
|
39
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
40
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
41
|
+
run: npx semantic-release
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# [1.0.0-canary.10](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.9...v1.0.0-canary.10) (2025-09-10)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* registered each breakpoint to the same handler for at-rule `container` ([f3f5b36](https://github.com/seyuna-corp/seyuna-postcss/commit/f3f5b3665291dc94e92f3f7c2b2a576b4bdb4517))
|
|
7
|
+
|
|
8
|
+
# [1.0.0-canary.9](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.8...v1.0.0-canary.9) (2025-09-10)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* colors selector in at-rule `each-fixed-color` ([31adb28](https://github.com/seyuna-corp/seyuna-postcss/commit/31adb28f3576a0dcfb78a9aadf331ee4b7ef3e0c))
|
|
14
|
+
|
|
15
|
+
# [1.0.0-canary.8](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.7...v1.0.0-canary.8) (2025-09-10)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* added at-rule `each-fixed-color` ([699ee0d](https://github.com/seyuna-corp/seyuna-postcss/commit/699ee0defd1fbb0ff91a90c1e13358b1ef9832b2))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### BREAKING CHANGES
|
|
24
|
+
|
|
25
|
+
* at-rule `each-seyuna-color` renamed to `each-standard-color`
|
|
26
|
+
|
|
27
|
+
# [1.0.0-canary.7](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.6...v1.0.0-canary.7) (2025-09-10)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Features
|
|
31
|
+
|
|
32
|
+
* Added at-rule ([8a08289](https://github.com/seyuna-corp/seyuna-postcss/commit/8a08289023f4aa6f65d56e10697e64d02444f118))
|
|
33
|
+
|
|
34
|
+
# [1.0.0-canary.6](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.5...v1.0.0-canary.6) (2025-09-09)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
### Features
|
|
38
|
+
|
|
39
|
+
* add new color function 'fc' ([70e961f](https://github.com/seyuna-corp/seyuna-postcss/commit/70e961fb0b0a13e358de15b42a87b7890f3fc5c0))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
### BREAKING CHANGES
|
|
43
|
+
|
|
44
|
+
* changed how 'sc' functions, not backward-compatible
|
|
45
|
+
|
|
46
|
+
# [1.0.0-canary.5](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.4...v1.0.0-canary.5) (2025-08-25)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
### Bug Fixes
|
|
50
|
+
|
|
51
|
+
* changed color function name to sc to avoid conflicts with the default css color() function ([a46e96c](https://github.com/seyuna-corp/seyuna-postcss/commit/a46e96c74839f930d39a2c273c822a689a942783))
|
|
52
|
+
|
|
53
|
+
# [1.0.0-canary.4](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.3...v1.0.0-canary.4) (2025-08-24)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
### Bug Fixes
|
|
57
|
+
|
|
58
|
+
* at-rules for mode now ensure that [data-mode=system] before enforcing prefers-color-scheme ([475055d](https://github.com/seyuna-corp/seyuna-postcss/commit/475055db1d5662d25631953af669bf64b2e0468e))
|
|
59
|
+
|
|
60
|
+
# [1.0.0-canary.3](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.2...v1.0.0-canary.3) (2025-08-24)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
### Bug Fixes
|
|
64
|
+
|
|
65
|
+
* prioritized data-mode over prefers-color-scheme for [@dark](https://github.com/dark) & [@light](https://github.com/light) rules ([1dafbe7](https://github.com/seyuna-corp/seyuna-postcss/commit/1dafbe74c2ceae8faf28f55ac64846e9e752405b))
|
|
66
|
+
|
|
67
|
+
# [1.0.0-canary.2](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.1...v1.0.0-canary.2) (2025-08-08)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
### Bug Fixes
|
|
71
|
+
|
|
72
|
+
* added os mode selectors to dark and light at-rules ([aab8f42](https://github.com/seyuna-corp/seyuna-postcss/commit/aab8f42f05d8bfedf45b19352134254f2da4d9f0))
|
|
73
|
+
|
|
74
|
+
# 1.0.0-canary.1 (2025-08-07)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
### Bug Fixes
|
|
78
|
+
|
|
79
|
+
* Clone all child nodes inside [@rules](https://github.com/rules) to prevent them from being detached ([6697d34](https://github.com/seyuna-corp/seyuna-postcss/commit/6697d34e604b1de1f319eee060604c4efc201251))
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AtRule } from "postcss";
|
|
2
|
+
/**
|
|
3
|
+
* Custom PostCSS plugin handler for `@each-standard-color` at-rules.
|
|
4
|
+
*
|
|
5
|
+
* Example usage:
|
|
6
|
+
*
|
|
7
|
+
* @each-standard-color {
|
|
8
|
+
* color: white;
|
|
9
|
+
* }
|
|
10
|
+
*
|
|
11
|
+
* Will generate:
|
|
12
|
+
*
|
|
13
|
+
* .alpha { color: white; }
|
|
14
|
+
* .beta { color: white; }
|
|
15
|
+
* .gamma { color: white; }
|
|
16
|
+
* ...
|
|
17
|
+
*/
|
|
18
|
+
export declare function eachStandardColor(atRule: AtRule): void;
|
|
19
|
+
/**
|
|
20
|
+
* Custom PostCSS plugin handler for `@each-fixed-color` at-rules.
|
|
21
|
+
*
|
|
22
|
+
* Example usage:
|
|
23
|
+
*
|
|
24
|
+
* @each-fixed-color {
|
|
25
|
+
* color: white;
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* Will generate:
|
|
29
|
+
*
|
|
30
|
+
* .primary { color: white; }
|
|
31
|
+
* .secondary { color: white; }
|
|
32
|
+
* ...
|
|
33
|
+
*/
|
|
34
|
+
export declare function eachFixedColor(atRule: AtRule): void;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.eachStandardColor = eachStandardColor;
|
|
7
|
+
exports.eachFixedColor = eachFixedColor;
|
|
8
|
+
const postcss_1 = require("postcss");
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
/**
|
|
12
|
+
* Custom PostCSS plugin handler for `@each-standard-color` at-rules.
|
|
13
|
+
*
|
|
14
|
+
* Example usage:
|
|
15
|
+
*
|
|
16
|
+
* @each-standard-color {
|
|
17
|
+
* color: white;
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* Will generate:
|
|
21
|
+
*
|
|
22
|
+
* .alpha { color: white; }
|
|
23
|
+
* .beta { color: white; }
|
|
24
|
+
* .gamma { color: white; }
|
|
25
|
+
* ...
|
|
26
|
+
*/
|
|
27
|
+
function eachStandardColor(atRule) {
|
|
28
|
+
// Read seyuna.json from project root
|
|
29
|
+
const jsonPath = path_1.default.resolve(process.cwd(), "seyuna.json");
|
|
30
|
+
const fileContents = fs_1.default.readFileSync(jsonPath, "utf-8");
|
|
31
|
+
const data = JSON.parse(fileContents);
|
|
32
|
+
const hues = data.ui.theme.hues;
|
|
33
|
+
const hueNamesSet = new Set(Object.keys(hues));
|
|
34
|
+
// Guard against atRule.nodes being undefined
|
|
35
|
+
const nodes = atRule.nodes ?? [];
|
|
36
|
+
const generatedRules = [];
|
|
37
|
+
// Helper to clone nodes and replace {name} placeholder
|
|
38
|
+
const cloneNodesWithName = (name) => nodes.map((node) => {
|
|
39
|
+
const cloned = node.clone();
|
|
40
|
+
// Only process declarations
|
|
41
|
+
if (cloned.type === "decl") {
|
|
42
|
+
const decl = cloned;
|
|
43
|
+
decl.value = decl.value.replace(/\{name\}/g, name);
|
|
44
|
+
}
|
|
45
|
+
return cloned;
|
|
46
|
+
});
|
|
47
|
+
// Generate rules for each hue
|
|
48
|
+
for (const hueName of hueNamesSet) {
|
|
49
|
+
const rule = new postcss_1.Rule({ selector: `.${hueName}` });
|
|
50
|
+
cloneNodesWithName(hueName).forEach((n) => rule.append(n));
|
|
51
|
+
generatedRules.push(rule);
|
|
52
|
+
}
|
|
53
|
+
// Replace the original @each-seyuna-color at-rule with all the generated rules
|
|
54
|
+
atRule.replaceWith(...generatedRules);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Custom PostCSS plugin handler for `@each-fixed-color` at-rules.
|
|
58
|
+
*
|
|
59
|
+
* Example usage:
|
|
60
|
+
*
|
|
61
|
+
* @each-fixed-color {
|
|
62
|
+
* color: white;
|
|
63
|
+
* }
|
|
64
|
+
*
|
|
65
|
+
* Will generate:
|
|
66
|
+
*
|
|
67
|
+
* .primary { color: white; }
|
|
68
|
+
* .secondary { color: white; }
|
|
69
|
+
* ...
|
|
70
|
+
*/
|
|
71
|
+
function eachFixedColor(atRule) {
|
|
72
|
+
// Read seyuna.json from project root
|
|
73
|
+
const jsonPath = path_1.default.resolve(process.cwd(), "seyuna.json");
|
|
74
|
+
const fileContents = fs_1.default.readFileSync(jsonPath, "utf-8");
|
|
75
|
+
const data = JSON.parse(fileContents);
|
|
76
|
+
const light_colors = data.ui.theme.light.colors;
|
|
77
|
+
const dark_colors = data.ui.theme.dark.colors;
|
|
78
|
+
const lightColorNamesSet = new Set(Object.keys(light_colors));
|
|
79
|
+
const darkColorNamesSet = new Set(Object.keys(dark_colors));
|
|
80
|
+
const mergedColorNamesSet = new Set([
|
|
81
|
+
...lightColorNamesSet,
|
|
82
|
+
...darkColorNamesSet,
|
|
83
|
+
]);
|
|
84
|
+
// Guard against atRule.nodes being undefined
|
|
85
|
+
const nodes = atRule.nodes ?? [];
|
|
86
|
+
const generatedRules = [];
|
|
87
|
+
// Helper to clone nodes and replace {name} placeholder
|
|
88
|
+
const cloneNodesWithName = (name) => nodes.map((node) => {
|
|
89
|
+
const cloned = node.clone();
|
|
90
|
+
// Only process declarations
|
|
91
|
+
if (cloned.type === "decl") {
|
|
92
|
+
const decl = cloned;
|
|
93
|
+
decl.value = decl.value.replace(/\{name\}/g, name);
|
|
94
|
+
}
|
|
95
|
+
return cloned;
|
|
96
|
+
});
|
|
97
|
+
// Generate rules for mergedColorNamesSet
|
|
98
|
+
for (const colorName of mergedColorNamesSet) {
|
|
99
|
+
const rule = new postcss_1.Rule({ selector: `.${colorName}` });
|
|
100
|
+
cloneNodesWithName(colorName).forEach((n) => rule.append(n));
|
|
101
|
+
generatedRules.push(rule);
|
|
102
|
+
}
|
|
103
|
+
// Replace the original @each-seyuna-color at-rule with all the generated rules
|
|
104
|
+
atRule.replaceWith(...generatedRules);
|
|
105
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AtRule } from "postcss";
|
|
2
|
+
/**
|
|
3
|
+
* Custom PostCSS plugin handler for responsive at-rules.
|
|
4
|
+
*
|
|
5
|
+
* Example:
|
|
6
|
+
*
|
|
7
|
+
* @xs {
|
|
8
|
+
* .box { color: red; }
|
|
9
|
+
* }
|
|
10
|
+
*
|
|
11
|
+
* Into:
|
|
12
|
+
*
|
|
13
|
+
* @xs (min-width: 234px) {
|
|
14
|
+
* .box { color: red; }
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
*/
|
|
18
|
+
export default function container(atRule: AtRule): void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = container;
|
|
4
|
+
const postcss_1 = require("postcss");
|
|
5
|
+
/**
|
|
6
|
+
* Custom PostCSS plugin handler for responsive at-rules.
|
|
7
|
+
*
|
|
8
|
+
* Example:
|
|
9
|
+
*
|
|
10
|
+
* @xs {
|
|
11
|
+
* .box { color: red; }
|
|
12
|
+
* }
|
|
13
|
+
*
|
|
14
|
+
* Into:
|
|
15
|
+
*
|
|
16
|
+
* @xs (min-width: 234px) {
|
|
17
|
+
* .box { color: red; }
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
function container(atRule) {
|
|
22
|
+
// Map of shortcuts → container widths
|
|
23
|
+
const breakpoints = {
|
|
24
|
+
xs: "20rem",
|
|
25
|
+
sm: "40rem",
|
|
26
|
+
md: "48rem",
|
|
27
|
+
lg: "64rem",
|
|
28
|
+
xl: "80rem",
|
|
29
|
+
"2xl": "96rem",
|
|
30
|
+
};
|
|
31
|
+
if (Object.keys(breakpoints).includes(atRule.name)) {
|
|
32
|
+
const minWidth = breakpoints[atRule.name];
|
|
33
|
+
const clonedNodes = [];
|
|
34
|
+
atRule.each((node) => {
|
|
35
|
+
clonedNodes.push(node.clone());
|
|
36
|
+
});
|
|
37
|
+
const containerAtRule = new postcss_1.AtRule({
|
|
38
|
+
name: "container",
|
|
39
|
+
params: `(min-width: ${minWidth})`,
|
|
40
|
+
});
|
|
41
|
+
clonedNodes.forEach((node) => containerAtRule.append(node));
|
|
42
|
+
atRule.replaceWith(containerAtRule);
|
|
43
|
+
}
|
|
44
|
+
}
|
package/dist/at-rules/dark.d.ts
CHANGED
|
@@ -1,2 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AtRule } from "postcss";
|
|
2
|
+
/**
|
|
3
|
+
* Custom PostCSS plugin handler for `@dark` at-rules.
|
|
4
|
+
*
|
|
5
|
+
* Transforms:
|
|
6
|
+
*
|
|
7
|
+
* @dark {
|
|
8
|
+
* color: white;
|
|
9
|
+
* }
|
|
10
|
+
*
|
|
11
|
+
* Into:
|
|
12
|
+
*
|
|
13
|
+
* @media (prefers-color-scheme: dark) {
|
|
14
|
+
* [data-mode="system"] & {
|
|
15
|
+
* color: white;
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* [data-mode="dark"] & {
|
|
20
|
+
* color: white;
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
2
23
|
export default function dark(atRule: AtRule): void;
|
package/dist/at-rules/dark.js
CHANGED
|
@@ -2,12 +2,64 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = dark;
|
|
4
4
|
const postcss_1 = require("postcss");
|
|
5
|
+
/**
|
|
6
|
+
* Custom PostCSS plugin handler for `@dark` at-rules.
|
|
7
|
+
*
|
|
8
|
+
* Transforms:
|
|
9
|
+
*
|
|
10
|
+
* @dark {
|
|
11
|
+
* color: white;
|
|
12
|
+
* }
|
|
13
|
+
*
|
|
14
|
+
* Into:
|
|
15
|
+
*
|
|
16
|
+
* @media (prefers-color-scheme: dark) {
|
|
17
|
+
* [data-mode="system"] & {
|
|
18
|
+
* color: white;
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* [data-mode="dark"] & {
|
|
23
|
+
* color: white;
|
|
24
|
+
* }
|
|
25
|
+
*/
|
|
5
26
|
function dark(atRule) {
|
|
6
|
-
|
|
7
|
-
|
|
27
|
+
const clonedNodes = [];
|
|
28
|
+
// Clone all child nodes inside the @dark block
|
|
29
|
+
// (so we can reuse them in both generated rules).
|
|
30
|
+
atRule.each((node) => {
|
|
31
|
+
clonedNodes.push(node.clone());
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* Rule 1: [data-mode="dark"] & { ... }
|
|
35
|
+
*
|
|
36
|
+
* This applies the styles when the user explicitly sets dark mode.
|
|
37
|
+
*/
|
|
38
|
+
const darkRule = new postcss_1.Rule({
|
|
8
39
|
selector: `[data-mode="dark"] &`,
|
|
9
|
-
nodes: atRule.nodes, // move children inside
|
|
10
40
|
});
|
|
11
|
-
|
|
12
|
-
|
|
41
|
+
clonedNodes.forEach((node) => darkRule.append(node.clone()));
|
|
42
|
+
/**
|
|
43
|
+
* Rule 2: @media (prefers-color-scheme: dark) { [data-mode="system"] & { ... } }
|
|
44
|
+
*
|
|
45
|
+
* This applies the styles only when:
|
|
46
|
+
* - The user’s OS prefers dark mode
|
|
47
|
+
* - AND the app is in "system" mode (i.e. follow system preference)
|
|
48
|
+
*/
|
|
49
|
+
const mediaAtRule = new postcss_1.AtRule({
|
|
50
|
+
name: "media",
|
|
51
|
+
params: "(prefers-color-scheme: dark)",
|
|
52
|
+
});
|
|
53
|
+
// Wrap cloned rules under `[data-mode="system"]`
|
|
54
|
+
const systemRule = new postcss_1.Rule({
|
|
55
|
+
selector: `[data-mode="system"] &`,
|
|
56
|
+
});
|
|
57
|
+
clonedNodes.forEach((node) => systemRule.append(node.clone()));
|
|
58
|
+
// Nest `[data-mode="system"]` inside the @media block
|
|
59
|
+
mediaAtRule.append(systemRule);
|
|
60
|
+
/**
|
|
61
|
+
* Replace the original @dark rule in the CSS tree
|
|
62
|
+
* with our two new generated rules.
|
|
63
|
+
*/
|
|
64
|
+
atRule.replaceWith(mediaAtRule, darkRule);
|
|
13
65
|
}
|
package/dist/at-rules/index.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import type { AtRule } from "postcss";
|
|
2
|
-
export
|
|
3
|
-
|
|
2
|
+
export interface AtRuleHandler {
|
|
3
|
+
name: string;
|
|
4
|
+
handler: (atRule: AtRule) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare const atRuleHandlers: AtRuleHandler[];
|
package/dist/at-rules/index.js
CHANGED
|
@@ -4,10 +4,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.atRuleHandlers = void 0;
|
|
7
|
+
// atRuleHandlers.ts
|
|
7
8
|
const dark_1 = __importDefault(require("./dark"));
|
|
8
9
|
const light_1 = __importDefault(require("./light"));
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const container_1 = __importDefault(require("./container"));
|
|
11
|
+
const color_1 = require("./color");
|
|
12
|
+
// Ordered array ensures execution order
|
|
13
|
+
exports.atRuleHandlers = [
|
|
14
|
+
{ name: "each-standard-color", handler: color_1.eachStandardColor }, // first
|
|
15
|
+
{ name: "each-fixed-color", handler: color_1.eachFixedColor },
|
|
16
|
+
{ name: "light", handler: light_1.default },
|
|
17
|
+
{ name: "dark", handler: dark_1.default },
|
|
18
|
+
{ name: "xs", handler: container_1.default },
|
|
19
|
+
{ name: "sm", handler: container_1.default },
|
|
20
|
+
{ name: "md", handler: container_1.default },
|
|
21
|
+
{ name: "lg", handler: container_1.default },
|
|
22
|
+
{ name: "xl", handler: container_1.default },
|
|
23
|
+
{ name: "2xl", handler: container_1.default },
|
|
12
24
|
// add more handlers here as needed
|
|
13
|
-
|
|
25
|
+
];
|
package/dist/at-rules/light.d.ts
CHANGED
|
@@ -1,2 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AtRule } from "postcss";
|
|
2
|
+
/**
|
|
3
|
+
* Custom PostCSS plugin handler for `@light` at-rules.
|
|
4
|
+
*
|
|
5
|
+
* Transforms:
|
|
6
|
+
*
|
|
7
|
+
* @light {
|
|
8
|
+
* color: white;
|
|
9
|
+
* }
|
|
10
|
+
*
|
|
11
|
+
* Into:
|
|
12
|
+
*
|
|
13
|
+
* @media (prefers-color-scheme: light) {
|
|
14
|
+
* [data-mode="system"] & {
|
|
15
|
+
* color: white;
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* [data-mode="light"] & {
|
|
20
|
+
* color: white;
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
2
23
|
export default function light(atRule: AtRule): void;
|
package/dist/at-rules/light.js
CHANGED
|
@@ -2,12 +2,64 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = light;
|
|
4
4
|
const postcss_1 = require("postcss");
|
|
5
|
+
/**
|
|
6
|
+
* Custom PostCSS plugin handler for `@light` at-rules.
|
|
7
|
+
*
|
|
8
|
+
* Transforms:
|
|
9
|
+
*
|
|
10
|
+
* @light {
|
|
11
|
+
* color: white;
|
|
12
|
+
* }
|
|
13
|
+
*
|
|
14
|
+
* Into:
|
|
15
|
+
*
|
|
16
|
+
* @media (prefers-color-scheme: light) {
|
|
17
|
+
* [data-mode="system"] & {
|
|
18
|
+
* color: white;
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* [data-mode="light"] & {
|
|
23
|
+
* color: white;
|
|
24
|
+
* }
|
|
25
|
+
*/
|
|
5
26
|
function light(atRule) {
|
|
6
|
-
|
|
7
|
-
|
|
27
|
+
const clonedNodes = [];
|
|
28
|
+
// Clone all child nodes inside the @light block
|
|
29
|
+
// (so we can reuse them in both generated rules).
|
|
30
|
+
atRule.each((node) => {
|
|
31
|
+
clonedNodes.push(node.clone());
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* Rule 1: [data-mode="light"] & { ... }
|
|
35
|
+
*
|
|
36
|
+
* This applies the styles when the user explicitly sets light mode.
|
|
37
|
+
*/
|
|
38
|
+
const lightRule = new postcss_1.Rule({
|
|
8
39
|
selector: `[data-mode="light"] &`,
|
|
9
|
-
nodes: atRule.nodes, // move children inside
|
|
10
40
|
});
|
|
11
|
-
|
|
12
|
-
|
|
41
|
+
clonedNodes.forEach((node) => lightRule.append(node.clone()));
|
|
42
|
+
/**
|
|
43
|
+
* Rule 2: @media (prefers-color-scheme: light) { [data-mode="system"] & { ... } }
|
|
44
|
+
*
|
|
45
|
+
* This applies the styles only when:
|
|
46
|
+
* - The user’s OS prefers light mode
|
|
47
|
+
* - AND the app is in "system" mode (i.e. follow system preference)
|
|
48
|
+
*/
|
|
49
|
+
const mediaAtRule = new postcss_1.AtRule({
|
|
50
|
+
name: "media",
|
|
51
|
+
params: "(prefers-color-scheme: light)",
|
|
52
|
+
});
|
|
53
|
+
// Wrap cloned rules under `[data-mode="system"]`
|
|
54
|
+
const systemRule = new postcss_1.Rule({
|
|
55
|
+
selector: `[data-mode="system"] &`,
|
|
56
|
+
});
|
|
57
|
+
clonedNodes.forEach((node) => systemRule.append(node.clone()));
|
|
58
|
+
// Nest `[data-mode="system"]` inside the @media block
|
|
59
|
+
mediaAtRule.append(systemRule);
|
|
60
|
+
/**
|
|
61
|
+
* Replace the original @light rule in the CSS tree
|
|
62
|
+
* with our two new generated rules.
|
|
63
|
+
*/
|
|
64
|
+
atRule.replaceWith(mediaAtRule, lightRule);
|
|
13
65
|
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export declare function sc(name: string, alpha?: string, lightness?: string, chroma?: string): string;
|
|
2
|
+
export declare function fc(name: string, alpha?: string, lightness?: string, chroma?: string): string;
|
package/dist/functions/color.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
|
|
3
|
+
exports.sc = sc;
|
|
4
|
+
exports.fc = fc;
|
|
5
|
+
function sc(name, alpha, lightness, chroma) {
|
|
5
6
|
let a = "1";
|
|
6
7
|
let l = "var(--lightness)";
|
|
7
8
|
let c = "var(--chroma)";
|
|
@@ -14,5 +15,20 @@ function color(name, alpha, lightness, chroma) {
|
|
|
14
15
|
if (chroma && chroma !== "null") {
|
|
15
16
|
c = chroma;
|
|
16
17
|
}
|
|
17
|
-
return `oklch(${l} ${c} var(--${name}) / ${a})`;
|
|
18
|
+
return `oklch(${l} ${c} var(--${name}-hue) / ${a})`;
|
|
19
|
+
}
|
|
20
|
+
function fc(name, alpha, lightness, chroma) {
|
|
21
|
+
let a = "1";
|
|
22
|
+
let l = `var(--${name}-lightness)`;
|
|
23
|
+
let c = `var(--${name}-chroma)`;
|
|
24
|
+
if (alpha && alpha !== "null") {
|
|
25
|
+
a = alpha;
|
|
26
|
+
}
|
|
27
|
+
if (lightness && lightness !== "null") {
|
|
28
|
+
l = lightness;
|
|
29
|
+
}
|
|
30
|
+
if (chroma && chroma !== "null") {
|
|
31
|
+
c = chroma;
|
|
32
|
+
}
|
|
33
|
+
return `oklch(${l} ${c} var(--${name}-hue) / ${a})`;
|
|
18
34
|
}
|
package/dist/functions/index.js
CHANGED
|
@@ -4,9 +4,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.functions = void 0;
|
|
7
|
-
const color_1 =
|
|
7
|
+
const color_1 = require("./color");
|
|
8
8
|
const spacing_1 = __importDefault(require("./spacing"));
|
|
9
9
|
exports.functions = {
|
|
10
|
-
|
|
10
|
+
sc: color_1.sc,
|
|
11
|
+
fc: color_1.fc,
|
|
11
12
|
spacing: spacing_1.default,
|
|
12
13
|
};
|
package/dist/plugin.js
CHANGED
|
@@ -20,8 +20,14 @@ const dynamicFunctionsPlugin = (opts = {}) => {
|
|
|
20
20
|
}
|
|
21
21
|
decl.value = value;
|
|
22
22
|
},
|
|
23
|
-
AtRule
|
|
24
|
-
|
|
23
|
+
// Override AtRule handler to ensure ordered execution
|
|
24
|
+
AtRule(atRule) {
|
|
25
|
+
// Iterate over handlers in order (array) instead of object spread
|
|
26
|
+
for (const { name, handler } of at_rules_1.atRuleHandlers) {
|
|
27
|
+
if (atRule.name === name) {
|
|
28
|
+
handler(atRule);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
25
31
|
},
|
|
26
32
|
};
|
|
27
33
|
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface SeyunaConfig {
|
|
2
|
+
ui: {
|
|
3
|
+
theme: {
|
|
4
|
+
hues: Record<string, number>;
|
|
5
|
+
light: {
|
|
6
|
+
colors: Record<string, Color>;
|
|
7
|
+
};
|
|
8
|
+
dark: {
|
|
9
|
+
colors: Record<string, Color>;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
type Color = {
|
|
15
|
+
lightness: number;
|
|
16
|
+
chroma: number;
|
|
17
|
+
hue: number;
|
|
18
|
+
};
|
|
19
|
+
export {};
|
package/dist/types.js
ADDED
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seyuna/postcss",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0-canary.10",
|
|
4
4
|
"description": "Seyuna UI's postcss plugin",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"build": "tsc"
|
|
9
|
-
"prepare": "npm run build"
|
|
8
|
+
"build": "tsc"
|
|
10
9
|
},
|
|
11
10
|
"keywords": [
|
|
12
11
|
"postcss",
|
|
@@ -14,12 +13,24 @@
|
|
|
14
13
|
"seyuna"
|
|
15
14
|
],
|
|
16
15
|
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/seyuna-corp/seyuna-postcss.git"
|
|
19
|
+
},
|
|
17
20
|
"peerDependencies": {
|
|
18
21
|
"postcss": "^8.5.6"
|
|
19
22
|
},
|
|
20
23
|
"devDependencies": {
|
|
24
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
25
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
26
|
+
"@semantic-release/exec": "^7.1.0",
|
|
27
|
+
"@semantic-release/git": "^10.0.1",
|
|
28
|
+
"@semantic-release/github": "^11.0.3",
|
|
29
|
+
"@semantic-release/npm": "^12.0.2",
|
|
30
|
+
"@semantic-release/release-notes-generator": "^14.0.3",
|
|
21
31
|
"@types/node": "^20.0.0",
|
|
22
|
-
"
|
|
23
|
-
"
|
|
32
|
+
"postcss": "^8.5.6",
|
|
33
|
+
"semantic-release": "^24.2.7",
|
|
34
|
+
"typescript": "^5.0.0"
|
|
24
35
|
}
|
|
25
36
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @type {import('semantic-release').GlobalConfig}
|
|
3
|
+
*/
|
|
4
|
+
export default {
|
|
5
|
+
branches: [{ name: "canary", prerelease: "canary" }, "main"],
|
|
6
|
+
plugins: [
|
|
7
|
+
"@semantic-release/commit-analyzer",
|
|
8
|
+
"@semantic-release/release-notes-generator",
|
|
9
|
+
["@semantic-release/changelog", { changelogFile: "CHANGELOG.md" }],
|
|
10
|
+
[
|
|
11
|
+
"@semantic-release/exec", // Runs build before publishing
|
|
12
|
+
{
|
|
13
|
+
prepareCmd: "npm run build",
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
[
|
|
17
|
+
"@semantic-release/npm", // Publishes to npm
|
|
18
|
+
{
|
|
19
|
+
npmPublish: true,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
[
|
|
23
|
+
"@semantic-release/git", // Commits updated files
|
|
24
|
+
{
|
|
25
|
+
assets: ["package.json", "package-lock.json", "CHANGELOG.md"],
|
|
26
|
+
message:
|
|
27
|
+
"chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}",
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
"@semantic-release/github", // Creates GitHub release
|
|
31
|
+
],
|
|
32
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { AtRule, Rule, ChildNode, Declaration } from "postcss";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { SeyunaConfig } from "../types";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Custom PostCSS plugin handler for `@each-standard-color` at-rules.
|
|
8
|
+
*
|
|
9
|
+
* Example usage:
|
|
10
|
+
*
|
|
11
|
+
* @each-standard-color {
|
|
12
|
+
* color: white;
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* Will generate:
|
|
16
|
+
*
|
|
17
|
+
* .alpha { color: white; }
|
|
18
|
+
* .beta { color: white; }
|
|
19
|
+
* .gamma { color: white; }
|
|
20
|
+
* ...
|
|
21
|
+
*/
|
|
22
|
+
export function eachStandardColor(atRule: AtRule) {
|
|
23
|
+
// Read seyuna.json from project root
|
|
24
|
+
const jsonPath = path.resolve(process.cwd(), "seyuna.json");
|
|
25
|
+
const fileContents = fs.readFileSync(jsonPath, "utf-8");
|
|
26
|
+
const data: SeyunaConfig = JSON.parse(fileContents);
|
|
27
|
+
const hues = data.ui.theme.hues;
|
|
28
|
+
const hueNamesSet = new Set(Object.keys(hues));
|
|
29
|
+
|
|
30
|
+
// Guard against atRule.nodes being undefined
|
|
31
|
+
const nodes = atRule.nodes ?? [];
|
|
32
|
+
|
|
33
|
+
const generatedRules: Rule[] = [];
|
|
34
|
+
|
|
35
|
+
// Helper to clone nodes and replace {name} placeholder
|
|
36
|
+
const cloneNodesWithName = (name: string) =>
|
|
37
|
+
nodes.map((node) => {
|
|
38
|
+
const cloned = node.clone();
|
|
39
|
+
|
|
40
|
+
// Only process declarations
|
|
41
|
+
if (cloned.type === "decl") {
|
|
42
|
+
const decl = cloned as Declaration;
|
|
43
|
+
decl.value = decl.value.replace(/\{name\}/g, name);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return cloned;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Generate rules for each hue
|
|
50
|
+
for (const hueName of hueNamesSet) {
|
|
51
|
+
const rule = new Rule({ selector: `.${hueName}` });
|
|
52
|
+
cloneNodesWithName(hueName).forEach((n) => rule.append(n));
|
|
53
|
+
generatedRules.push(rule);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Replace the original @each-seyuna-color at-rule with all the generated rules
|
|
57
|
+
atRule.replaceWith(...generatedRules);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Custom PostCSS plugin handler for `@each-fixed-color` at-rules.
|
|
62
|
+
*
|
|
63
|
+
* Example usage:
|
|
64
|
+
*
|
|
65
|
+
* @each-fixed-color {
|
|
66
|
+
* color: white;
|
|
67
|
+
* }
|
|
68
|
+
*
|
|
69
|
+
* Will generate:
|
|
70
|
+
*
|
|
71
|
+
* .primary { color: white; }
|
|
72
|
+
* .secondary { color: white; }
|
|
73
|
+
* ...
|
|
74
|
+
*/
|
|
75
|
+
export function eachFixedColor(atRule: AtRule) {
|
|
76
|
+
// Read seyuna.json from project root
|
|
77
|
+
const jsonPath = path.resolve(process.cwd(), "seyuna.json");
|
|
78
|
+
const fileContents = fs.readFileSync(jsonPath, "utf-8");
|
|
79
|
+
const data: SeyunaConfig = JSON.parse(fileContents);
|
|
80
|
+
const light_colors = data.ui.theme.light.colors;
|
|
81
|
+
const dark_colors = data.ui.theme.dark.colors;
|
|
82
|
+
const lightColorNamesSet = new Set(Object.keys(light_colors));
|
|
83
|
+
const darkColorNamesSet = new Set(Object.keys(dark_colors));
|
|
84
|
+
|
|
85
|
+
const mergedColorNamesSet = new Set([
|
|
86
|
+
...lightColorNamesSet,
|
|
87
|
+
...darkColorNamesSet,
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
// Guard against atRule.nodes being undefined
|
|
91
|
+
const nodes = atRule.nodes ?? [];
|
|
92
|
+
|
|
93
|
+
const generatedRules: Rule[] = [];
|
|
94
|
+
|
|
95
|
+
// Helper to clone nodes and replace {name} placeholder
|
|
96
|
+
const cloneNodesWithName = (name: string) =>
|
|
97
|
+
nodes.map((node) => {
|
|
98
|
+
const cloned = node.clone();
|
|
99
|
+
|
|
100
|
+
// Only process declarations
|
|
101
|
+
if (cloned.type === "decl") {
|
|
102
|
+
const decl = cloned as Declaration;
|
|
103
|
+
decl.value = decl.value.replace(/\{name\}/g, name);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return cloned;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Generate rules for mergedColorNamesSet
|
|
110
|
+
for (const colorName of mergedColorNamesSet) {
|
|
111
|
+
const rule = new Rule({ selector: `.${colorName}` });
|
|
112
|
+
cloneNodesWithName(colorName).forEach((n) => rule.append(n));
|
|
113
|
+
generatedRules.push(rule);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Replace the original @each-seyuna-color at-rule with all the generated rules
|
|
117
|
+
atRule.replaceWith(...generatedRules);
|
|
118
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { AtRule, ChildNode } from "postcss";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Custom PostCSS plugin handler for responsive at-rules.
|
|
5
|
+
*
|
|
6
|
+
* Example:
|
|
7
|
+
*
|
|
8
|
+
* @xs {
|
|
9
|
+
* .box { color: red; }
|
|
10
|
+
* }
|
|
11
|
+
*
|
|
12
|
+
* Into:
|
|
13
|
+
*
|
|
14
|
+
* @xs (min-width: 234px) {
|
|
15
|
+
* .box { color: red; }
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
*/
|
|
19
|
+
export default function container(atRule: AtRule) {
|
|
20
|
+
// Map of shortcuts → container widths
|
|
21
|
+
const breakpoints: Record<string, string> = {
|
|
22
|
+
xs: "20rem",
|
|
23
|
+
sm: "40rem",
|
|
24
|
+
md: "48rem",
|
|
25
|
+
lg: "64rem",
|
|
26
|
+
xl: "80rem",
|
|
27
|
+
"2xl": "96rem",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
if (Object.keys(breakpoints).includes(atRule.name)) {
|
|
31
|
+
const minWidth = breakpoints[atRule.name];
|
|
32
|
+
|
|
33
|
+
const clonedNodes: ChildNode[] = [];
|
|
34
|
+
atRule.each((node: ChildNode) => {
|
|
35
|
+
clonedNodes.push(node.clone());
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const containerAtRule = new AtRule({
|
|
39
|
+
name: "container",
|
|
40
|
+
params: `(min-width: ${minWidth})`,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
clonedNodes.forEach((node) => containerAtRule.append(node));
|
|
44
|
+
|
|
45
|
+
atRule.replaceWith(containerAtRule);
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/at-rules/dark.ts
CHANGED
|
@@ -1,12 +1,69 @@
|
|
|
1
|
-
import { Rule,
|
|
1
|
+
import { Rule, AtRule, ChildNode } from "postcss";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Custom PostCSS plugin handler for `@dark` at-rules.
|
|
5
|
+
*
|
|
6
|
+
* Transforms:
|
|
7
|
+
*
|
|
8
|
+
* @dark {
|
|
9
|
+
* color: white;
|
|
10
|
+
* }
|
|
11
|
+
*
|
|
12
|
+
* Into:
|
|
13
|
+
*
|
|
14
|
+
* @media (prefers-color-scheme: dark) {
|
|
15
|
+
* [data-mode="system"] & {
|
|
16
|
+
* color: white;
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* [data-mode="dark"] & {
|
|
21
|
+
* color: white;
|
|
22
|
+
* }
|
|
23
|
+
*/
|
|
3
24
|
export default function dark(atRule: AtRule) {
|
|
4
|
-
|
|
5
|
-
|
|
25
|
+
const clonedNodes: ChildNode[] = [];
|
|
26
|
+
|
|
27
|
+
// Clone all child nodes inside the @dark block
|
|
28
|
+
// (so we can reuse them in both generated rules).
|
|
29
|
+
atRule.each((node: ChildNode) => {
|
|
30
|
+
clonedNodes.push(node.clone());
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Rule 1: [data-mode="dark"] & { ... }
|
|
35
|
+
*
|
|
36
|
+
* This applies the styles when the user explicitly sets dark mode.
|
|
37
|
+
*/
|
|
38
|
+
const darkRule = new Rule({
|
|
6
39
|
selector: `[data-mode="dark"] &`,
|
|
7
|
-
nodes: atRule.nodes, // move children inside
|
|
8
40
|
});
|
|
41
|
+
clonedNodes.forEach((node) => darkRule.append(node.clone()));
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Rule 2: @media (prefers-color-scheme: dark) { [data-mode="system"] & { ... } }
|
|
45
|
+
*
|
|
46
|
+
* This applies the styles only when:
|
|
47
|
+
* - The user’s OS prefers dark mode
|
|
48
|
+
* - AND the app is in "system" mode (i.e. follow system preference)
|
|
49
|
+
*/
|
|
50
|
+
const mediaAtRule = new AtRule({
|
|
51
|
+
name: "media",
|
|
52
|
+
params: "(prefers-color-scheme: dark)",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Wrap cloned rules under `[data-mode="system"]`
|
|
56
|
+
const systemRule = new Rule({
|
|
57
|
+
selector: `[data-mode="system"] &`,
|
|
58
|
+
});
|
|
59
|
+
clonedNodes.forEach((node) => systemRule.append(node.clone()));
|
|
60
|
+
|
|
61
|
+
// Nest `[data-mode="system"]` inside the @media block
|
|
62
|
+
mediaAtRule.append(systemRule);
|
|
9
63
|
|
|
10
|
-
|
|
11
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Replace the original @dark rule in the CSS tree
|
|
66
|
+
* with our two new generated rules.
|
|
67
|
+
*/
|
|
68
|
+
atRule.replaceWith(mediaAtRule, darkRule);
|
|
12
69
|
}
|
package/src/at-rules/index.ts
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
|
+
// atRuleHandlers.ts
|
|
1
2
|
import dark from "./dark";
|
|
2
3
|
import light from "./light";
|
|
4
|
+
import container from "./container";
|
|
5
|
+
import { eachStandardColor, eachFixedColor } from "./color";
|
|
3
6
|
import type { AtRule } from "postcss";
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
// Each handler has a name (matches the at-rule) and the function
|
|
9
|
+
export interface AtRuleHandler {
|
|
10
|
+
name: string;
|
|
11
|
+
handler: (atRule: AtRule) => void;
|
|
12
|
+
}
|
|
6
13
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
14
|
+
// Ordered array ensures execution order
|
|
15
|
+
export const atRuleHandlers: AtRuleHandler[] = [
|
|
16
|
+
{ name: "each-standard-color", handler: eachStandardColor }, // first
|
|
17
|
+
{ name: "each-fixed-color", handler: eachFixedColor },
|
|
18
|
+
{ name: "light", handler: light },
|
|
19
|
+
{ name: "dark", handler: dark },
|
|
20
|
+
{ name: "xs", handler: container },
|
|
21
|
+
{ name: "sm", handler: container },
|
|
22
|
+
{ name: "md", handler: container },
|
|
23
|
+
{ name: "lg", handler: container },
|
|
24
|
+
{ name: "xl", handler: container },
|
|
25
|
+
{ name: "2xl", handler: container },
|
|
10
26
|
// add more handlers here as needed
|
|
11
|
-
|
|
27
|
+
];
|
package/src/at-rules/light.ts
CHANGED
|
@@ -1,12 +1,69 @@
|
|
|
1
|
-
import { Rule,
|
|
1
|
+
import { Rule, AtRule, ChildNode } from "postcss";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Custom PostCSS plugin handler for `@light` at-rules.
|
|
5
|
+
*
|
|
6
|
+
* Transforms:
|
|
7
|
+
*
|
|
8
|
+
* @light {
|
|
9
|
+
* color: white;
|
|
10
|
+
* }
|
|
11
|
+
*
|
|
12
|
+
* Into:
|
|
13
|
+
*
|
|
14
|
+
* @media (prefers-color-scheme: light) {
|
|
15
|
+
* [data-mode="system"] & {
|
|
16
|
+
* color: white;
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* [data-mode="light"] & {
|
|
21
|
+
* color: white;
|
|
22
|
+
* }
|
|
23
|
+
*/
|
|
3
24
|
export default function light(atRule: AtRule) {
|
|
4
|
-
|
|
5
|
-
|
|
25
|
+
const clonedNodes: ChildNode[] = [];
|
|
26
|
+
|
|
27
|
+
// Clone all child nodes inside the @light block
|
|
28
|
+
// (so we can reuse them in both generated rules).
|
|
29
|
+
atRule.each((node: ChildNode) => {
|
|
30
|
+
clonedNodes.push(node.clone());
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Rule 1: [data-mode="light"] & { ... }
|
|
35
|
+
*
|
|
36
|
+
* This applies the styles when the user explicitly sets light mode.
|
|
37
|
+
*/
|
|
38
|
+
const lightRule = new Rule({
|
|
6
39
|
selector: `[data-mode="light"] &`,
|
|
7
|
-
nodes: atRule.nodes, // move children inside
|
|
8
40
|
});
|
|
41
|
+
clonedNodes.forEach((node) => lightRule.append(node.clone()));
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Rule 2: @media (prefers-color-scheme: light) { [data-mode="system"] & { ... } }
|
|
45
|
+
*
|
|
46
|
+
* This applies the styles only when:
|
|
47
|
+
* - The user’s OS prefers light mode
|
|
48
|
+
* - AND the app is in "system" mode (i.e. follow system preference)
|
|
49
|
+
*/
|
|
50
|
+
const mediaAtRule = new AtRule({
|
|
51
|
+
name: "media",
|
|
52
|
+
params: "(prefers-color-scheme: light)",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Wrap cloned rules under `[data-mode="system"]`
|
|
56
|
+
const systemRule = new Rule({
|
|
57
|
+
selector: `[data-mode="system"] &`,
|
|
58
|
+
});
|
|
59
|
+
clonedNodes.forEach((node) => systemRule.append(node.clone()));
|
|
60
|
+
|
|
61
|
+
// Nest `[data-mode="system"]` inside the @media block
|
|
62
|
+
mediaAtRule.append(systemRule);
|
|
9
63
|
|
|
10
|
-
|
|
11
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Replace the original @light rule in the CSS tree
|
|
66
|
+
* with our two new generated rules.
|
|
67
|
+
*/
|
|
68
|
+
atRule.replaceWith(mediaAtRule, lightRule);
|
|
12
69
|
}
|
package/src/functions/color.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export
|
|
1
|
+
export function sc(
|
|
2
2
|
name: string,
|
|
3
3
|
alpha?: string,
|
|
4
4
|
lightness?: string,
|
|
@@ -20,5 +20,30 @@ export default function color(
|
|
|
20
20
|
c = chroma;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
return `oklch(${l} ${c} var(--${name}) / ${a})`;
|
|
23
|
+
return `oklch(${l} ${c} var(--${name}-hue) / ${a})`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function fc(
|
|
27
|
+
name: string,
|
|
28
|
+
alpha?: string,
|
|
29
|
+
lightness?: string,
|
|
30
|
+
chroma?: string
|
|
31
|
+
) {
|
|
32
|
+
let a: string = "1";
|
|
33
|
+
let l: string = `var(--${name}-lightness)`;
|
|
34
|
+
let c: string = `var(--${name}-chroma)`;
|
|
35
|
+
|
|
36
|
+
if (alpha && alpha !== "null") {
|
|
37
|
+
a = alpha;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (lightness && lightness !== "null") {
|
|
41
|
+
l = lightness;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (chroma && chroma !== "null") {
|
|
45
|
+
c = chroma;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return `oklch(${l} ${c} var(--${name}-hue) / ${a})`;
|
|
24
49
|
}
|
package/src/functions/index.ts
CHANGED
package/src/plugin.ts
CHANGED
|
@@ -31,8 +31,14 @@ export const dynamicFunctionsPlugin: PluginCreator<PluginOptions> = (
|
|
|
31
31
|
decl.value = value;
|
|
32
32
|
},
|
|
33
33
|
|
|
34
|
-
AtRule
|
|
35
|
-
|
|
34
|
+
// Override AtRule handler to ensure ordered execution
|
|
35
|
+
AtRule(atRule) {
|
|
36
|
+
// Iterate over handlers in order (array) instead of object spread
|
|
37
|
+
for (const { name, handler } of atRuleHandlers) {
|
|
38
|
+
if (atRule.name === name) {
|
|
39
|
+
handler(atRule);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
36
42
|
},
|
|
37
43
|
};
|
|
38
44
|
};
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface SeyunaConfig {
|
|
2
|
+
ui: {
|
|
3
|
+
theme: {
|
|
4
|
+
hues: Record<string, number>;
|
|
5
|
+
light: {
|
|
6
|
+
colors: Record<string, Color>;
|
|
7
|
+
};
|
|
8
|
+
dark: {
|
|
9
|
+
colors: Record<string, Color>;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type Color = {
|
|
16
|
+
lightness: number;
|
|
17
|
+
chroma: number;
|
|
18
|
+
hue: number;
|
|
19
|
+
};
|