nextia 6.1.1 → 7.0.2
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 +1 -1
- package/biome.json +21 -0
- package/package.json +13 -8
- package/src/bin.js +239 -0
- package/src/{lib.js → lib/fx.js} +18 -33
- package/src/lib/index.js +32 -0
- package/src/lib/ui.js +112 -0
- package/src/lib/utils.js +107 -0
- package/template/README.md +29 -0
- package/template/_env.dev +4 -0
- package/template/_env.prod +1 -0
- package/template/_env.test +1 -0
- package/template/_gitignore +10 -0
- package/template/biome.json +21 -0
- package/template/package.json +35 -0
- package/template/public/error.html +14 -0
- package/template/public/logo.svg +105 -0
- package/template/src/assets/i18n/index.js +26 -0
- package/template/src/assets/img/image.svg +6 -0
- package/template/src/assets/img/image.webp +0 -0
- package/template/src/components/Counter/index.jsx +33 -0
- package/template/src/components/Counter/style.css +5 -0
- package/template/src/components/Message/index.jsx +12 -0
- package/template/src/components/index.js +6 -0
- package/template/src/components/ui/Translate/index.jsx +20 -0
- package/template/src/index.html +18 -0
- package/template/src/index.jsx +4 -0
- package/template/src/pages/env/functions.js +3 -0
- package/template/src/pages/env/index.jsx +26 -0
- package/template/src/pages/env/style.css +2 -0
- package/template/src/pages/functions.js +37 -0
- package/template/src/pages/home/functions.js +43 -0
- package/template/src/pages/home/index.jsx +211 -0
- package/template/src/pages/home/style.css +51 -0
- package/template/src/pages/http/not-found/index.jsx +10 -0
- package/template/src/pages/http/not-found/style.css +2 -0
- package/template/src/pages/icons/functions.js +3 -0
- package/template/src/pages/icons/index.jsx +21 -0
- package/template/src/pages/icons/style.css +8 -0
- package/template/src/pages/images/functions.js +3 -0
- package/template/src/pages/images/index.jsx +25 -0
- package/template/src/pages/images/style.css +27 -0
- package/template/src/pages/index.jsx +124 -0
- package/template/src/pages/mockapi/functions.js +71 -0
- package/template/src/pages/mockapi/index.jsx +101 -0
- package/template/src/pages/mockapi/style.css +57 -0
- package/template/src/pages/my-context/functions.js +7 -0
- package/template/src/pages/my-context/index.jsx +32 -0
- package/template/src/pages/my-context/style.css +2 -0
- package/template/src/pages/resize/functions.js +3 -0
- package/template/src/pages/resize/index.jsx +15 -0
- package/template/src/pages/resize/style.css +2 -0
- package/template/src/pages/search-params/functions.js +3 -0
- package/template/src/pages/search-params/index.jsx +35 -0
- package/template/src/pages/search-params/style.css +2 -0
- package/template/src/pages/subpage/hello/functions.js +3 -0
- package/template/src/pages/subpage/hello/index.jsx +11 -0
- package/template/src/pages/subpage/hello/style.css +2 -0
- package/template/src/pages/translate/functions.js +5 -0
- package/template/src/pages/translate/index.jsx +31 -0
- package/template/src/pages/translate/style.css +12 -0
- package/template/src/pages/view-transition/functions.js +6 -0
- package/template/src/pages/view-transition/index.jsx +30 -0
- package/template/src/pages/view-transition/style.css +2 -0
- package/template/src/services/api.js +9 -0
- package/template/src/services/http.js +40 -0
- package/template/src/theme/fonts/Roboto-Regular.ttf +0 -0
- package/template/src/theme/fonts/index.css +7 -0
- package/template/src/theme/icons/icons.svg +125 -0
- package/template/src/theme/icons/index.css +55 -0
- package/template/src/theme/index.css +39 -0
- package/template/src/theme/utils/index.css +29 -0
- package/template/src/theme/utils/view-transition.css +72 -0
- package/template/src/utils/index.js +5 -0
- package/template/test/index.test.js +11 -0
- package/template/vite.config.js +97 -0
package/README.md
CHANGED
package/biome.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"formatter": {
|
|
3
|
+
"indentStyle": "space",
|
|
4
|
+
"indentWidth": 2
|
|
5
|
+
},
|
|
6
|
+
|
|
7
|
+
"javascript": {
|
|
8
|
+
"formatter": {
|
|
9
|
+
"semicolons": "asNeeded",
|
|
10
|
+
"quoteStyle": "single",
|
|
11
|
+
"jsxQuoteStyle": "double",
|
|
12
|
+
"trailingCommas": "none"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
"css": {
|
|
17
|
+
"parser": {
|
|
18
|
+
"tailwindDirectives": true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nextia",
|
|
3
3
|
"description": "Create fast web applications",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "7.0.2",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
5
7
|
"engines": {
|
|
6
8
|
"node": ">22"
|
|
7
9
|
},
|
|
8
|
-
"type": "module",
|
|
9
|
-
"license": "MIT",
|
|
10
10
|
"author": {
|
|
11
11
|
"name": "Sinuhe Maceda",
|
|
12
12
|
"email": "sinuhe.dev@gmail.com",
|
|
@@ -20,9 +20,14 @@
|
|
|
20
20
|
"keywords": [
|
|
21
21
|
"react"
|
|
22
22
|
],
|
|
23
|
-
"
|
|
23
|
+
"bin": {
|
|
24
|
+
"nextia": "src/bin.js"
|
|
25
|
+
},
|
|
26
|
+
"exports": {
|
|
27
|
+
".": "./src/lib/index.js"
|
|
28
|
+
},
|
|
24
29
|
"scripts": {
|
|
25
|
-
"clean": "rm -fr node_modules package-lock.json
|
|
30
|
+
"clean": "rm -fr my-app node_modules package-lock.json .coverage out",
|
|
26
31
|
"format": "biome format",
|
|
27
32
|
"lint": "biome lint src",
|
|
28
33
|
"check": "biome check --reporter=summary src",
|
|
@@ -37,8 +42,8 @@
|
|
|
37
42
|
},
|
|
38
43
|
"devDependencies": {
|
|
39
44
|
"@vitejs/plugin-react": "^6.0.1",
|
|
40
|
-
"@vitest/coverage-v8": "^4.
|
|
41
|
-
"jsdom": "^29.0.
|
|
42
|
-
"vitest": "^4.
|
|
45
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
46
|
+
"jsdom": "^29.0.1",
|
|
47
|
+
"vitest": "^4.1.2"
|
|
43
48
|
}
|
|
44
49
|
}
|
package/src/bin.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Copyright (c) 2025 Sinuhe Maceda https://sinuhe.dev
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*
|
|
9
|
+
* https://github.com/sinuhedev/create-nextia
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
access,
|
|
14
|
+
cp,
|
|
15
|
+
mkdir,
|
|
16
|
+
readFile,
|
|
17
|
+
rename,
|
|
18
|
+
writeFile
|
|
19
|
+
} from 'node:fs/promises'
|
|
20
|
+
import { dirname } from 'node:path'
|
|
21
|
+
import { fileURLToPath } from 'node:url'
|
|
22
|
+
import pkg from '../package.json' with { type: 'json' }
|
|
23
|
+
|
|
24
|
+
function toPascalCase(str) {
|
|
25
|
+
return str
|
|
26
|
+
.toLowerCase()
|
|
27
|
+
.replace(/[^a-zA-Z0-9 ]/g, ' ') // replace special characters
|
|
28
|
+
.split(/\s+/) // split by spaces
|
|
29
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
30
|
+
.join('')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function createPage(name) {
|
|
34
|
+
const dirName = `./src/pages/${name}`
|
|
35
|
+
const pageName = `${toPascalCase(name)}Page`
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
await mkdir(dirName)
|
|
39
|
+
|
|
40
|
+
// index.jsx
|
|
41
|
+
writeFile(
|
|
42
|
+
`${dirName}/index.jsx`,
|
|
43
|
+
`import { css, useFx } from 'nextia'
|
|
44
|
+
import { useEffect } from 'react'
|
|
45
|
+
import functions from './functions'
|
|
46
|
+
import './style.css'
|
|
47
|
+
|
|
48
|
+
export default function ${pageName} () {
|
|
49
|
+
const { state, fx } = useFx(functions)
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<section className={css('${pageName}', '')}>
|
|
53
|
+
${pageName}
|
|
54
|
+
</section>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
`
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
// style.sss
|
|
61
|
+
writeFile(
|
|
62
|
+
`${dirName}/style.css`,
|
|
63
|
+
`.${pageName} {
|
|
64
|
+
}
|
|
65
|
+
`
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
// function.js
|
|
69
|
+
writeFile(
|
|
70
|
+
`${dirName}/functions.js`,
|
|
71
|
+
`const initialState = {}
|
|
72
|
+
|
|
73
|
+
export default { initialState }
|
|
74
|
+
`
|
|
75
|
+
)
|
|
76
|
+
console.info(`✔ Page "${pageName}" created at ${dirName}`)
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.error(`Failed to create page: ${err.message}`)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function createComponent(name) {
|
|
83
|
+
const dirName = `./src/components/${name}`
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
await mkdir(dirName)
|
|
87
|
+
const componentName = toPascalCase(name)
|
|
88
|
+
|
|
89
|
+
// index.jsx
|
|
90
|
+
writeFile(
|
|
91
|
+
`${dirName}/index.jsx`,
|
|
92
|
+
`import { css } from 'nextia'
|
|
93
|
+
import { useEffect } from 'react'
|
|
94
|
+
import './style.css'
|
|
95
|
+
|
|
96
|
+
export default function ${componentName} ({ className, style }) {
|
|
97
|
+
return (
|
|
98
|
+
<article className={css('${componentName}', className)} style={style}>
|
|
99
|
+
${componentName}
|
|
100
|
+
</article>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
`
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
// style.css
|
|
107
|
+
writeFile(
|
|
108
|
+
`${dirName}/style.css`,
|
|
109
|
+
`.${componentName} {
|
|
110
|
+
}
|
|
111
|
+
`
|
|
112
|
+
)
|
|
113
|
+
console.info(`✔ Component "${name}" created at ${dirName}`)
|
|
114
|
+
} catch (err) {
|
|
115
|
+
console.error(`Failed to create component: ${err.message}`)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function createContainer(name) {
|
|
120
|
+
const dirName = `./src/components/${name}`
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
await mkdir(dirName)
|
|
124
|
+
const containerName = toPascalCase(name)
|
|
125
|
+
|
|
126
|
+
// index.jsx
|
|
127
|
+
writeFile(
|
|
128
|
+
`${dirName}/index.jsx`,
|
|
129
|
+
`import { useEffect } from 'react'
|
|
130
|
+
import { css, useFx } from 'nextia'
|
|
131
|
+
import functions from './functions'
|
|
132
|
+
import './style.css'
|
|
133
|
+
|
|
134
|
+
export default function ${containerName} ({ className, style }) {
|
|
135
|
+
const { state, fx } = useFx(functions)
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<article className={css('${containerName}', className, '')} style={style}>
|
|
139
|
+
${containerName}
|
|
140
|
+
</article>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
`
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
// style.css
|
|
147
|
+
writeFile(
|
|
148
|
+
`${dirName}/style.css`,
|
|
149
|
+
`.${containerName} {
|
|
150
|
+
}
|
|
151
|
+
`
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
// function.js
|
|
155
|
+
writeFile(
|
|
156
|
+
`${dirName}/functions.js`,
|
|
157
|
+
`const initialState = {}
|
|
158
|
+
|
|
159
|
+
export default { initialState }
|
|
160
|
+
`
|
|
161
|
+
)
|
|
162
|
+
console.info(`✔ Container "${name}" created at ${dirName}`)
|
|
163
|
+
} catch (err) {
|
|
164
|
+
console.error(`Failed to create container: ${err.message}`)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function createProject(name) {
|
|
169
|
+
const projectPath = `${process.cwd()}/${name}/`
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
await access(projectPath)
|
|
173
|
+
console.error(`The ${name} already exists`)
|
|
174
|
+
return
|
|
175
|
+
} catch {
|
|
176
|
+
/* directory doesn't exist, proceed */
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
181
|
+
const templatePath = `${__dirname}/../template`
|
|
182
|
+
|
|
183
|
+
const replaceToken = async (filename, token, value) => {
|
|
184
|
+
const content = await readFile(projectPath + filename, 'utf8')
|
|
185
|
+
await writeFile(
|
|
186
|
+
projectPath + filename,
|
|
187
|
+
content.replaceAll(token, value),
|
|
188
|
+
'utf8'
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
await cp(templatePath, projectPath, { recursive: true })
|
|
193
|
+
|
|
194
|
+
await Promise.all(
|
|
195
|
+
['env.dev', 'env.prod', 'env.test', 'gitignore'].map((fileName) =>
|
|
196
|
+
rename(`${projectPath}_${fileName}`, `${projectPath}.${fileName}`)
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
await replaceToken('README.md', 'TEMPLATE', name)
|
|
201
|
+
await replaceToken('package.json', 'TEMPLATE', name)
|
|
202
|
+
await replaceToken('package.json', 'file:../', pkg.version)
|
|
203
|
+
|
|
204
|
+
console.info(`✔ Project "${name}" created successfully!`)
|
|
205
|
+
} catch (err) {
|
|
206
|
+
console.error(`Failed to create project: ${err.message}`)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function main() {
|
|
211
|
+
const ARG1 = process.argv[2]
|
|
212
|
+
const ARG2 = process.argv[3]
|
|
213
|
+
|
|
214
|
+
switch (ARG1) {
|
|
215
|
+
case 'page':
|
|
216
|
+
if (ARG2) await createPage(ARG2)
|
|
217
|
+
else console.warn('node --run nextia page <page-name>')
|
|
218
|
+
break
|
|
219
|
+
|
|
220
|
+
case 'component':
|
|
221
|
+
if (ARG2) await createComponent(ARG2)
|
|
222
|
+
else console.warn('node --run nextia component <ComponentName>')
|
|
223
|
+
break
|
|
224
|
+
|
|
225
|
+
case 'container':
|
|
226
|
+
if (ARG2) await createContainer(ARG2)
|
|
227
|
+
else console.warn('node --run nextia container <ContainerName>')
|
|
228
|
+
break
|
|
229
|
+
|
|
230
|
+
default:
|
|
231
|
+
if (ARG1) await createProject(ARG1)
|
|
232
|
+
else console.info(`v${pkg.version}\nnextia <ProjectName>`)
|
|
233
|
+
break
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
main().catch((e) => {
|
|
238
|
+
console.error(e)
|
|
239
|
+
})
|
package/src/{lib.js → lib/fx.js}
RENAMED
|
@@ -9,32 +9,13 @@
|
|
|
9
9
|
|
|
10
10
|
import { createContext, use, useReducer } from 'react'
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
const
|
|
12
|
+
const LOGGER = import.meta.env.DEV && import.meta.env.PUBLIC_LOGGER !== 'false'
|
|
13
|
+
const PagesFx = createContext()
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* util
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
function css(...classNames) {
|
|
20
|
-
return classNames
|
|
21
|
-
.reduce((accumulator, currentValue) => {
|
|
22
|
-
if (typeof currentValue === 'string') {
|
|
23
|
-
accumulator.push(currentValue.trim())
|
|
24
|
-
} else if (
|
|
25
|
-
!Array.isArray(currentValue) &&
|
|
26
|
-
typeof currentValue === 'object'
|
|
27
|
-
) {
|
|
28
|
-
for (const e in currentValue) {
|
|
29
|
-
if (currentValue[e]) accumulator.push(e.trim())
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return accumulator
|
|
33
|
-
}, [])
|
|
34
|
-
.filter((e) => e)
|
|
35
|
-
.join(' ')
|
|
36
|
-
}
|
|
37
|
-
|
|
38
19
|
function values(state, payload, value) {
|
|
39
20
|
const paths = payload.split('.')
|
|
40
21
|
|
|
@@ -166,10 +147,10 @@ const reducerLogger = (state, action) => {
|
|
|
166
147
|
*/
|
|
167
148
|
|
|
168
149
|
function useFx(functions = { initialState: {} }) {
|
|
169
|
-
const
|
|
150
|
+
const pagesFx = use(PagesFx)
|
|
170
151
|
const { initialState } = functions
|
|
171
152
|
const [state, dispatch] = useReducer(
|
|
172
|
-
|
|
153
|
+
LOGGER ? reducerLogger : reducer,
|
|
173
154
|
initialState
|
|
174
155
|
)
|
|
175
156
|
|
|
@@ -177,7 +158,12 @@ function useFx(functions = { initialState: {} }) {
|
|
|
177
158
|
const commonActions = ['set', 'show', 'hide', 'change', 'reset'].reduce(
|
|
178
159
|
(acc, e) => {
|
|
179
160
|
acc[e] = (payload) =>
|
|
180
|
-
dispatch({
|
|
161
|
+
dispatch({
|
|
162
|
+
type: e,
|
|
163
|
+
payload,
|
|
164
|
+
initialState,
|
|
165
|
+
isContext: !pagesFx?.context
|
|
166
|
+
})
|
|
181
167
|
return acc
|
|
182
168
|
},
|
|
183
169
|
{}
|
|
@@ -190,10 +176,8 @@ function useFx(functions = { initialState: {} }) {
|
|
|
190
176
|
const actionsProps = {
|
|
191
177
|
...commonActions,
|
|
192
178
|
state,
|
|
193
|
-
payload
|
|
194
|
-
|
|
195
|
-
if (pageContext) {
|
|
196
|
-
actionsProps.context = pageContext
|
|
179
|
+
payload,
|
|
180
|
+
context: pagesFx?.context
|
|
197
181
|
}
|
|
198
182
|
|
|
199
183
|
return functions[e](Object.freeze(actionsProps))
|
|
@@ -206,13 +190,14 @@ function useFx(functions = { initialState: {} }) {
|
|
|
206
190
|
const props = {
|
|
207
191
|
initialState,
|
|
208
192
|
state,
|
|
209
|
-
fx: { ...commonActions, ...actions }
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
193
|
+
fx: { ...commonActions, ...actions },
|
|
194
|
+
//
|
|
195
|
+
context: pagesFx?.context,
|
|
196
|
+
icons: pagesFx?.icons,
|
|
197
|
+
i18n: pagesFx?.i18n
|
|
213
198
|
}
|
|
214
199
|
|
|
215
200
|
return Object.freeze(props)
|
|
216
201
|
}
|
|
217
202
|
|
|
218
|
-
export {
|
|
203
|
+
export { PagesFx, useFx }
|
package/src/lib/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Sinuhe Maceda https://sinuhe.dev
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* https://github.com/sinuhedev/nextia
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { PagesFx, useFx } from './fx.js'
|
|
11
|
+
import { I18n, Icon, Link, Svg } from './ui.js'
|
|
12
|
+
import {
|
|
13
|
+
css,
|
|
14
|
+
env,
|
|
15
|
+
startViewTransition,
|
|
16
|
+
useQueryString,
|
|
17
|
+
useResize
|
|
18
|
+
} from './utils.js'
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
css,
|
|
22
|
+
env,
|
|
23
|
+
I18n,
|
|
24
|
+
Icon,
|
|
25
|
+
Link,
|
|
26
|
+
PagesFx,
|
|
27
|
+
Svg,
|
|
28
|
+
startViewTransition,
|
|
29
|
+
useFx,
|
|
30
|
+
useQueryString,
|
|
31
|
+
useResize
|
|
32
|
+
}
|
package/src/lib/ui.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { createElement, useEffect, useRef } from 'react'
|
|
2
|
+
import { useFx } from './fx'
|
|
3
|
+
import { css } from './utils'
|
|
4
|
+
|
|
5
|
+
function Link({ children, href, value = {}, ...props }) {
|
|
6
|
+
href ??= window.location.hash.split('?')[0]
|
|
7
|
+
value = Object.keys(value).length
|
|
8
|
+
? `?${new URLSearchParams(value).toString()}`
|
|
9
|
+
: ''
|
|
10
|
+
|
|
11
|
+
return createElement('a', { href: href + value, ...props }, children)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function I18n({ value, args = [] }) {
|
|
15
|
+
const { context, i18n } = useFx()
|
|
16
|
+
|
|
17
|
+
if (i18n) {
|
|
18
|
+
try {
|
|
19
|
+
let text = value.split('.').reduce((ac, el) => ac[el], i18n)
|
|
20
|
+
text = text[i18n.locales.indexOf(context.state.i18n.currentLocale)]
|
|
21
|
+
|
|
22
|
+
if (args) {
|
|
23
|
+
text = text.replace(
|
|
24
|
+
/([{}])\\1|[{](.*?)(?:!(.+?))?[}]/g,
|
|
25
|
+
(match, _literal, number) => args[number] || match
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return text
|
|
30
|
+
} catch {
|
|
31
|
+
console.error(`Error in [il8n] => ${value}`)
|
|
32
|
+
return value
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function Icon({
|
|
38
|
+
id,
|
|
39
|
+
className,
|
|
40
|
+
animate = false,
|
|
41
|
+
style,
|
|
42
|
+
width = '48',
|
|
43
|
+
height,
|
|
44
|
+
viewBox = '0 0 48 48',
|
|
45
|
+
fill = 'none',
|
|
46
|
+
color = 'currentColor',
|
|
47
|
+
stroke = 'currentColor',
|
|
48
|
+
strokeWidth = '2',
|
|
49
|
+
strokeLinecap = 'round',
|
|
50
|
+
strokeLinejoin = 'round',
|
|
51
|
+
...props
|
|
52
|
+
}) {
|
|
53
|
+
const { icons } = useFx()
|
|
54
|
+
const ref = useRef()
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (icons) {
|
|
58
|
+
const svg = new DOMParser()
|
|
59
|
+
.parseFromString(icons, 'image/svg+xml')
|
|
60
|
+
.documentElement.getElementById(id)
|
|
61
|
+
|
|
62
|
+
if (svg) {
|
|
63
|
+
ref.current.innerHTML = svg.innerHTML
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}, [id, icons])
|
|
67
|
+
|
|
68
|
+
return createElement('svg', {
|
|
69
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
|
70
|
+
ref,
|
|
71
|
+
id,
|
|
72
|
+
className: css({ 'nextia-animate-icon': animate }, className),
|
|
73
|
+
style,
|
|
74
|
+
width,
|
|
75
|
+
height: height ?? width,
|
|
76
|
+
viewBox,
|
|
77
|
+
fill,
|
|
78
|
+
color,
|
|
79
|
+
stroke,
|
|
80
|
+
strokeWidth,
|
|
81
|
+
strokeLinecap,
|
|
82
|
+
strokeLinejoin,
|
|
83
|
+
...props
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function Svg({ ref, src, width, height, ...props }) {
|
|
88
|
+
ref ??= useRef()
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
const svg = new DOMParser().parseFromString(
|
|
92
|
+
src,
|
|
93
|
+
'image/svg+xml'
|
|
94
|
+
).documentElement
|
|
95
|
+
|
|
96
|
+
for (const { name, value } of svg.attributes) {
|
|
97
|
+
if (name !== 'width' && name !== 'height')
|
|
98
|
+
ref.current.setAttribute(name, value)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
ref.current.replaceChildren(...svg.children)
|
|
102
|
+
}, [src, ref])
|
|
103
|
+
|
|
104
|
+
return createElement('svg', {
|
|
105
|
+
ref,
|
|
106
|
+
width,
|
|
107
|
+
height: height ?? width,
|
|
108
|
+
...props
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export { I18n, Icon, Link, Svg }
|
package/src/lib/utils.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Sinuhe Maceda https://sinuhe.dev
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* https://github.com/sinuhedev/nextia
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
11
|
+
import { flushSync } from 'react-dom'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* env
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const env = import.meta.env
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* View Transition
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
async function startViewTransition(fun = () => {}, ref, animation = 'fade') {
|
|
24
|
+
if (!document.startViewTransition || env.PUBLIC_VIEW_TRANSITION === 'false')
|
|
25
|
+
return fun()
|
|
26
|
+
|
|
27
|
+
ref.style.viewTransitionName = animation
|
|
28
|
+
await document.startViewTransition(() => flushSync(fun)).finished
|
|
29
|
+
ref.style.viewTransitionName = ''
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* hooks
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
function useQueryString() {
|
|
37
|
+
const getQueryString = useCallback(
|
|
38
|
+
() => ({
|
|
39
|
+
hash: window.location.hash.split('?')[0],
|
|
40
|
+
queryString: Object.fromEntries(
|
|
41
|
+
new URLSearchParams(window.location.hash.split('?')[1])
|
|
42
|
+
)
|
|
43
|
+
}),
|
|
44
|
+
[]
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
const [queryString, setQueryString] = useState(getQueryString)
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
const handlePopState = () => setQueryString(getQueryString())
|
|
51
|
+
|
|
52
|
+
window.addEventListener('popstate', handlePopState)
|
|
53
|
+
return () => {
|
|
54
|
+
window.removeEventListener('popstate', handlePopState)
|
|
55
|
+
}
|
|
56
|
+
}, [getQueryString])
|
|
57
|
+
|
|
58
|
+
return queryString
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function useResize() {
|
|
62
|
+
const getResize = useCallback(
|
|
63
|
+
() => ({
|
|
64
|
+
width: window.innerWidth,
|
|
65
|
+
height: window.innerHeight
|
|
66
|
+
}),
|
|
67
|
+
[]
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
const [resize, setResize] = useState(getResize)
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const handleResize = () => setResize(getResize())
|
|
74
|
+
|
|
75
|
+
window.addEventListener('resize', handleResize)
|
|
76
|
+
return () => {
|
|
77
|
+
window.removeEventListener('resize', handleResize)
|
|
78
|
+
}
|
|
79
|
+
}, [getResize])
|
|
80
|
+
|
|
81
|
+
return resize
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* util
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
function css(...classNames) {
|
|
89
|
+
return classNames
|
|
90
|
+
.reduce((accumulator, currentValue) => {
|
|
91
|
+
if (typeof currentValue === 'string') {
|
|
92
|
+
accumulator.push(currentValue.trim())
|
|
93
|
+
} else if (
|
|
94
|
+
!Array.isArray(currentValue) &&
|
|
95
|
+
typeof currentValue === 'object'
|
|
96
|
+
) {
|
|
97
|
+
for (const e in currentValue) {
|
|
98
|
+
if (currentValue[e]) accumulator.push(e.trim())
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return accumulator
|
|
102
|
+
}, [])
|
|
103
|
+
.filter((e) => e)
|
|
104
|
+
.join(' ')
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export { css, env, startViewTransition, useQueryString, useResize }
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# TEMPLATE
|
|
2
|
+
|
|
3
|
+
# To start
|
|
4
|
+
Open http://localhost:3000 to view it in the browser.
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npm install
|
|
8
|
+
#
|
|
9
|
+
node --run dev
|
|
10
|
+
node --run test
|
|
11
|
+
node --run build <ENV>
|
|
12
|
+
node --run preview
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
# env
|
|
16
|
+
```.env
|
|
17
|
+
.env # loaded in all cases
|
|
18
|
+
.env.[ENV] # only loaded in specified ENV [ dev, test, prod ]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
* .env.dev
|
|
22
|
+
* .env.prod
|
|
23
|
+
* .env.test
|
|
24
|
+
|
|
25
|
+
```env
|
|
26
|
+
PUBLIC_TITLE=TITLE
|
|
27
|
+
PUBLIC_LOGGER=true
|
|
28
|
+
PUBLIC_VIEW_TRANSITION=true
|
|
29
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
PUBLIC_TITLE=prod
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
PUBLIC_TITLE=test
|