@inkdropapp/theme-dev-helpers 0.3.6 → 0.3.8
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/CI.yaml +1 -1
- package/.oxfmtrc.json +8 -0
- package/.oxlintrc.json +29 -0
- package/README.md +11 -0
- package/bin/dev-server +14 -4
- package/index.html +10 -12
- package/package.json +27 -23
- package/pnpm-workspace.yaml +3 -0
- package/src/dev-server/color-tokens.tsx +15 -13
- package/src/dev-server/components.tsx +86 -54
- package/src/dev-server/get-css-variables.ts +3 -4
- package/src/dev-server/index.tsx +0 -1
- package/src/dev-server/nav.tsx +3 -3
- package/src/dev-server/variables.tsx +12 -12
- package/src/dev-server.css +1 -2
- package/src/dev-server.tsx +26 -26
- package/src/generate-palette.ts +33 -48
- package/src/palette.test.ts +73 -0
- package/src/palette.ts +78 -0
- package/tsconfig.json +7 -10
- package/vite.config.ts +12 -6
- package/bun.lockb +0 -0
package/.oxfmtrc.json
ADDED
package/.oxlintrc.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
|
3
|
+
"plugins": ["typescript", "unicorn", "oxc", "react"],
|
|
4
|
+
"settings": {
|
|
5
|
+
"react": {
|
|
6
|
+
"version": "19.2.7"
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"categories": {
|
|
10
|
+
"correctness": "error"
|
|
11
|
+
},
|
|
12
|
+
"rules": {
|
|
13
|
+
"typescript/no-explicit-any": "off",
|
|
14
|
+
"typescript/ban-ts-comment": "off",
|
|
15
|
+
"typescript/no-this-alias": "off",
|
|
16
|
+
"typescript/no-unused-vars": [
|
|
17
|
+
"warn",
|
|
18
|
+
{
|
|
19
|
+
"argsIgnorePattern": "^_",
|
|
20
|
+
"varsIgnorePattern": "^_",
|
|
21
|
+
"caughtErrorsIgnorePattern": "^_"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"no-useless-escape": "off",
|
|
25
|
+
"prefer-const": "error",
|
|
26
|
+
"no-unused-vars": "off"
|
|
27
|
+
},
|
|
28
|
+
"ignorePatterns": ["node_modules/**"]
|
|
29
|
+
}
|
package/README.md
CHANGED
|
@@ -45,3 +45,14 @@ It provides a simple UI to preview your theme with hot-reloading.
|
|
|
45
45
|
dev-server
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
## Development
|
|
49
|
+
|
|
50
|
+
Dependencies are managed with [pnpm](https://pnpm.io/), and [Bun](https://bun.sh/) is the runtime used to run the CLIs and tests.
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
pnpm install # install dependencies
|
|
54
|
+
pnpm format # format with oxfmt
|
|
55
|
+
pnpm lint # lint with oxlint
|
|
56
|
+
pnpm typecheck # type-check with tsc
|
|
57
|
+
pnpm test # run unit tests (bun test)
|
|
58
|
+
```
|
package/bin/dev-server
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { exec } from 'child_process';
|
|
6
|
+
import { createServer } from 'vite'
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
const scriptPath = fileURLToPath(import.meta.url);
|
|
9
|
+
const scriptDir = dirname(scriptPath);
|
|
10
|
+
|
|
11
|
+
const server = await createServer({
|
|
12
|
+
root: join(scriptDir, "..")
|
|
13
|
+
})
|
|
14
|
+
await server.listen()
|
|
15
|
+
|
|
16
|
+
server.printUrls()
|
package/index.html
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Inkdrop theme dev tool</title>
|
|
8
|
+
</head>
|
|
3
9
|
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<title>Inkdrop theme dev tool</title>
|
|
9
|
-
</head>
|
|
10
|
-
|
|
11
|
-
<body>
|
|
12
|
-
<div id="root"></div>
|
|
13
|
-
<script type="module" src="/src/dev-server.tsx"></script>
|
|
14
|
-
</body>
|
|
15
|
-
|
|
10
|
+
<body>
|
|
11
|
+
<div id="root"></div>
|
|
12
|
+
<script type="module" src="/src/dev-server.tsx"></script>
|
|
13
|
+
</body>
|
|
16
14
|
</html>
|
package/package.json
CHANGED
|
@@ -1,41 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inkdropapp/theme-dev-helpers",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"type": "module",
|
|
3
|
+
"version": "0.3.8",
|
|
5
4
|
"description": "A helper module for creating themes for Inkdrop",
|
|
6
5
|
"keywords": [
|
|
7
6
|
"inkdrop",
|
|
8
7
|
"markdown"
|
|
9
8
|
],
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"author": "Takuya Matsuyama<t@inkdrop.app>",
|
|
13
11
|
"repository": {
|
|
14
12
|
"type": "git",
|
|
15
13
|
"url": "git+https://github.com/inkdropapp/theme-dev-helpers.git"
|
|
16
14
|
},
|
|
17
|
-
"author": "Takuya Matsuyama<t@inkdrop.app>",
|
|
18
|
-
"license": "MIT",
|
|
19
15
|
"bin": {
|
|
20
|
-
"
|
|
21
|
-
"
|
|
16
|
+
"dev-server": "./bin/dev-server",
|
|
17
|
+
"generate-palette": "./bin/generate-palette"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"scripts": {
|
|
21
|
+
"format": "oxfmt",
|
|
22
|
+
"lint": "oxlint",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"test": "bun test"
|
|
22
25
|
},
|
|
23
26
|
"dependencies": {
|
|
24
|
-
"@inkdropapp/base-ui-theme": "^0.3
|
|
25
|
-
"@inkdropapp/css": "^0.
|
|
26
|
-
"@vitejs/plugin-react": "^
|
|
27
|
-
"commander": "^
|
|
28
|
-
"puppeteer": "^
|
|
29
|
-
"react": "^
|
|
30
|
-
"react-dom": "^
|
|
31
|
-
"react-router-dom": "^
|
|
32
|
-
"typescript": "^
|
|
33
|
-
"vite": "^
|
|
27
|
+
"@inkdropapp/base-ui-theme": "^0.7.3",
|
|
28
|
+
"@inkdropapp/css": "^0.6.3",
|
|
29
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
30
|
+
"commander": "^15.0.0",
|
|
31
|
+
"puppeteer": "^25.1.0",
|
|
32
|
+
"react": "^19.2.7",
|
|
33
|
+
"react-dom": "^19.2.7",
|
|
34
|
+
"react-router-dom": "^7.17.0",
|
|
35
|
+
"typescript": "^6.0.3",
|
|
36
|
+
"vite": "^8.0.16"
|
|
34
37
|
},
|
|
35
38
|
"devDependencies": {
|
|
36
|
-
"@types/
|
|
37
|
-
"@types/react
|
|
38
|
-
"
|
|
39
|
-
"
|
|
39
|
+
"@types/bun": "1.3.14",
|
|
40
|
+
"@types/react": "^19.2.17",
|
|
41
|
+
"@types/react-dom": "^19.2.3",
|
|
42
|
+
"oxfmt": "0.55.0",
|
|
43
|
+
"oxlint": "1.70.0"
|
|
40
44
|
}
|
|
41
45
|
}
|
|
@@ -6,20 +6,23 @@ export const ColorTokensPage = () => {
|
|
|
6
6
|
return (
|
|
7
7
|
<>
|
|
8
8
|
<h2>Color Tokens</h2>
|
|
9
|
-
<div className=
|
|
9
|
+
<div className="variable-list">
|
|
10
10
|
{Object.keys(cssVariables)
|
|
11
|
-
.filter(name => name.startsWith('--color') || name.startsWith('--hsl'))
|
|
12
|
-
.map(variableName => {
|
|
11
|
+
.filter((name) => name.startsWith('--color') || name.startsWith('--hsl'))
|
|
12
|
+
.map((variableName) => {
|
|
13
13
|
return (
|
|
14
|
-
<div key={variableName} className=
|
|
15
|
-
<div
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
<div key={variableName} className="variable-item">
|
|
15
|
+
<div
|
|
16
|
+
className="color-box"
|
|
17
|
+
style={{
|
|
18
|
+
background: variableName.startsWith('--hsl')
|
|
19
|
+
? `hsl(var(${variableName}))`
|
|
20
|
+
: `var(${variableName})`
|
|
21
|
+
}}
|
|
22
|
+
></div>
|
|
23
|
+
<div className="data">
|
|
24
|
+
<div className="variable-name">{variableName}</div>
|
|
25
|
+
<div className="variable-value">{cssVariables[variableName]}</div>
|
|
23
26
|
</div>
|
|
24
27
|
</div>
|
|
25
28
|
)
|
|
@@ -28,4 +31,3 @@ export const ColorTokensPage = () => {
|
|
|
28
31
|
</>
|
|
29
32
|
)
|
|
30
33
|
}
|
|
31
|
-
|
|
@@ -5,7 +5,6 @@ export const ComponentsPage = () => {
|
|
|
5
5
|
<>
|
|
6
6
|
<h2>Components</h2>
|
|
7
7
|
<div className="component-list">
|
|
8
|
-
|
|
9
8
|
<div className="component-item">
|
|
10
9
|
<h3>Link</h3>
|
|
11
10
|
<div>
|
|
@@ -21,12 +20,21 @@ export const ComponentsPage = () => {
|
|
|
21
20
|
<button className="ui basic button">Basic Button</button>
|
|
22
21
|
<button className="ui primary button">Primary Button</button>
|
|
23
22
|
<button className="ui negative button">Negative Button</button>
|
|
24
|
-
<button
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
<button
|
|
24
|
+
className="ui tiny circular icon button plugin-uninstall-button "
|
|
25
|
+
title="Uninstall"
|
|
26
|
+
>
|
|
27
|
+
<svg
|
|
28
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
29
|
+
viewBox="0 0 24 24"
|
|
30
|
+
width="14"
|
|
31
|
+
height="14"
|
|
32
|
+
name="bin-1"
|
|
33
|
+
className="svg-icon streamline bin-1 "
|
|
34
|
+
>
|
|
28
35
|
<defs>
|
|
29
|
-
<style>
|
|
36
|
+
<style>
|
|
37
|
+
{`
|
|
30
38
|
.bin-1_svg__a {
|
|
31
39
|
fill: none;
|
|
32
40
|
stroke: currentColor;
|
|
@@ -39,7 +47,8 @@ export const ComponentsPage = () => {
|
|
|
39
47
|
</defs>
|
|
40
48
|
<path
|
|
41
49
|
d="M1.5 4.5h21M14.25 1.5h-4.5A1.5 1.5 0 0 0 8.25 3v1.5h7.5V3a1.5 1.5 0 0 0-1.5-1.5M9.75 17.25v-7.5M14.25 17.25v-7.5M18.865 21.124A1.5 1.5 0 0 1 17.37 22.5H6.631a1.5 1.5 0 0 1-1.495-1.376L3.75 4.5h16.5Z"
|
|
42
|
-
className="bin-1_svg__a"
|
|
50
|
+
className="bin-1_svg__a"
|
|
51
|
+
></path>
|
|
43
52
|
</svg>
|
|
44
53
|
</button>
|
|
45
54
|
</div>
|
|
@@ -47,27 +56,35 @@ export const ComponentsPage = () => {
|
|
|
47
56
|
</div>
|
|
48
57
|
|
|
49
58
|
<div className="component-item">
|
|
50
|
-
<h3 className=
|
|
59
|
+
<h3 className="ui header">Message</h3>
|
|
51
60
|
<div>
|
|
52
61
|
<div className="ui message">
|
|
53
|
-
<div className="header">
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
<div className="header">Changes in Service</div>
|
|
63
|
+
<p>
|
|
64
|
+
We just updated our privacy policy here to better service our customers. We
|
|
65
|
+
recommend reviewing the changes.
|
|
57
66
|
</p>
|
|
58
67
|
</div>
|
|
59
68
|
|
|
60
69
|
<div className="ui info message">
|
|
61
|
-
<div className="header">
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
<div className="header">Info</div>
|
|
71
|
+
|
|
72
|
+
<p>
|
|
73
|
+
Plugins add new functionality or provide new look to Inkdrop. You can activate or
|
|
74
|
+
deactivate them at any time. Read
|
|
75
|
+
<a href="https://docs.inkdrop.app/reference/extend-with-plugins">
|
|
76
|
+
the manual
|
|
77
|
+
<svg
|
|
78
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
79
|
+
viewBox="0 0 24 24"
|
|
80
|
+
width="1em"
|
|
81
|
+
height="1em"
|
|
82
|
+
name="expand-6"
|
|
83
|
+
className="svg-icon streamline expand-6 inline"
|
|
84
|
+
>
|
|
85
|
+
<defs>
|
|
86
|
+
<style>
|
|
87
|
+
{`
|
|
71
88
|
.expand-6_svg__a {
|
|
72
89
|
fill: none;
|
|
73
90
|
stroke: currentColor;
|
|
@@ -76,37 +93,37 @@ export const ComponentsPage = () => {
|
|
|
76
93
|
stroke-width: 1.5px
|
|
77
94
|
}
|
|
78
95
|
`}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
</style>
|
|
97
|
+
</defs>
|
|
98
|
+
<path
|
|
99
|
+
d="M23.251 7.498V.748h-6.75M23.251.748l-15 15M11.251 5.248h-9a1.5 1.5 0 0 0-1.5 1.5v15a1.5 1.5 0 0 0 1.5 1.5h15a1.5 1.5 0 0 0 1.5-1.5v-9"
|
|
100
|
+
className="expand-6_svg__a"
|
|
101
|
+
></path>
|
|
102
|
+
</svg>
|
|
103
|
+
</a>{' '}
|
|
104
|
+
to learn more.
|
|
105
|
+
</p>
|
|
85
106
|
</div>
|
|
86
107
|
|
|
87
108
|
<div className="ui positive message">
|
|
88
|
-
<div className="header">
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
109
|
+
<div className="header">You are eligible for a reward</div>
|
|
110
|
+
<p>
|
|
111
|
+
Go to your <b>special offers</b> page to see now.
|
|
112
|
+
</p>
|
|
92
113
|
</div>
|
|
93
114
|
|
|
94
115
|
<div className="ui negative message">
|
|
95
|
-
<div className="header">
|
|
96
|
-
|
|
97
|
-
</div>
|
|
98
|
-
<p>That offer has expired
|
|
99
|
-
</p>
|
|
116
|
+
<div className="header">We're sorry we can't apply that discount</div>
|
|
117
|
+
<p>That offer has expired</p>
|
|
100
118
|
</div>
|
|
101
119
|
|
|
102
120
|
<div className="ui warning message">
|
|
103
|
-
<div className="header">
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
121
|
+
<div className="header">Changes in Service</div>
|
|
122
|
+
<p>
|
|
123
|
+
We just updated our privacy policy here to better service our customers. We
|
|
124
|
+
recommend reviewing the changes.
|
|
107
125
|
</p>
|
|
108
126
|
</div>
|
|
109
|
-
|
|
110
127
|
</div>
|
|
111
128
|
</div>
|
|
112
129
|
|
|
@@ -160,8 +177,8 @@ export const ComponentsPage = () => {
|
|
|
160
177
|
<div className="component-item">
|
|
161
178
|
<h3>Input</h3>
|
|
162
179
|
<div>
|
|
163
|
-
<div className=
|
|
164
|
-
<div className=
|
|
180
|
+
<div className="ui form">
|
|
181
|
+
<div className="field">
|
|
165
182
|
<input type="text" placeholder="Search keybindings" defaultValue="" />
|
|
166
183
|
</div>
|
|
167
184
|
</div>
|
|
@@ -190,13 +207,30 @@ export const ComponentsPage = () => {
|
|
|
190
207
|
<h3>Dropdown</h3>
|
|
191
208
|
<div>
|
|
192
209
|
<div className="ui selection dropdown active focus-outline">
|
|
193
|
-
<svg
|
|
194
|
-
|
|
210
|
+
<svg
|
|
211
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
212
|
+
viewBox="0 0 24 24"
|
|
213
|
+
width="1em"
|
|
214
|
+
height="1em"
|
|
215
|
+
name="dropdown-chevron"
|
|
216
|
+
className="svg-icon streamline dropdown-chevron dropdown icon"
|
|
217
|
+
>
|
|
218
|
+
<path
|
|
219
|
+
fill="currentColor"
|
|
220
|
+
d="M11.96 19.318a2.09 2.09 0 0 1-1.663-.782L.617 7.682C.031 6.9.128 5.726.911 5.14c.782-.587 1.857-.587 2.444.195l8.41 9.583c.097.098.195.098.39 0l8.41-9.583c.684-.782 1.76-.88 2.542-.195s.88 1.76.195 2.542l-.098.098-9.582 10.854c-.391.39-1.076.684-1.662.684"
|
|
221
|
+
></path>
|
|
195
222
|
</svg>
|
|
196
|
-
<div className="text">
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
223
|
+
<div className="text">Inbox</div>
|
|
224
|
+
<div
|
|
225
|
+
className="menu visible"
|
|
226
|
+
style={{
|
|
227
|
+
display: 'block',
|
|
228
|
+
overflow: 'auto',
|
|
229
|
+
opacity: 1,
|
|
230
|
+
height: 'auto',
|
|
231
|
+
willChange: 'transform'
|
|
232
|
+
}}
|
|
233
|
+
>
|
|
200
234
|
<div className="item " data-value="book:Bk5Ivk0T">
|
|
201
235
|
<span style={{ paddingLeft: 0 }}>App Development</span>
|
|
202
236
|
</div>
|
|
@@ -207,9 +241,7 @@ export const ComponentsPage = () => {
|
|
|
207
241
|
</div>
|
|
208
242
|
</div>
|
|
209
243
|
</div>
|
|
210
|
-
|
|
211
|
-
|
|
212
244
|
</div>
|
|
213
245
|
</>
|
|
214
|
-
)
|
|
215
|
-
}
|
|
246
|
+
)
|
|
247
|
+
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
export function getCSSVariables() {
|
|
2
|
-
const computedStyles = document.body.computedStyleMap()
|
|
3
|
-
const variables: Record<string, string> = {}
|
|
2
|
+
const computedStyles = document.body.computedStyleMap()
|
|
3
|
+
const variables: Record<string, string> = {}
|
|
4
4
|
for (const [prop, val] of computedStyles) {
|
|
5
5
|
if (prop.startsWith('--')) {
|
|
6
|
-
variables[prop] = val.toString()
|
|
6
|
+
variables[prop] = val.toString()
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
11
10
|
return variables
|
|
12
11
|
}
|
package/src/dev-server/index.tsx
CHANGED
package/src/dev-server/nav.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Link } from
|
|
1
|
+
import { Link } from 'react-router-dom'
|
|
2
2
|
|
|
3
3
|
export const Nav = () => {
|
|
4
4
|
return (
|
|
@@ -7,5 +7,5 @@ export const Nav = () => {
|
|
|
7
7
|
<Link to="/tokens">Color Tokens</Link>
|
|
8
8
|
<Link to="/components">Components</Link>
|
|
9
9
|
</div>
|
|
10
|
-
)
|
|
11
|
-
}
|
|
10
|
+
)
|
|
11
|
+
}
|
|
@@ -6,18 +6,19 @@ export const VariablesPage = () => {
|
|
|
6
6
|
return (
|
|
7
7
|
<>
|
|
8
8
|
<h2>Variables</h2>
|
|
9
|
-
<div className=
|
|
10
|
-
{themeVariableNames.map(variableName => {
|
|
9
|
+
<div className="variable-list">
|
|
10
|
+
{themeVariableNames.map((variableName) => {
|
|
11
11
|
return (
|
|
12
|
-
<div key={variableName} className=
|
|
13
|
-
<div
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
</div>
|
|
12
|
+
<div key={variableName} className="variable-item">
|
|
13
|
+
<div
|
|
14
|
+
className="color-box"
|
|
15
|
+
style={{
|
|
16
|
+
background: `var(${variableName})`
|
|
17
|
+
}}
|
|
18
|
+
></div>
|
|
19
|
+
<div className="data">
|
|
20
|
+
<div className="variable-name">{variableName}</div>
|
|
21
|
+
<div className="variable-value">{cssVariables[variableName]}</div>
|
|
21
22
|
</div>
|
|
22
23
|
</div>
|
|
23
24
|
)
|
|
@@ -26,4 +27,3 @@ export const VariablesPage = () => {
|
|
|
26
27
|
</>
|
|
27
28
|
)
|
|
28
29
|
}
|
|
29
|
-
|
package/src/dev-server.css
CHANGED
package/src/dev-server.tsx
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import { StrictMode } from
|
|
2
|
-
import { createRoot } from
|
|
3
|
-
import { createBrowserRouter, RouterProvider } from
|
|
4
|
-
import { Index } from
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
1
|
+
import { StrictMode } from 'react'
|
|
2
|
+
import { createRoot } from 'react-dom/client'
|
|
3
|
+
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
|
|
4
|
+
import { Index } from './dev-server/index'
|
|
5
|
+
import './dev-server.css'
|
|
6
|
+
import '@inkdropapp/css/reset.css'
|
|
7
|
+
import '@inkdropapp/css/tokens.css'
|
|
8
|
+
import '@inkdropapp/base-ui-theme/styles/theme.css'
|
|
9
9
|
|
|
10
|
-
import { ColorTokensPage } from
|
|
11
|
-
import { VariablesPage } from
|
|
12
|
-
import { ComponentsPage } from
|
|
10
|
+
import { ColorTokensPage } from './dev-server/color-tokens'
|
|
11
|
+
import { VariablesPage } from './dev-server/variables'
|
|
12
|
+
import { ComponentsPage } from './dev-server/components'
|
|
13
13
|
const themePackageName = import.meta.env.THEME_NAME || ''
|
|
14
14
|
const themeAppearance = import.meta.env.THEME_APPEARANCE
|
|
15
15
|
const baseProjectPath = import.meta.env.BASE_PROJECT_PATH || ''
|
|
16
16
|
const styleSheets: string[] = import.meta.env.STYLE_SHEETS || []
|
|
17
17
|
|
|
18
|
-
const cssFiles = styleSheets.map(ss => `${baseProjectPath}/styles/${ss}`)
|
|
18
|
+
const cssFiles = styleSheets.map((ss) => `${baseProjectPath}/styles/${ss}`)
|
|
19
19
|
|
|
20
20
|
cssFiles.forEach((file) => {
|
|
21
|
-
import(file)
|
|
22
|
-
})
|
|
21
|
+
import(/* @vite-ignore */ file)
|
|
22
|
+
})
|
|
23
23
|
|
|
24
|
-
document.body.classList.add(`theme-${themePackageName}`)
|
|
24
|
+
document.body.classList.add(`theme-${themePackageName}`)
|
|
25
25
|
if (themeAppearance) {
|
|
26
|
-
document.body.classList.add(`${themeAppearance}-mode`)
|
|
26
|
+
document.body.classList.add(`${themeAppearance}-mode`)
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const router = createBrowserRouter([
|
|
30
30
|
{
|
|
31
|
-
path:
|
|
31
|
+
path: '/',
|
|
32
32
|
element: <Index />,
|
|
33
33
|
children: [
|
|
34
|
-
{ path:
|
|
35
|
-
{ path:
|
|
36
|
-
{ path:
|
|
37
|
-
]
|
|
38
|
-
}
|
|
39
|
-
])
|
|
34
|
+
{ path: '', element: <VariablesPage /> },
|
|
35
|
+
{ path: 'tokens', element: <ColorTokensPage /> },
|
|
36
|
+
{ path: 'components', element: <ComponentsPage /> }
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
])
|
|
40
40
|
|
|
41
|
-
createRoot(document.getElementById(
|
|
41
|
+
createRoot(document.getElementById('root')!).render(
|
|
42
42
|
<StrictMode>
|
|
43
43
|
<RouterProvider router={router} />
|
|
44
|
-
</StrictMode
|
|
45
|
-
)
|
|
44
|
+
</StrictMode>
|
|
45
|
+
)
|
package/src/generate-palette.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import puppeteer from 'puppeteer'
|
|
2
|
-
import { writeFile } from 'fs/promises'
|
|
3
|
-
import path from 'path'
|
|
1
|
+
import puppeteer from 'puppeteer'
|
|
2
|
+
import { writeFile } from 'fs/promises'
|
|
3
|
+
import path from 'path'
|
|
4
4
|
import { pathToFileURL } from 'url'
|
|
5
|
-
import { Command } from 'commander'
|
|
6
|
-
import packageJson from '../package.json'
|
|
5
|
+
import { Command } from 'commander'
|
|
6
|
+
import packageJson from '../package.json'
|
|
7
|
+
import { buildPreviewHTML, mapThemeVariables } from './palette'
|
|
7
8
|
|
|
8
|
-
const program = new Command()
|
|
9
|
+
const program = new Command()
|
|
9
10
|
|
|
10
11
|
// Define CLI options and help using commander
|
|
11
12
|
program
|
|
@@ -14,74 +15,58 @@ program
|
|
|
14
15
|
.version(packageJson.version)
|
|
15
16
|
.option('-a, --appearance <light/dark>', 'Force the UI appearance ("light" or "dark")')
|
|
16
17
|
.option('-o, --output <path>', 'Output file path (default: ./palette.json)', './palette.json')
|
|
17
|
-
.parse(process.argv)
|
|
18
|
+
.parse(process.argv)
|
|
18
19
|
|
|
19
20
|
// Parse options
|
|
20
|
-
const options = program.opts()
|
|
21
|
-
const outputPath = options.output as string
|
|
22
|
-
const appearance = options.appearance as 'light' | 'dark' | undefined
|
|
21
|
+
const options = program.opts()
|
|
22
|
+
const outputPath = options.output as string
|
|
23
|
+
const appearance = options.appearance as 'light' | 'dark' | undefined
|
|
23
24
|
|
|
24
25
|
// Function to extract theme CSS variables
|
|
25
26
|
async function extractPalette(outputPath: string) {
|
|
26
|
-
const themePackageJson =
|
|
27
|
-
const themeVariableNames: string[] = (
|
|
27
|
+
const themePackageJson = await import(path.join(process.cwd(), 'package.json'))
|
|
28
|
+
const themeVariableNames: string[] = (
|
|
29
|
+
await import(`@inkdropapp/base-ui-theme/lib/variable-names.json`)
|
|
30
|
+
).default
|
|
28
31
|
|
|
29
|
-
const browser = await puppeteer.launch()
|
|
30
|
-
const page = await browser.newPage()
|
|
32
|
+
const browser = await puppeteer.launch()
|
|
33
|
+
const page = await browser.newPage()
|
|
31
34
|
|
|
32
35
|
page
|
|
33
|
-
.on('console', message => {
|
|
36
|
+
.on('console', (message) => {
|
|
34
37
|
if (message.type() === 'error') {
|
|
35
38
|
console.error(`${message.type().substr(0, 3).toUpperCase()} ${message.text()}`)
|
|
36
39
|
}
|
|
37
40
|
})
|
|
38
|
-
.on('pageerror', (
|
|
41
|
+
.on('pageerror', (error) => console.error(error instanceof Error ? error.message : error))
|
|
39
42
|
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
const content = `<!DOCTYPE html>
|
|
43
|
-
<html>
|
|
44
|
-
<head>
|
|
45
|
-
<base href="${baseUrl}" />
|
|
46
|
-
<link rel="stylesheet" href="node_modules/@inkdropapp/css/reset.css" />
|
|
47
|
-
<link rel="stylesheet" href="node_modules/@inkdropapp/css/tokens.css" />
|
|
48
|
-
<link rel="stylesheet" href="node_modules/@inkdropapp/css/tags.css" />
|
|
49
|
-
<link rel="stylesheet" href="node_modules/@inkdropapp/base-ui-theme/styles/theme.css" />
|
|
50
|
-
${themeCSSFiles}
|
|
51
|
-
</head>
|
|
52
|
-
<body class="${themePackageJson.name} ${typeof appearance !== 'undefined' ? appearance + '-mode' : ''}">
|
|
53
|
-
<h1>Hello</h1>
|
|
54
|
-
</body>
|
|
55
|
-
</html>
|
|
56
|
-
`;
|
|
43
|
+
const baseUrl = pathToFileURL(process.cwd()).toString() + '/'
|
|
44
|
+
const content = buildPreviewHTML(themePackageJson, baseUrl, appearance)
|
|
57
45
|
|
|
58
|
-
await page.goto(baseUrl)
|
|
59
|
-
await page.setContent(content)
|
|
46
|
+
await page.goto(baseUrl)
|
|
47
|
+
await page.setContent(content)
|
|
60
48
|
|
|
61
49
|
// Extract CSS variables
|
|
62
50
|
const computedCSSVariables = await page.$eval('body', (body) => {
|
|
63
|
-
const computedStyles = body.computedStyleMap()
|
|
64
|
-
const variables: Record<string, string> = {}
|
|
51
|
+
const computedStyles = body.computedStyleMap()
|
|
52
|
+
const variables: Record<string, string> = {}
|
|
65
53
|
for (const [prop, val] of computedStyles) {
|
|
66
54
|
if (prop.startsWith('--')) {
|
|
67
|
-
variables[prop] = val.toString()
|
|
55
|
+
variables[prop] = val.toString()
|
|
68
56
|
}
|
|
69
57
|
}
|
|
70
|
-
return variables
|
|
71
|
-
})
|
|
58
|
+
return variables
|
|
59
|
+
})
|
|
72
60
|
|
|
73
|
-
const themeCSSVariables = themeVariableNames
|
|
74
|
-
variables[name] = computedCSSVariables[name];
|
|
75
|
-
return variables;
|
|
76
|
-
}, {} as Record<string, string>);
|
|
61
|
+
const themeCSSVariables = mapThemeVariables(themeVariableNames, computedCSSVariables)
|
|
77
62
|
|
|
78
63
|
// Write to the output file
|
|
79
|
-
const outputFilePath = path.resolve(outputPath)
|
|
80
|
-
await writeFile(outputFilePath, JSON.stringify(themeCSSVariables, null, 2))
|
|
64
|
+
const outputFilePath = path.resolve(outputPath)
|
|
65
|
+
await writeFile(outputFilePath, JSON.stringify(themeCSSVariables, null, 2))
|
|
81
66
|
|
|
82
|
-
await browser.close()
|
|
67
|
+
await browser.close()
|
|
83
68
|
}
|
|
84
69
|
|
|
85
70
|
extractPalette(outputPath)
|
|
86
71
|
.then(() => console.log('Palette extraction complete!'))
|
|
87
|
-
.catch(err => console.error('Error extracting palette:', err))
|
|
72
|
+
.catch((err) => console.error('Error extracting palette:', err))
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { buildPreviewHTML, mapThemeVariables } from './palette'
|
|
3
|
+
|
|
4
|
+
describe('buildPreviewHTML', () => {
|
|
5
|
+
const baseUrl = 'file:///themes/acme/'
|
|
6
|
+
|
|
7
|
+
test('emits the base href so relative links resolve', () => {
|
|
8
|
+
const html = buildPreviewHTML({ name: 'acme' }, baseUrl)
|
|
9
|
+
expect(html).toContain(`<base href="${baseUrl}" />`)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('always includes the Inkdrop base stylesheets', () => {
|
|
13
|
+
const html = buildPreviewHTML({ name: 'acme' }, baseUrl)
|
|
14
|
+
expect(html).toContain('node_modules/@inkdropapp/css/reset.css')
|
|
15
|
+
expect(html).toContain('node_modules/@inkdropapp/css/tokens.css')
|
|
16
|
+
expect(html).toContain('node_modules/@inkdropapp/css/tags.css')
|
|
17
|
+
expect(html).toContain('node_modules/@inkdropapp/base-ui-theme/styles/theme.css')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('links each declared theme stylesheet under styles/', () => {
|
|
21
|
+
const html = buildPreviewHTML(
|
|
22
|
+
{ name: 'acme', styleSheets: ['base.css', 'syntax.css'] },
|
|
23
|
+
baseUrl
|
|
24
|
+
)
|
|
25
|
+
expect(html).toContain('<link rel="stylesheet" href="styles/base.css" />')
|
|
26
|
+
expect(html).toContain('<link rel="stylesheet" href="styles/syntax.css" />')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('omits theme stylesheet links when none are declared', () => {
|
|
30
|
+
const html = buildPreviewHTML({ name: 'acme' }, baseUrl)
|
|
31
|
+
expect(html).not.toContain('href="styles/')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('sets the body class to the theme name', () => {
|
|
35
|
+
const html = buildPreviewHTML({ name: 'acme-dark' }, baseUrl)
|
|
36
|
+
expect(html).toContain('<body class="acme-dark">')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('appends an appearance-mode class when an appearance is forced', () => {
|
|
40
|
+
const html = buildPreviewHTML({ name: 'acme' }, baseUrl, 'dark')
|
|
41
|
+
expect(html).toContain('<body class="acme dark-mode">')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('adds no appearance class when appearance is omitted', () => {
|
|
45
|
+
const html = buildPreviewHTML({ name: 'acme' }, baseUrl)
|
|
46
|
+
expect(html).not.toContain('-mode')
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
describe('mapThemeVariables', () => {
|
|
51
|
+
test('maps each declared name to its computed value, ignoring extras', () => {
|
|
52
|
+
const result = mapThemeVariables(['--accent', '--bg'], {
|
|
53
|
+
'--accent': '#f00',
|
|
54
|
+
'--bg': '#fff',
|
|
55
|
+
'--extra': '#000'
|
|
56
|
+
})
|
|
57
|
+
expect(result).toEqual({ '--accent': '#f00', '--bg': '#fff' })
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('preserves the declaration order of the variable names', () => {
|
|
61
|
+
const result = mapThemeVariables(['--b', '--a'], { '--a': '1', '--b': '2' })
|
|
62
|
+
expect(Object.keys(result)).toEqual(['--b', '--a'])
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('leaves variables absent from the computed set undefined', () => {
|
|
66
|
+
const result = mapThemeVariables(['--missing'], { '--accent': '#f00' })
|
|
67
|
+
expect(result['--missing']).toBeUndefined()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('returns an empty object when no names are declared', () => {
|
|
71
|
+
expect(mapThemeVariables([], { '--accent': '#f00' })).toEqual({})
|
|
72
|
+
})
|
|
73
|
+
})
|
package/src/palette.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for palette extraction, kept free of puppeteer / fs / process
|
|
3
|
+
* so they can be unit-tested directly. The orchestration that drives a headless
|
|
4
|
+
* browser lives in `generate-palette.ts`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Theme metadata read from a theme package's `package.json`. */
|
|
8
|
+
export interface ThemePackage {
|
|
9
|
+
name?: string
|
|
10
|
+
styleSheets?: string[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type ThemeAppearance = 'light' | 'dark'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Builds the standalone HTML document that is loaded into a headless browser to
|
|
17
|
+
* compute a theme's CSS custom properties.
|
|
18
|
+
*
|
|
19
|
+
* It pulls in Inkdrop's base CSS plus the theme's own stylesheets so that the
|
|
20
|
+
* computed style of `<body>` resolves every themed variable. A `<base href>` is
|
|
21
|
+
* emitted so the relative `node_modules/` and `styles/` links resolve against
|
|
22
|
+
* the theme project root.
|
|
23
|
+
*
|
|
24
|
+
* @param theme - The theme package's metadata (`name`, `styleSheets`).
|
|
25
|
+
* @param baseUrl - File URL used as the document `<base href>`.
|
|
26
|
+
* @param appearance - Optional forced appearance; adds a `<appearance>-mode`
|
|
27
|
+
* body class when provided.
|
|
28
|
+
* @returns The full HTML document as a string.
|
|
29
|
+
*/
|
|
30
|
+
export function buildPreviewHTML(
|
|
31
|
+
theme: ThemePackage,
|
|
32
|
+
baseUrl: string,
|
|
33
|
+
appearance?: ThemeAppearance
|
|
34
|
+
): string {
|
|
35
|
+
const themeCSSLinks = (theme.styleSheets ?? [])
|
|
36
|
+
.map((filePath) => `<link rel="stylesheet" href="styles/${filePath}" />`)
|
|
37
|
+
.join('\n')
|
|
38
|
+
|
|
39
|
+
const bodyClass = [theme.name, appearance && `${appearance}-mode`].filter(Boolean).join(' ')
|
|
40
|
+
|
|
41
|
+
return `<!DOCTYPE html>
|
|
42
|
+
<html>
|
|
43
|
+
<head>
|
|
44
|
+
<base href="${baseUrl}" />
|
|
45
|
+
<link rel="stylesheet" href="node_modules/@inkdropapp/css/reset.css" />
|
|
46
|
+
<link rel="stylesheet" href="node_modules/@inkdropapp/css/tokens.css" />
|
|
47
|
+
<link rel="stylesheet" href="node_modules/@inkdropapp/css/tags.css" />
|
|
48
|
+
<link rel="stylesheet" href="node_modules/@inkdropapp/base-ui-theme/styles/theme.css" />
|
|
49
|
+
${themeCSSLinks}
|
|
50
|
+
</head>
|
|
51
|
+
<body class="${bodyClass}">
|
|
52
|
+
<h1>Hello</h1>
|
|
53
|
+
</body>
|
|
54
|
+
</html>
|
|
55
|
+
`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Projects the raw computed CSS variables onto the theme's declared variable
|
|
60
|
+
* names, preserving their declaration order. Names absent from `computed` map
|
|
61
|
+
* to `undefined` (and are therefore dropped by `JSON.stringify`).
|
|
62
|
+
*
|
|
63
|
+
* @param variableNames - The theme's declared CSS variable names.
|
|
64
|
+
* @param computed - All `--*` custom properties read from the rendered body.
|
|
65
|
+
* @returns A record of `variableName -> computed value`.
|
|
66
|
+
*/
|
|
67
|
+
export function mapThemeVariables(
|
|
68
|
+
variableNames: string[],
|
|
69
|
+
computed: Record<string, string>
|
|
70
|
+
): Record<string, string> {
|
|
71
|
+
return variableNames.reduce(
|
|
72
|
+
(acc, name) => {
|
|
73
|
+
acc[name] = computed[name]
|
|
74
|
+
return acc
|
|
75
|
+
},
|
|
76
|
+
{} as Record<string, string>
|
|
77
|
+
)
|
|
78
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -2,27 +2,24 @@
|
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"target": "ESNext",
|
|
4
4
|
"useDefineForClassFields": true,
|
|
5
|
-
"lib": [
|
|
6
|
-
"ES2020",
|
|
7
|
-
"DOM",
|
|
8
|
-
"DOM.Iterable"
|
|
9
|
-
],
|
|
5
|
+
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
|
10
6
|
"module": "ESNext",
|
|
11
7
|
"skipLibCheck": true,
|
|
12
8
|
/* Bundler mode */
|
|
13
|
-
"moduleResolution": "
|
|
9
|
+
"moduleResolution": "bundler",
|
|
14
10
|
"allowImportingTsExtensions": true,
|
|
15
11
|
"isolatedModules": true,
|
|
16
12
|
"moduleDetection": "force",
|
|
17
13
|
"noEmit": true,
|
|
18
14
|
"jsx": "react-jsx",
|
|
15
|
+
"types": ["bun"],
|
|
19
16
|
/* Linting */
|
|
20
17
|
"strict": true,
|
|
21
18
|
"noUnusedLocals": true,
|
|
22
19
|
"noUnusedParameters": true,
|
|
23
|
-
"noFallthroughCasesInSwitch": true
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"allowSyntheticDefaultImports": true,
|
|
22
|
+
"resolveJsonModule": true
|
|
24
23
|
},
|
|
25
|
-
"include": [
|
|
26
|
-
"src"
|
|
27
|
-
]
|
|
24
|
+
"include": ["src"]
|
|
28
25
|
}
|
package/vite.config.ts
CHANGED
|
@@ -5,11 +5,8 @@ import { readFile } from 'fs/promises'
|
|
|
5
5
|
const baseProjectPath = process.env.BASE_PROJECT_PATH || process.cwd()
|
|
6
6
|
console.log('Base project path:', baseProjectPath)
|
|
7
7
|
const packageJson = JSON.parse(
|
|
8
|
-
await readFile(
|
|
9
|
-
|
|
10
|
-
{ encoding: 'utf-8' }
|
|
11
|
-
)
|
|
12
|
-
);
|
|
8
|
+
await readFile(`${baseProjectPath}/package.json`, { encoding: 'utf-8' })
|
|
9
|
+
)
|
|
13
10
|
const { styleSheets } = packageJson
|
|
14
11
|
|
|
15
12
|
// https://vitejs.dev/config/
|
|
@@ -30,11 +27,20 @@ export default defineConfig({
|
|
|
30
27
|
'import.meta.env.STYLE_SHEETS': JSON.stringify(styleSheets)
|
|
31
28
|
},
|
|
32
29
|
server: {
|
|
30
|
+
host: '127.0.0.1',
|
|
31
|
+
port: Number(process.env.PORT) || 5180,
|
|
32
|
+
strictPort: false,
|
|
33
33
|
fs: {
|
|
34
34
|
strict: false
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
optimizeDeps: {
|
|
38
|
-
include: [
|
|
38
|
+
include: [
|
|
39
|
+
'react',
|
|
40
|
+
'react-dom',
|
|
41
|
+
'react-dom/client',
|
|
42
|
+
'react/jsx-dev-runtime',
|
|
43
|
+
'react/jsx-runtime'
|
|
44
|
+
]
|
|
39
45
|
}
|
|
40
46
|
})
|
package/bun.lockb
DELETED
|
Binary file
|