nextia 5.0.2 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -11
- package/biome.json +43 -0
- package/package.json +5 -6
- package/src/bin.js +64 -40
- package/src/lib.js +52 -27
- package/src/template/README.md +2 -4
- package/src/template/biome.json +43 -0
- package/src/template/package.json +40 -37
- package/src/template/src/components/I18n/index.jsx +1 -1
- package/src/template/src/components/Icon/index.jsx +4 -3
- package/src/template/src/components/Link/index.jsx +4 -2
- package/src/template/src/components/Translate/index.jsx +9 -8
- package/src/template/src/components/Translate/style.css +2 -1
- package/src/template/src/pages/demo/functions.js +1 -2
- package/src/template/src/pages/demo/index.jsx +2 -6
- package/src/template/src/pages/demo/style.css +1 -1
- package/src/template/src/pages/home/functions.js +2 -2
- package/src/template/src/pages/home/index.jsx +2 -6
- package/src/template/src/pages/home/style.css +2 -1
- package/src/template/src/pages/http/not-found/index.jsx +7 -5
- package/src/template/src/pages/http/not-found/style.css +2 -1
- package/src/template/src/pages/index.jsx +11 -10
- package/src/template/src/theme/animations.css +2 -2
- package/src/template/src/theme/fonts/index.css +2 -2
- package/src/template/src/theme/icons/index.css +1 -57
- package/src/template/src/theme/index.css +18 -18
- package/src/template/src/theme/util.css +1 -1
- package/src/template/src/utils/hooks.js +7 -3
- package/src/template/src/utils/index.js +4 -7
- package/src/template/vite.config.js +13 -17
package/README.md
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
# nextia
|
|
2
|
+
Create fast web applications
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Go to test
|
|
6
|
-
|
|
4
|
+
### To start
|
|
7
5
|
```sh
|
|
8
6
|
npm install
|
|
9
7
|
pnpm install
|
|
@@ -11,8 +9,7 @@ pnpm install
|
|
|
11
9
|
cd test
|
|
12
10
|
```
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
### To test
|
|
16
13
|
```sh
|
|
17
14
|
npm install
|
|
18
15
|
pnpm install
|
|
@@ -20,11 +17,10 @@ pnpm install
|
|
|
20
17
|
node --run test:my-app
|
|
21
18
|
```
|
|
22
19
|
|
|
23
|
-
create project
|
|
24
|
-
|
|
20
|
+
### To create project
|
|
25
21
|
```sh
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
pnpm i -g nextia
|
|
23
|
+
nextia my-app
|
|
28
24
|
```
|
|
29
25
|
|
|
30
|
-
[
|
|
26
|
+
[npmjs.com/package/nextia](https://www.npmjs.com/package/nextia)
|
package/biome.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
|
3
|
+
"assist": {
|
|
4
|
+
"actions": {
|
|
5
|
+
"source": {
|
|
6
|
+
"organizeImports": "off"
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"linter": {
|
|
11
|
+
"enabled": true,
|
|
12
|
+
"rules": {
|
|
13
|
+
"recommended": true,
|
|
14
|
+
"correctness": {
|
|
15
|
+
"noUnusedVariables": "error"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"formatter": {
|
|
20
|
+
"enabled": true,
|
|
21
|
+
"formatWithErrors": false,
|
|
22
|
+
"indentStyle": "space",
|
|
23
|
+
"indentWidth": 2
|
|
24
|
+
},
|
|
25
|
+
"javascript": {
|
|
26
|
+
"formatter": {
|
|
27
|
+
"semicolons": "asNeeded",
|
|
28
|
+
"quoteStyle": "single",
|
|
29
|
+
"jsxQuoteStyle": "double",
|
|
30
|
+
"trailingCommas": "none"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"css": {
|
|
34
|
+
"formatter": {
|
|
35
|
+
"enabled": true
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"json": {
|
|
39
|
+
"formatter": {
|
|
40
|
+
"enabled": true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nextia",
|
|
3
3
|
"description": "Create fast web applications",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "6.0.0",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">22"
|
|
7
7
|
},
|
|
@@ -24,19 +24,18 @@
|
|
|
24
24
|
"nextia": "src/bin.js"
|
|
25
25
|
},
|
|
26
26
|
"main": "src/lib.js",
|
|
27
|
-
"eslintConfig": {
|
|
28
|
-
"extends": "./node_modules/standard/eslintrc.json"
|
|
29
|
-
},
|
|
30
27
|
"peerDependencies": {
|
|
31
28
|
"react": "^19.2.3",
|
|
32
29
|
"react-dom": "^19.2.3"
|
|
33
30
|
},
|
|
34
31
|
"devDependencies": {
|
|
35
|
-
"
|
|
32
|
+
"@biomejs/biome": "^2.3.14"
|
|
36
33
|
},
|
|
37
34
|
"scripts": {
|
|
38
35
|
"clean": "rm -fr node_modules package-lock.json pnpm-lock.yaml .coverage out my-app",
|
|
39
|
-
"
|
|
36
|
+
"format": "biome format",
|
|
37
|
+
"lint": "biome lint",
|
|
38
|
+
"check": "biome check --reporter=summary",
|
|
40
39
|
"test": "node src/bin.js",
|
|
41
40
|
"test:my-app": "node src/bin.js my-app"
|
|
42
41
|
}
|
package/src/bin.js
CHANGED
|
@@ -9,18 +9,26 @@
|
|
|
9
9
|
* https://github.com/sinuhedev/nextia
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import pkg from
|
|
12
|
+
import pkg from '../package.json' with { type: 'json' }
|
|
13
13
|
import { fileURLToPath } from 'node:url'
|
|
14
14
|
import { dirname } from 'node:path'
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
import {
|
|
16
|
+
mkdir,
|
|
17
|
+
writeFile,
|
|
18
|
+
readFile,
|
|
19
|
+
cp,
|
|
20
|
+
rename,
|
|
21
|
+
access
|
|
22
|
+
} from 'node:fs/promises'
|
|
23
|
+
|
|
24
|
+
async function createPage(name) {
|
|
25
|
+
const toPascalCase = (str) =>
|
|
26
|
+
str
|
|
27
|
+
.toLowerCase()
|
|
28
|
+
.replace(/[^a-zA-Z0-9 ]/g, ' ') // replace special characters
|
|
29
|
+
.split(/\s+/) // split by spaces
|
|
30
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
31
|
+
.join('')
|
|
24
32
|
|
|
25
33
|
const dirName = `./src/pages/${name}`
|
|
26
34
|
|
|
@@ -30,8 +38,9 @@ async function createPage (name) {
|
|
|
30
38
|
const pageName = toPascalCase(name) + 'Page'
|
|
31
39
|
|
|
32
40
|
// index.jsx
|
|
33
|
-
writeFile(
|
|
34
|
-
|
|
41
|
+
writeFile(
|
|
42
|
+
`${dirName}/index.jsx`,
|
|
43
|
+
`import React, { useEffect } from 'react'
|
|
35
44
|
import { useFx, css } from 'nextia'
|
|
36
45
|
import functions from './functions'
|
|
37
46
|
import './style.css'
|
|
@@ -45,26 +54,31 @@ export default function ${pageName} () {
|
|
|
45
54
|
</section>
|
|
46
55
|
)
|
|
47
56
|
}
|
|
48
|
-
`
|
|
57
|
+
`
|
|
58
|
+
)
|
|
49
59
|
|
|
50
60
|
// style.sss
|
|
51
|
-
writeFile(
|
|
52
|
-
|
|
53
|
-
}
|
|
61
|
+
writeFile(
|
|
62
|
+
`${dirName}/style.css`,
|
|
63
|
+
`.${pageName} {
|
|
64
|
+
}`
|
|
65
|
+
)
|
|
54
66
|
|
|
55
67
|
// function.js
|
|
56
|
-
writeFile(
|
|
57
|
-
|
|
68
|
+
writeFile(
|
|
69
|
+
`${dirName}/functions.js`,
|
|
70
|
+
`const initialState = {
|
|
58
71
|
}
|
|
59
72
|
|
|
60
73
|
export default { initialState }
|
|
61
|
-
`
|
|
74
|
+
`
|
|
75
|
+
)
|
|
62
76
|
} catch (err) {
|
|
63
77
|
console.error(err)
|
|
64
78
|
}
|
|
65
79
|
}
|
|
66
80
|
|
|
67
|
-
async function createComponent
|
|
81
|
+
async function createComponent(name) {
|
|
68
82
|
const dirName = `./src/components/${name}`
|
|
69
83
|
|
|
70
84
|
try {
|
|
@@ -72,8 +86,9 @@ async function createComponent (name) {
|
|
|
72
86
|
const componentName = name.replaceAll('/', '') + '-component'
|
|
73
87
|
|
|
74
88
|
// index.jsx
|
|
75
|
-
writeFile(
|
|
76
|
-
|
|
89
|
+
writeFile(
|
|
90
|
+
`${dirName}/index.jsx`,
|
|
91
|
+
`import React, { useEffect } from 'react'
|
|
77
92
|
import { css } from 'nextia'
|
|
78
93
|
import './style.css'
|
|
79
94
|
|
|
@@ -84,11 +99,13 @@ export default function ${name} ({ className, style }) {
|
|
|
84
99
|
</article>
|
|
85
100
|
)
|
|
86
101
|
}
|
|
87
|
-
`
|
|
102
|
+
`
|
|
103
|
+
)
|
|
88
104
|
|
|
89
105
|
// style.css
|
|
90
|
-
writeFile(
|
|
91
|
-
|
|
106
|
+
writeFile(
|
|
107
|
+
`${dirName}/style.css`,
|
|
108
|
+
`.${componentName} {
|
|
92
109
|
}`
|
|
93
110
|
)
|
|
94
111
|
} catch (err) {
|
|
@@ -96,7 +113,7 @@ export default function ${name} ({ className, style }) {
|
|
|
96
113
|
}
|
|
97
114
|
}
|
|
98
115
|
|
|
99
|
-
async function createComponentFx
|
|
116
|
+
async function createComponentFx(name) {
|
|
100
117
|
const dirName = `./src/components/${name}`
|
|
101
118
|
|
|
102
119
|
try {
|
|
@@ -104,8 +121,9 @@ async function createComponentFx (name) {
|
|
|
104
121
|
const containerName = name.replaceAll('/', '') + '-component'
|
|
105
122
|
|
|
106
123
|
// index.jsx
|
|
107
|
-
writeFile(
|
|
108
|
-
|
|
124
|
+
writeFile(
|
|
125
|
+
`${dirName}/index.jsx`,
|
|
126
|
+
`import React, { useEffect } from 'react'
|
|
109
127
|
import { useFx, css } from 'nextia'
|
|
110
128
|
import functions from './functions'
|
|
111
129
|
import './style.css'
|
|
@@ -119,26 +137,31 @@ export default function ${name} ({ className, style }) {
|
|
|
119
137
|
</article>
|
|
120
138
|
)
|
|
121
139
|
}
|
|
122
|
-
`
|
|
140
|
+
`
|
|
141
|
+
)
|
|
123
142
|
|
|
124
143
|
// style.css
|
|
125
|
-
writeFile(
|
|
126
|
-
|
|
127
|
-
}
|
|
144
|
+
writeFile(
|
|
145
|
+
`${dirName}/style.css`,
|
|
146
|
+
`.${containerName} {
|
|
147
|
+
}`
|
|
148
|
+
)
|
|
128
149
|
|
|
129
150
|
// function.js
|
|
130
|
-
writeFile(
|
|
131
|
-
|
|
151
|
+
writeFile(
|
|
152
|
+
`${dirName}/functions.js`,
|
|
153
|
+
`const initialState = {
|
|
132
154
|
}
|
|
133
155
|
|
|
134
156
|
export default { initialState }
|
|
135
|
-
`
|
|
157
|
+
`
|
|
158
|
+
)
|
|
136
159
|
} catch (err) {
|
|
137
160
|
console.error(err)
|
|
138
161
|
}
|
|
139
162
|
}
|
|
140
163
|
|
|
141
|
-
async function createProject
|
|
164
|
+
async function createProject(name) {
|
|
142
165
|
let projectPath
|
|
143
166
|
|
|
144
167
|
try {
|
|
@@ -146,14 +169,14 @@ async function createProject (name) {
|
|
|
146
169
|
await access(projectPath)
|
|
147
170
|
console.error(`The "${name}" already exists.`)
|
|
148
171
|
return
|
|
149
|
-
} catch (error) {
|
|
150
|
-
}
|
|
172
|
+
} catch (error) {}
|
|
151
173
|
|
|
152
174
|
const template = dirname(fileURLToPath(import.meta.url)) + '/template/'
|
|
153
175
|
|
|
154
176
|
// Create new project
|
|
155
177
|
try {
|
|
156
|
-
const mv = fileName =>
|
|
178
|
+
const mv = (fileName) =>
|
|
179
|
+
rename(projectPath + `_${fileName}`, projectPath + `.${fileName}`)
|
|
157
180
|
await cp(template, projectPath, { recursive: true })
|
|
158
181
|
const replaceToken = async (filename, token, value) => {
|
|
159
182
|
const content = await readFile(projectPath + filename, 'utf8')
|
|
@@ -200,7 +223,8 @@ switch (ARG1) {
|
|
|
200
223
|
|
|
201
224
|
default:
|
|
202
225
|
if (ARG1) createProject(ARG1)
|
|
203
|
-
else
|
|
226
|
+
else
|
|
227
|
+
console.info(`
|
|
204
228
|
Version ${pkg.version}
|
|
205
229
|
|
|
206
230
|
npx nextia@latest <ProjectName>
|
package/src/lib.js
CHANGED
|
@@ -14,13 +14,16 @@ const Context = createContext()
|
|
|
14
14
|
/**
|
|
15
15
|
* css
|
|
16
16
|
*/
|
|
17
|
-
function css
|
|
17
|
+
function css(...classNames) {
|
|
18
18
|
classNames = classNames
|
|
19
|
-
.filter(e => e)
|
|
19
|
+
.filter((e) => e)
|
|
20
20
|
.reduce((accumulator, currentValue) => {
|
|
21
21
|
if (typeof currentValue === 'string') {
|
|
22
22
|
accumulator.push(currentValue)
|
|
23
|
-
} else if (
|
|
23
|
+
} else if (
|
|
24
|
+
!Array.isArray(currentValue) &&
|
|
25
|
+
typeof currentValue === 'object'
|
|
26
|
+
) {
|
|
24
27
|
for (const e in currentValue) {
|
|
25
28
|
if (currentValue[e]) accumulator.push(e)
|
|
26
29
|
}
|
|
@@ -28,13 +31,13 @@ function css (...classNames) {
|
|
|
28
31
|
return accumulator
|
|
29
32
|
}, [])
|
|
30
33
|
|
|
31
|
-
return
|
|
34
|
+
return [...new Set(classNames)].join(' ')
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
/**
|
|
35
38
|
* values
|
|
36
39
|
*/
|
|
37
|
-
function values
|
|
40
|
+
function values(state, payload, value) {
|
|
38
41
|
const paths = payload.split('.')
|
|
39
42
|
|
|
40
43
|
// one level
|
|
@@ -63,15 +66,15 @@ function values (state, payload, value) {
|
|
|
63
66
|
/**
|
|
64
67
|
* merge
|
|
65
68
|
*/
|
|
66
|
-
function merge
|
|
69
|
+
function merge(target, source) {
|
|
67
70
|
// in array return all source
|
|
68
71
|
if (Array.isArray(target)) return source
|
|
69
72
|
|
|
70
|
-
const isObject = obj => obj && typeof obj === 'object'
|
|
73
|
+
const isObject = (obj) => obj && typeof obj === 'object'
|
|
71
74
|
const output = { ...target }
|
|
72
75
|
|
|
73
76
|
// merge
|
|
74
|
-
Object.keys(source).forEach(key => {
|
|
77
|
+
Object.keys(source).forEach((key) => {
|
|
75
78
|
if (isObject(target[key]) && isObject(source[key])) {
|
|
76
79
|
output[key] = merge(target[key], source[key])
|
|
77
80
|
} else output[key] = structuredClone(source[key])
|
|
@@ -97,7 +100,10 @@ const log = (reducer) => {
|
|
|
97
100
|
if (type === 'change') {
|
|
98
101
|
const { name, type, checked, value } = payload.target
|
|
99
102
|
return {
|
|
100
|
-
name,
|
|
103
|
+
name,
|
|
104
|
+
type,
|
|
105
|
+
checked,
|
|
106
|
+
value
|
|
101
107
|
}
|
|
102
108
|
}
|
|
103
109
|
|
|
@@ -108,16 +114,22 @@ const log = (reducer) => {
|
|
|
108
114
|
return payload
|
|
109
115
|
}
|
|
110
116
|
|
|
111
|
-
const reducerWithLogger = useCallback(
|
|
112
|
-
|
|
117
|
+
const reducerWithLogger = useCallback(
|
|
118
|
+
(state, action) => {
|
|
119
|
+
const newState = reducer(state, action)
|
|
113
120
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
console.log(
|
|
122
|
+
`%c${action.isContext ? 'Context' : 'Page '} %c${action.type}`,
|
|
123
|
+
'color: #90b1d1',
|
|
124
|
+
'color: #6592c8',
|
|
125
|
+
getPayload(action),
|
|
126
|
+
{ state, new_state: newState }
|
|
127
|
+
)
|
|
118
128
|
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
return newState
|
|
130
|
+
},
|
|
131
|
+
[reducer]
|
|
132
|
+
)
|
|
121
133
|
|
|
122
134
|
return reducerWithLogger
|
|
123
135
|
}
|
|
@@ -149,7 +161,9 @@ const reducer = (state, action) => {
|
|
|
149
161
|
return values(
|
|
150
162
|
state,
|
|
151
163
|
payload.target.name,
|
|
152
|
-
payload.target.type === 'checkbox'
|
|
164
|
+
payload.target.type === 'checkbox'
|
|
165
|
+
? payload.target.checked
|
|
166
|
+
: payload.target.value
|
|
153
167
|
)
|
|
154
168
|
|
|
155
169
|
case 'reset':
|
|
@@ -171,27 +185,36 @@ const reducer = (state, action) => {
|
|
|
171
185
|
/**
|
|
172
186
|
* useFx
|
|
173
187
|
*/
|
|
174
|
-
function useFx
|
|
188
|
+
function useFx(functions = { initialState: {} }) {
|
|
175
189
|
const context = use(Context)
|
|
176
190
|
const { initialState } = functions
|
|
177
|
-
const [state, dispatch] = useReducer(
|
|
191
|
+
const [state, dispatch] = useReducer(
|
|
192
|
+
logger() ? log(reducer) : reducer,
|
|
193
|
+
initialState
|
|
194
|
+
)
|
|
178
195
|
|
|
179
196
|
// Common actions
|
|
180
|
-
const commonActions = ['set', 'show', 'hide', 'change', 'reset'].reduce(
|
|
181
|
-
acc
|
|
182
|
-
|
|
183
|
-
|
|
197
|
+
const commonActions = ['set', 'show', 'hide', 'change', 'reset'].reduce(
|
|
198
|
+
(acc, e) => {
|
|
199
|
+
acc[e] = (payload) =>
|
|
200
|
+
dispatch({ type: e, payload, initialState, isContext: !context })
|
|
201
|
+
return acc
|
|
202
|
+
},
|
|
203
|
+
{}
|
|
204
|
+
)
|
|
184
205
|
|
|
185
206
|
// Actions
|
|
186
207
|
const actions = Object.keys(functions).reduce((ac, e) => {
|
|
187
208
|
if (functions[e] instanceof Function) {
|
|
188
|
-
ac[e] = payload => {
|
|
209
|
+
ac[e] = (payload) => {
|
|
189
210
|
const props = {
|
|
190
211
|
...commonActions,
|
|
191
212
|
state,
|
|
192
213
|
payload
|
|
193
214
|
}
|
|
194
|
-
if (context) {
|
|
215
|
+
if (context) {
|
|
216
|
+
props.context = context
|
|
217
|
+
}
|
|
195
218
|
|
|
196
219
|
return functions[e](Object.freeze(props))
|
|
197
220
|
}
|
|
@@ -205,7 +228,9 @@ function useFx (functions = { initialState: {} }) {
|
|
|
205
228
|
state,
|
|
206
229
|
fx: { ...commonActions, ...actions }
|
|
207
230
|
}
|
|
208
|
-
if (context) {
|
|
231
|
+
if (context) {
|
|
232
|
+
props.context = context
|
|
233
|
+
}
|
|
209
234
|
|
|
210
235
|
return Object.freeze(props)
|
|
211
236
|
}
|
package/src/template/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# TEMPLATE
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
# To start
|
|
5
4
|
Open http://localhost:3000 to view it in the browser.
|
|
6
5
|
|
|
7
6
|
```sh
|
|
@@ -14,8 +13,7 @@ node --run build <ENV>
|
|
|
14
13
|
node --run preview
|
|
15
14
|
```
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
# env
|
|
19
17
|
```.env
|
|
20
18
|
.env # loaded in all cases
|
|
21
19
|
.env.[ENV] # only loaded in specified ENV [ dev, test, prod ]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
|
3
|
+
"assist": {
|
|
4
|
+
"actions": {
|
|
5
|
+
"source": {
|
|
6
|
+
"organizeImports": "off"
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"linter": {
|
|
11
|
+
"enabled": true,
|
|
12
|
+
"rules": {
|
|
13
|
+
"recommended": true,
|
|
14
|
+
"correctness": {
|
|
15
|
+
"noUnusedVariables": "error"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"formatter": {
|
|
20
|
+
"enabled": true,
|
|
21
|
+
"formatWithErrors": false,
|
|
22
|
+
"indentStyle": "space",
|
|
23
|
+
"indentWidth": 2
|
|
24
|
+
},
|
|
25
|
+
"javascript": {
|
|
26
|
+
"formatter": {
|
|
27
|
+
"semicolons": "asNeeded",
|
|
28
|
+
"quoteStyle": "single",
|
|
29
|
+
"jsxQuoteStyle": "double",
|
|
30
|
+
"trailingCommas": "none"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"css": {
|
|
34
|
+
"formatter": {
|
|
35
|
+
"enabled": true
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"json": {
|
|
39
|
+
"formatter": {
|
|
40
|
+
"enabled": true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -1,38 +1,41 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
2
|
+
"name": "TEMPLATE",
|
|
3
|
+
"description": "description",
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": true,
|
|
7
|
+
"eslintConfig": {
|
|
8
|
+
"extends": "./node_modules/standard/eslintrc.json"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "vite --mode dev",
|
|
12
|
+
"clean": "rm -fr node_modules package-lock.json pnpm-lock.yaml .coverage out",
|
|
13
|
+
"build": "vite build --mode",
|
|
14
|
+
"preview": "vite preview",
|
|
15
|
+
"format": "biome format",
|
|
16
|
+
"lint": "biome lint",
|
|
17
|
+
"check": "biome check --reporter=summary",
|
|
18
|
+
"page": "nextia page",
|
|
19
|
+
"component": "nextia component",
|
|
20
|
+
"componentFx": "nextia componentFx",
|
|
21
|
+
"test": "vitest",
|
|
22
|
+
"test:name": "vitest run --testNamePattern",
|
|
23
|
+
"test:silent": "vitest run --silent",
|
|
24
|
+
"test:coverage": "vitest run --silent --coverage"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@biomejs/biome": "^2.3.14",
|
|
28
|
+
"@vitejs/plugin-react": "^5.1.3",
|
|
29
|
+
"@vitest/coverage-v8": "4.0.18",
|
|
30
|
+
"autoprefixer": "^10.4.24",
|
|
31
|
+
"jsdom": "^28.0.0",
|
|
32
|
+
"standard": "^17.1.2",
|
|
33
|
+
"vite": "^7.3.1",
|
|
34
|
+
"vitest": "4.0.18"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"nextia": "^VERSION",
|
|
38
|
+
"react": "^19.2.4",
|
|
39
|
+
"react-dom": "^19.2.4"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react'
|
|
2
2
|
import icons from 'theme/icons/icons.svg?raw'
|
|
3
3
|
|
|
4
|
-
export default function Icon
|
|
4
|
+
export default function Icon({
|
|
5
5
|
id,
|
|
6
6
|
className,
|
|
7
7
|
style,
|
|
@@ -18,7 +18,8 @@ export default function Icon ({
|
|
|
18
18
|
const ref = useRef()
|
|
19
19
|
|
|
20
20
|
useEffect(() => {
|
|
21
|
-
const svg = new window.DOMParser()
|
|
21
|
+
const svg = new window.DOMParser()
|
|
22
|
+
.parseFromString(icons, 'image/svg+xml')
|
|
22
23
|
.documentElement.getElementById(id)
|
|
23
24
|
|
|
24
25
|
if (svg) {
|
|
@@ -28,7 +29,7 @@ export default function Icon ({
|
|
|
28
29
|
|
|
29
30
|
return (
|
|
30
31
|
<svg
|
|
31
|
-
xmlns=
|
|
32
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
32
33
|
ref={ref}
|
|
33
34
|
id={id}
|
|
34
35
|
className={className}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
|
-
export default function Link
|
|
3
|
+
export default function Link({ children, href, value = {}, ...props }) {
|
|
4
4
|
href ??= window.location.hash.split('?')[0]
|
|
5
|
-
value = Object.keys(value).length
|
|
5
|
+
value = Object.keys(value).length
|
|
6
|
+
? '?' + new URLSearchParams(value).toString()
|
|
7
|
+
: ''
|
|
6
8
|
|
|
7
9
|
return (
|
|
8
10
|
<a href={href + value} {...props}>
|
|
@@ -3,28 +3,29 @@ import { useFx, css } from 'nextia'
|
|
|
3
3
|
import i18nFile from 'assets/i18n'
|
|
4
4
|
import './style.css'
|
|
5
5
|
|
|
6
|
-
export default function Translate
|
|
6
|
+
export default function Translate({ className, style }) {
|
|
7
7
|
const { context } = useFx()
|
|
8
8
|
|
|
9
9
|
return (
|
|
10
|
-
<article
|
|
11
|
-
|
|
10
|
+
<article
|
|
11
|
+
className={css('Translate-component', className, '')}
|
|
12
|
+
style={style}
|
|
13
|
+
>
|
|
12
14
|
<select
|
|
13
|
-
name=
|
|
15
|
+
name="i18n"
|
|
14
16
|
value={context.state.i18n}
|
|
15
|
-
onChange={e => {
|
|
17
|
+
onChange={(e) => {
|
|
16
18
|
const { value } = e.target
|
|
17
19
|
context.fx.set({ i18n: value })
|
|
18
20
|
window.localStorage.setItem('i18n', value)
|
|
19
21
|
}}
|
|
20
22
|
>
|
|
21
|
-
{i18nFile.locales.map(e => (
|
|
22
|
-
<option key={e} value={e} className=
|
|
23
|
+
{i18nFile.locales.map((e) => (
|
|
24
|
+
<option key={e} value={e} className="m-2">
|
|
23
25
|
{e}
|
|
24
26
|
</option>
|
|
25
27
|
))}
|
|
26
28
|
</select>
|
|
27
|
-
|
|
28
29
|
</article>
|
|
29
30
|
)
|
|
30
31
|
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
.Translate-component {
|
|
1
|
+
.Translate-component {
|
|
2
|
+
}
|
|
@@ -3,12 +3,8 @@ import { useFx, css } from 'nextia'
|
|
|
3
3
|
import functions from './functions'
|
|
4
4
|
import './style.css'
|
|
5
5
|
|
|
6
|
-
export default function DemoPage
|
|
6
|
+
export default function DemoPage() {
|
|
7
7
|
const { state, fx } = useFx(functions)
|
|
8
8
|
|
|
9
|
-
return (
|
|
10
|
-
<section className={css('DemoPage', '')}>
|
|
11
|
-
DemoPage
|
|
12
|
-
</section>
|
|
13
|
-
)
|
|
9
|
+
return <section className={css('DemoPage', '')}>DemoPage</section>
|
|
14
10
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
.DemoPage {
|
|
2
|
-
}
|
|
2
|
+
}
|
|
@@ -28,11 +28,11 @@ const initialState = {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function increment
|
|
31
|
+
function increment({ state, set }) {
|
|
32
32
|
set({ channel: state.channel + 1 })
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
function decrement
|
|
35
|
+
function decrement({ state, set }) {
|
|
36
36
|
set({ channel: state.channel - 1 })
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -3,12 +3,8 @@ import './style.css'
|
|
|
3
3
|
import { useFx, css } from 'nextia'
|
|
4
4
|
import functions from './functions'
|
|
5
5
|
|
|
6
|
-
export default function HomePage
|
|
6
|
+
export default function HomePage() {
|
|
7
7
|
const { state, initialState, fx, context } = useFx(functions)
|
|
8
8
|
|
|
9
|
-
return (
|
|
10
|
-
<section className={css('HomePage', 'container')}>
|
|
11
|
-
homePage
|
|
12
|
-
</section>
|
|
13
|
-
)
|
|
9
|
+
return <section className={css('HomePage', 'container')}>homePage</section>
|
|
14
10
|
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
.HomePage {
|
|
1
|
+
.HomePage {
|
|
2
|
+
}
|
|
@@ -2,12 +2,14 @@ import React, { useEffect } from 'react'
|
|
|
2
2
|
import { css } from 'nextia'
|
|
3
3
|
import './style.css'
|
|
4
4
|
|
|
5
|
-
export default function HttpNotFoundPage
|
|
5
|
+
export default function HttpNotFoundPage() {
|
|
6
6
|
return (
|
|
7
|
-
<section
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
<section
|
|
8
|
+
className={css('HttpNotFoundPage', 'd-flex justify-content-center')}
|
|
9
|
+
>
|
|
10
|
+
<div className="d-flex align-items-center">
|
|
11
|
+
<div className="d-flex flex-column">
|
|
12
|
+
<div className="text-center">
|
|
11
13
|
<h5>Not Found</h5>
|
|
12
14
|
</div>
|
|
13
15
|
</div>
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
.HttpNotFoundPage {
|
|
1
|
+
.HttpNotFoundPage {
|
|
2
|
+
}
|
|
@@ -4,7 +4,7 @@ import { Translate, I18n, Icon, Link } from 'components'
|
|
|
4
4
|
import functions from './functions.js'
|
|
5
5
|
import { startViewTransition, useQueryString } from 'utils'
|
|
6
6
|
|
|
7
|
-
export default function Pages
|
|
7
|
+
export default function Pages() {
|
|
8
8
|
const self = useFx(functions)
|
|
9
9
|
const { state, fx } = self
|
|
10
10
|
|
|
@@ -37,28 +37,29 @@ export default function Pages () {
|
|
|
37
37
|
return (
|
|
38
38
|
<Context value={self}>
|
|
39
39
|
<header style={{ display: 'flex', gap: '20px' }}>
|
|
40
|
-
<Icon id=
|
|
40
|
+
<Icon id="globe" width="24" />
|
|
41
41
|
|
|
42
|
-
<Translate
|
|
42
|
+
<Translate
|
|
43
|
+
value={state.i18nLocale}
|
|
44
|
+
onChange={(e) => fx.changeI18n(e)}
|
|
45
|
+
/>
|
|
43
46
|
|
|
44
|
-
<I18n value=
|
|
47
|
+
<I18n value="page.name" args={['Sinuhe', 'Maceda', 'Bouchan']} />
|
|
45
48
|
|
|
46
|
-
<Link href=
|
|
49
|
+
<Link href="/" className="mr-2">
|
|
47
50
|
/
|
|
48
51
|
</Link>
|
|
49
|
-
<Link href=
|
|
52
|
+
<Link href="#/" className="mr-2">
|
|
50
53
|
/home
|
|
51
54
|
</Link>
|
|
52
|
-
<Link href=
|
|
55
|
+
<Link href="#/demo" className="mr-2">
|
|
53
56
|
/demo
|
|
54
57
|
</Link>
|
|
55
|
-
|
|
56
58
|
</header>
|
|
57
59
|
|
|
58
|
-
<main ref={ref} className=
|
|
60
|
+
<main ref={ref} className="m-2">
|
|
59
61
|
{Page && <Page qs={qs.queryString} />}
|
|
60
62
|
</main>
|
|
61
|
-
|
|
62
63
|
</Context>
|
|
63
64
|
)
|
|
64
65
|
}
|
|
@@ -1,57 +1 @@
|
|
|
1
|
-
|
|
2
|
-
--primary-color: #821e8f;
|
|
3
|
-
|
|
4
|
-
transition: transform 200ms linear 0ms;
|
|
5
|
-
|
|
6
|
-
#animation {
|
|
7
|
-
transition: stroke-dashoffset 400ms linear 0ms, stroke-dasharray 400ms linear 0ms;
|
|
8
|
-
stroke: var(--primary-color);
|
|
9
|
-
stroke-dasharray: 1, 200;
|
|
10
|
-
stroke-dashoffset: 1;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
#arrow {
|
|
14
|
-
animation: arrow 2s ease-in-out infinite;
|
|
15
|
-
|
|
16
|
-
#right,
|
|
17
|
-
#left {
|
|
18
|
-
display: none;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
&:hover {
|
|
23
|
-
transform: rotate(-90deg);
|
|
24
|
-
|
|
25
|
-
#animation {
|
|
26
|
-
stroke-dasharray: 200, 1;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
#arrow {
|
|
30
|
-
transform: translateY(2px);
|
|
31
|
-
stroke: var(--primary-color);
|
|
32
|
-
|
|
33
|
-
line {
|
|
34
|
-
stroke-width: 6px;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
#right,
|
|
38
|
-
#left {
|
|
39
|
-
display: block;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
@keyframes arrow {
|
|
46
|
-
0% {
|
|
47
|
-
transform: translateY(2px);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
50% {
|
|
51
|
-
transform: translateY(6px);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
100% {
|
|
55
|
-
transform: translateY(2px);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
1
|
+
/* icons */
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
@import
|
|
2
|
-
@import
|
|
3
|
-
@import
|
|
4
|
-
@import
|
|
1
|
+
@import "./util.css";
|
|
2
|
+
@import "./fonts/index.css";
|
|
3
|
+
@import "./icons/index.css";
|
|
4
|
+
@import "./animations.css";
|
|
5
5
|
|
|
6
6
|
:root {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
--primary-color: #344188;
|
|
8
|
+
--primary-color-50: #3441887a;
|
|
9
|
+
--link: #5fac9f;
|
|
10
|
+
--body-bg: #282c34;
|
|
11
|
+
--body-color: #a8a8a8;
|
|
12
|
+
--font-family: "Roboto";
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -18,23 +18,23 @@
|
|
|
18
18
|
|
|
19
19
|
html,
|
|
20
20
|
body {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
background-color: var(--body-bg);
|
|
22
|
+
color: var(--body-color);
|
|
23
|
+
font-family: var(--font-family);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
a {
|
|
27
|
-
|
|
27
|
+
color: var(--link);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
.mr-2 {
|
|
31
|
-
|
|
31
|
+
margin-right: 20px;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
.m-2 {
|
|
35
|
-
|
|
35
|
+
margin: 20px;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
.p-2 {
|
|
39
|
-
|
|
40
|
-
}
|
|
39
|
+
padding: 20px;
|
|
40
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react'
|
|
2
2
|
|
|
3
|
-
export function useQueryString
|
|
3
|
+
export function useQueryString() {
|
|
4
4
|
const getQueryString = () => ({
|
|
5
5
|
hash: window.location.hash.split('?')[0],
|
|
6
|
-
queryString: Object.fromEntries(
|
|
6
|
+
queryString: Object.fromEntries(
|
|
7
|
+
new URLSearchParams(window.location.hash.split('?')[1])
|
|
8
|
+
)
|
|
7
9
|
})
|
|
8
10
|
|
|
9
11
|
const [queryString, setQueryString] = useState(getQueryString())
|
|
@@ -11,7 +13,9 @@ export function useQueryString () {
|
|
|
11
13
|
useEffect(() => {
|
|
12
14
|
window.addEventListener('popstate', () => setQueryString(getQueryString()))
|
|
13
15
|
return () => {
|
|
14
|
-
window.removeEventListener('popstate', () =>
|
|
16
|
+
window.removeEventListener('popstate', () =>
|
|
17
|
+
setQueryString(getQueryString())
|
|
18
|
+
)
|
|
15
19
|
}
|
|
16
20
|
}, [])
|
|
17
21
|
|
|
@@ -2,16 +2,13 @@ import { useQueryString } from './hooks'
|
|
|
2
2
|
|
|
3
3
|
const env = import.meta.env
|
|
4
4
|
|
|
5
|
-
async function startViewTransition
|
|
6
|
-
if (!document.startViewTransition
|
|
5
|
+
async function startViewTransition(fun = () => {}, ref, animation) {
|
|
6
|
+
if (!document.startViewTransition || env.VITE_VIEW_TRANSITION === 'false')
|
|
7
|
+
return fun()
|
|
7
8
|
|
|
8
9
|
ref.style.viewTransitionName = animation
|
|
9
10
|
await document.startViewTransition(fun).finished
|
|
10
11
|
ref.style.viewTransitionName = ''
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
export {
|
|
14
|
-
env,
|
|
15
|
-
useQueryString,
|
|
16
|
-
startViewTransition
|
|
17
|
-
}
|
|
14
|
+
export { env, useQueryString, startViewTransition }
|
|
@@ -42,9 +42,7 @@ export default defineConfig(({ mode }) => {
|
|
|
42
42
|
|
|
43
43
|
css: {
|
|
44
44
|
postcss: {
|
|
45
|
-
plugins: [
|
|
46
|
-
autoprefixer
|
|
47
|
-
]
|
|
45
|
+
plugins: [autoprefixer]
|
|
48
46
|
}
|
|
49
47
|
},
|
|
50
48
|
|
|
@@ -52,18 +50,23 @@ export default defineConfig(({ mode }) => {
|
|
|
52
50
|
react(),
|
|
53
51
|
{
|
|
54
52
|
name: 'html',
|
|
55
|
-
transformIndexHtml
|
|
53
|
+
transformIndexHtml(html) {
|
|
56
54
|
let gitHash = ''
|
|
57
55
|
try {
|
|
58
|
-
gitHash = execSync('git rev-parse --short HEAD 2> /dev/null')
|
|
59
|
-
|
|
56
|
+
gitHash = execSync('git rev-parse --short HEAD 2> /dev/null')
|
|
57
|
+
.toString()
|
|
58
|
+
.trim()
|
|
59
|
+
} catch (e) {}
|
|
60
60
|
|
|
61
|
-
return html.replaceAll(
|
|
61
|
+
return html.replaceAll(
|
|
62
|
+
'%VERSION%',
|
|
63
|
+
`version=${version}, env=${mode}, release-date=${new Date()}, git-hash=${gitHash}`
|
|
64
|
+
)
|
|
62
65
|
}
|
|
63
66
|
},
|
|
64
67
|
{
|
|
65
68
|
name: 'svg',
|
|
66
|
-
async transform
|
|
69
|
+
async transform(src, id) {
|
|
67
70
|
let code = id.split('?')[0]
|
|
68
71
|
const type = id.split('?')[1]
|
|
69
72
|
|
|
@@ -83,19 +86,12 @@ export default defineConfig(({ mode }) => {
|
|
|
83
86
|
|
|
84
87
|
test: {
|
|
85
88
|
root: './',
|
|
86
|
-
watch: false,
|
|
87
89
|
environment: 'jsdom',
|
|
88
|
-
include: ['test/**/*.js', 'test/**/*.jsx'],
|
|
89
90
|
coverage: {
|
|
90
|
-
all: true,
|
|
91
91
|
reportsDirectory: '.coverage',
|
|
92
|
-
include: ['src/**/*.js
|
|
93
|
-
exclude: [
|
|
94
|
-
'src/index.jsx',
|
|
95
|
-
'src/assets/i18n'
|
|
96
|
-
]
|
|
92
|
+
include: ['src/**/*.{js,jsx}'],
|
|
93
|
+
exclude: ['.coverage', 'src/assets', 'src/index.jsx']
|
|
97
94
|
}
|
|
98
95
|
}
|
|
99
|
-
|
|
100
96
|
}
|
|
101
97
|
})
|