nextia 3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Sinuhe Maceda https://sinuhe.dev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # nextia
2
+
3
+ ## Create fast web applications
4
+
5
+ ```sh
6
+ npm i
7
+ # Go to examples
8
+ cd examples
9
+ ```
10
+
11
+ ```sh
12
+ # create project
13
+ npx nextia@latest my-app
14
+ ```
15
+
16
+ [npm](https://www.npmjs.com/package/nextia)
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "nextia",
3
+ "description": "Create fast web applications",
4
+ "version": "3.0.0",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": {
8
+ "name": "Sinuhe Maceda",
9
+ "email": "sinuhe.dev@gmail.com",
10
+ "url": "https://sinuhe.dev"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/sinuhedev/nextia.git"
15
+ },
16
+ "keywords": [
17
+ "react"
18
+ ],
19
+ "main": "src/lib.js",
20
+ "bin": {
21
+ "nextia": "src/bin.js"
22
+ },
23
+ "eslintConfig": {
24
+ "extends": "./node_modules/standard/eslintrc.json"
25
+ },
26
+ "scripts": {
27
+ "start": "echo 'go to examples'",
28
+ "clean": "rm -fr node_modules package-lock.json",
29
+ "make": "npm run clean && npm install",
30
+ "eslint": "standard src",
31
+ "nextia": "node src/bin.js",
32
+ "nextia:my-app": "node src/bin.js my-app"
33
+ },
34
+ "peerDependencies": {
35
+ "react": "^19.0.0",
36
+ "react-dom": "^19.0.0"
37
+ },
38
+ "devDependencies": {
39
+ "standard": "^17.1.2"
40
+ }
41
+ }
package/src/bin.js ADDED
@@ -0,0 +1,187 @@
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/nextia
10
+ */
11
+
12
+ import { mkdir, writeFile } from 'node:fs/promises'
13
+
14
+ async function createPage (name, isNext, isType) {
15
+ const toPascalCase = str => str
16
+ .toLowerCase()
17
+ .replace(/[^a-zA-Z0-9 ]/g, ' ') // replace special characters
18
+ .split(/\s+/) // split by spaces
19
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
20
+ .join('')
21
+
22
+ const index = isNext ? 1 : 0
23
+ const config = {
24
+ root: ['pages', 'app'],
25
+ file: ['index.jsx', 'page.jsx'],
26
+ directive: [
27
+ '',
28
+ `'use client'
29
+
30
+ `]
31
+ }
32
+
33
+ const dirName = `./src/${config.root[index]}/${name}`
34
+
35
+ try {
36
+ await mkdir(dirName)
37
+
38
+ const pageName = toPascalCase(name) + 'Page'
39
+
40
+ // index.jsx
41
+ writeFile(`${dirName}/${config.file[index]}`,
42
+ `${config.directive[index]}import React, { useEffect } from 'react'
43
+ import { useFx, css } from 'nextia'
44
+ import functions from './functions'
45
+ import './style.css'
46
+
47
+ export default function ${pageName} () {
48
+ const { state, fx } = useFx(functions)
49
+
50
+ return (
51
+ <section className={css('${pageName}', '')}>
52
+ ${pageName}
53
+ </section>
54
+ )
55
+ }
56
+ `)
57
+
58
+ // style.sss
59
+ writeFile(`${dirName}/style.css`,
60
+ `.${pageName} {
61
+ }`)
62
+
63
+ // function.js
64
+ writeFile(`${dirName}/functions.js`,
65
+ `const initialState = {
66
+ }
67
+
68
+ export default { initialState }
69
+ `)
70
+ } catch (err) {
71
+ console.error(err)
72
+ }
73
+ }
74
+
75
+ async function createComponent (name, isType) {
76
+ const dirName = `./src/components/${name}`
77
+
78
+ try {
79
+ await mkdir(dirName)
80
+ const componentName = name.replaceAll('/', '') + '-component'
81
+
82
+ // index.jsx
83
+ writeFile(`${dirName}/index.jsx`,
84
+ `import React, { useEffect } from 'react'
85
+ import { css } from 'nextia'
86
+ import './style.css'
87
+
88
+ export default function ${name} ({ className, style }) {
89
+ return (
90
+ <article className={css('${componentName}', className)} style={style}>
91
+ ${componentName}
92
+ </article>
93
+ )
94
+ }
95
+ `)
96
+
97
+ // style.css
98
+ writeFile(`${dirName}/style.css`,
99
+ `.${componentName} {
100
+ }`
101
+ )
102
+ } catch (err) {
103
+ console.error(err)
104
+ }
105
+ }
106
+
107
+ async function createContainer (name, isType) {
108
+ const dirName = `./src/containers/${name}`
109
+
110
+ try {
111
+ await mkdir(dirName)
112
+ const containerName = name.replaceAll('/', '') + '-container'
113
+
114
+ // index.jsx
115
+ writeFile(`${dirName}/index.jsx`,
116
+ `import React, { useEffect } from 'react'
117
+ import { useFx, css } from 'nextia'
118
+ import functions from './functions'
119
+ import './style.css'
120
+
121
+ export default function ${name} ({ className, style }) {
122
+ const { state, fx } = useFx(functions)
123
+
124
+ return (
125
+ <article className={css('${containerName}', className, '')} style={style}>
126
+ ${containerName}
127
+ </article>
128
+ )
129
+ }
130
+ `)
131
+
132
+ // style.css
133
+ writeFile(`${dirName}/style.css`,
134
+ `.${containerName} {
135
+ }`)
136
+
137
+ // function.js
138
+ writeFile(`${dirName}/functions.js`,
139
+ `const initialState = {
140
+ }
141
+
142
+ export default { initialState }
143
+ `)
144
+ } catch (err) {
145
+ console.error(err)
146
+ }
147
+ }
148
+
149
+ /**
150
+ * main
151
+ */
152
+
153
+ const CMD = process.argv[2]
154
+ const PROJECT_NAME = process.argv[2]
155
+ const FILE_NAME = process.argv[3]
156
+
157
+ switch (CMD) {
158
+ case 'page':
159
+ case 'page:type':
160
+ case 'next:page':
161
+ case 'next:page:type':
162
+ if (FILE_NAME) {
163
+ createPage(
164
+ FILE_NAME,
165
+ ['next:page', 'next:page:type'].includes(CMD),
166
+ ['page:type', 'next:page:type'].includes(CMD))
167
+ } else console.warn('npm run page <page-name>')
168
+ break
169
+
170
+ case 'component':
171
+ case 'component:type':
172
+ if (FILE_NAME) createComponent(FILE_NAME, CMD === 'component:type')
173
+ else console.warn('npm run component <ComponentName>')
174
+ break
175
+
176
+ case 'container':
177
+ case 'container:type':
178
+ if (FILE_NAME) createContainer(FILE_NAME, CMD === 'component:type')
179
+ else console.warn('npm run container <ContainerName>')
180
+ break
181
+
182
+ default:
183
+ // create project
184
+ if (PROJECT_NAME) console.info(`Project name: ${PROJECT_NAME}`)
185
+ else console.warn('npx nextia@latest <my-app>')
186
+ break
187
+ }
package/src/lib.js ADDED
@@ -0,0 +1,223 @@
1
+ 'use client'
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/nextia
10
+ */
11
+
12
+ import { createContext, use, useReducer, useCallback } from 'react'
13
+
14
+ const Context = createContext()
15
+
16
+ /**
17
+ * css
18
+ */
19
+ function css (...classNames) {
20
+ classNames = classNames
21
+ .filter(e => e)
22
+ .reduce((accumulator, currentValue) => {
23
+ if (typeof currentValue === 'string') {
24
+ accumulator.push(currentValue)
25
+ } else if (!Array.isArray(currentValue) && typeof currentValue === 'object') {
26
+ for (const e in currentValue) {
27
+ if (currentValue[e]) accumulator.push(e)
28
+ }
29
+ }
30
+ return accumulator
31
+ }, [])
32
+
33
+ return ([...new Set(classNames)]).join(' ')
34
+ }
35
+
36
+ /**
37
+ * values
38
+ */
39
+ function values (state, payload, value) {
40
+ const paths = payload.split('.')
41
+
42
+ // one level
43
+ if (paths.length === 1) {
44
+ // set Object and exist Object
45
+ if (value && typeof value === 'object' && Object.keys(value).length) {
46
+ return { ...state, [payload]: { ...state[payload], ...value } }
47
+ }
48
+
49
+ // set Value
50
+ return {
51
+ ...state,
52
+ [payload]: value
53
+ }
54
+ }
55
+
56
+ // multi level
57
+ const stateClone = structuredClone(state)
58
+ const finalPath = paths.pop()
59
+ const stateRef = paths.reduce((ac, e) => ac[e], stateClone)
60
+ stateRef[finalPath] = value
61
+
62
+ return stateClone
63
+ }
64
+
65
+ /**
66
+ * merge
67
+ */
68
+ function merge (target, source) {
69
+ // in array return all source
70
+ if (Array.isArray(target)) return source
71
+
72
+ const isObject = obj => obj && typeof obj === 'object'
73
+ const output = { ...target }
74
+
75
+ // merge
76
+ Object.keys(source).forEach(key => {
77
+ if (isObject(target[key]) && isObject(source[key])) {
78
+ output[key] = merge(target[key], source[key])
79
+ } else output[key] = structuredClone(source[key])
80
+ })
81
+
82
+ return output
83
+ }
84
+
85
+ /**
86
+ * logger
87
+ */
88
+ const Logger = () => {
89
+ let instance
90
+
91
+ return {
92
+ getInstance: logger => {
93
+ if (!instance) {
94
+ instance = logger === 'true'
95
+ }
96
+ return instance
97
+ }
98
+ }
99
+ }
100
+
101
+ const logger = Logger().getInstance
102
+
103
+ const log = (reducer) => {
104
+ const getPayload = (action) => {
105
+ const { type, payload } = action
106
+
107
+ if (type === 'change') {
108
+ const { name, type, checked, value } = payload.target
109
+ return {
110
+ name, type, checked, value
111
+ }
112
+ }
113
+
114
+ if (typeof payload !== 'object') {
115
+ return `(${payload ?? ''})`
116
+ }
117
+
118
+ return payload
119
+ }
120
+
121
+ const reducerWithLogger = useCallback((state, action) => {
122
+ const newState = reducer(state, action)
123
+
124
+ console.log(
125
+ `%c${action.isContext ? 'Context' : 'Page '} : %c${action.type}`, 'color: #60a495', 'color: #7ee5cc',
126
+ getPayload(action), { a_State: state, b_NewState: newState }
127
+ )
128
+
129
+ return newState
130
+ }, [reducer])
131
+
132
+ return reducerWithLogger
133
+ }
134
+
135
+ /**
136
+ * reducer
137
+ */
138
+ const reducer = (state, action) => {
139
+ const { type, payload, initialState } = action
140
+
141
+ switch (type) {
142
+ case 'set':
143
+ // Merge only item
144
+ if (Object.keys(payload).length === 1) {
145
+ const key = Object.keys(payload)[0]
146
+ return values(state, key, payload[key])
147
+ }
148
+
149
+ // Merge all json
150
+ return merge(state, payload)
151
+
152
+ case 'show':
153
+ return values(state, payload, true)
154
+
155
+ case 'hide':
156
+ return values(state, payload, false)
157
+
158
+ case 'change':
159
+ return values(
160
+ state,
161
+ payload.target.name,
162
+ payload.target.type === 'checkbox' ? payload.target.checked : payload.target.value
163
+ )
164
+
165
+ case 'reset':
166
+ // value reset
167
+ if (payload) {
168
+ const paths = Array.isArray(payload) ? payload : [payload]
169
+
170
+ return paths.reduce((ac, path) => {
171
+ const value = path.split('.').reduce((ac, e) => ac[e], initialState)
172
+ return values(ac, path, value)
173
+ }, state)
174
+ }
175
+
176
+ // all reset
177
+ return initialState
178
+ }
179
+ }
180
+
181
+ /**
182
+ * useFx
183
+ */
184
+ function useFx (functions = { initialState: {} }) {
185
+ const context = use(Context)
186
+ const { initialState } = functions
187
+ const [state, dispatch] = useReducer(logger() ? log(reducer) : reducer, initialState)
188
+
189
+ // Common actions
190
+ const commonActions = ['set', 'show', 'hide', 'change', 'reset'].reduce((acc, e) => {
191
+ acc[e] = payload => dispatch({ type: e, payload, initialState, isContext: !context })
192
+ return acc
193
+ }, {})
194
+
195
+ // Actions
196
+ const actions = Object.keys(functions).reduce((ac, e) => {
197
+ if (functions[e] instanceof Function) {
198
+ ac[e] = payload => {
199
+ const props = {
200
+ ...commonActions,
201
+ state,
202
+ payload
203
+ }
204
+ if (context) { props.context = context }
205
+
206
+ return functions[e](Object.freeze(props))
207
+ }
208
+ }
209
+ return ac
210
+ }, {})
211
+
212
+ // return initialState, state, actions and context
213
+ const props = {
214
+ initialState,
215
+ state,
216
+ fx: { ...commonActions, ...actions }
217
+ }
218
+ if (context) { props.context = context }
219
+
220
+ return Object.freeze(props)
221
+ }
222
+
223
+ export { css, Context, logger, useFx }