nextia 6.1.1 → 7.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.
Files changed (76) hide show
  1. package/README.md +1 -1
  2. package/biome.json +21 -0
  3. package/package.json +13 -4
  4. package/src/bin.js +240 -0
  5. package/src/lib.js +2 -2
  6. package/template/README.md +29 -0
  7. package/template/_env.dev +4 -0
  8. package/template/_env.prod +1 -0
  9. package/template/_env.test +1 -0
  10. package/template/_gitignore +10 -0
  11. package/template/package.json +35 -0
  12. package/template/public/error.html +14 -0
  13. package/template/public/logo.svg +105 -0
  14. package/template/src/assets/i18n/index.js +26 -0
  15. package/template/src/assets/img/image.jpg +0 -0
  16. package/template/src/components/Counter/index.jsx +34 -0
  17. package/template/src/components/Counter/style.css +5 -0
  18. package/template/src/components/Message/index.jsx +12 -0
  19. package/template/src/components/index.js +10 -0
  20. package/template/src/components/ui/I18n/index.jsx +23 -0
  21. package/template/src/components/ui/Icon/index.jsx +50 -0
  22. package/template/src/components/ui/Link/index.jsx +12 -0
  23. package/template/src/components/ui/Svg/index.jsx +54 -0
  24. package/template/src/components/ui/Translate/index.jsx +18 -0
  25. package/template/src/index.html +18 -0
  26. package/template/src/index.jsx +4 -0
  27. package/template/src/pages/counter/functions.js +6 -0
  28. package/template/src/pages/counter/index.jsx +51 -0
  29. package/template/src/pages/counter/style.css +2 -0
  30. package/template/src/pages/env/functions.js +3 -0
  31. package/template/src/pages/env/index.jsx +27 -0
  32. package/template/src/pages/env/style.css +2 -0
  33. package/template/src/pages/functions.js +37 -0
  34. package/template/src/pages/home/functions.js +43 -0
  35. package/template/src/pages/home/index.jsx +211 -0
  36. package/template/src/pages/home/style.css +51 -0
  37. package/template/src/pages/http/not-found/index.jsx +10 -0
  38. package/template/src/pages/http/not-found/style.css +2 -0
  39. package/template/src/pages/icons/functions.js +3 -0
  40. package/template/src/pages/icons/index.jsx +20 -0
  41. package/template/src/pages/icons/style.css +5 -0
  42. package/template/src/pages/images/functions.js +3 -0
  43. package/template/src/pages/images/index.jsx +20 -0
  44. package/template/src/pages/images/style.css +8 -0
  45. package/template/src/pages/index.jsx +114 -0
  46. package/template/src/pages/mockapi/functions.js +71 -0
  47. package/template/src/pages/mockapi/index.jsx +101 -0
  48. package/template/src/pages/mockapi/style.css +57 -0
  49. package/template/src/pages/my-context/functions.js +7 -0
  50. package/template/src/pages/my-context/index.jsx +32 -0
  51. package/template/src/pages/my-context/style.css +2 -0
  52. package/template/src/pages/resize/functions.js +3 -0
  53. package/template/src/pages/resize/index.jsx +15 -0
  54. package/template/src/pages/resize/style.css +2 -0
  55. package/template/src/pages/search-params/functions.js +3 -0
  56. package/template/src/pages/search-params/index.jsx +36 -0
  57. package/template/src/pages/search-params/style.css +2 -0
  58. package/template/src/pages/subpage/hello/functions.js +3 -0
  59. package/template/src/pages/subpage/hello/index.jsx +11 -0
  60. package/template/src/pages/subpage/hello/style.css +2 -0
  61. package/template/src/pages/translate/functions.js +5 -0
  62. package/template/src/pages/translate/index.jsx +31 -0
  63. package/template/src/pages/translate/style.css +12 -0
  64. package/template/src/services/api.js +9 -0
  65. package/template/src/services/http.js +40 -0
  66. package/template/src/theme/animations.css +72 -0
  67. package/template/src/theme/fonts/Roboto-Regular.ttf +0 -0
  68. package/template/src/theme/fonts/index.css +7 -0
  69. package/template/src/theme/icons/exit.svg +69 -0
  70. package/template/src/theme/icons/icons.svg +126 -0
  71. package/template/src/theme/index.css +39 -0
  72. package/template/src/theme/util.css +27 -0
  73. package/template/src/utils/hooks.js +49 -0
  74. package/template/src/utils/index.js +19 -0
  75. package/template/test/index.test.js +12 -0
  76. package/template/vite.config.js +97 -0
@@ -0,0 +1,114 @@
1
+ import { I18n, Icon, Link, Translate } from 'components'
2
+ import { PagesContext, useFx } from 'nextia'
3
+ import { lazy, useEffect, useRef, useState } from 'react'
4
+ import { startViewTransition, useQueryString, useResize } from 'utils'
5
+ import functions from './functions.js'
6
+
7
+ export default function Pages() {
8
+ const pagesContext = useFx(functions)
9
+ const { state, fx } = pagesContext
10
+
11
+ const [Page, setPage] = useState()
12
+ const qs = useQueryString()
13
+ const resize = useResize()
14
+ const ref = useRef()
15
+
16
+ useEffect(() => {
17
+ const hash = ['', '#/'].includes(qs.hash) ? '#/home' : qs.hash
18
+
19
+ const page = lazy(async () => {
20
+ const path = hash.substring(2).split('/')
21
+
22
+ try {
23
+ if (path.length === 1) {
24
+ return await import(`./${path[0]}/index.jsx`)
25
+ } else if (path.length === 2) {
26
+ return await import(`./${path[0]}/${path[1]}/index.jsx`)
27
+ }
28
+ } catch (e) {
29
+ console.error(e)
30
+ return await import('./http/not-found/index.jsx')
31
+ }
32
+ })
33
+
34
+ startViewTransition(setPage(page), ref.current)
35
+ }, [qs.hash])
36
+
37
+ return (
38
+ <PagesContext value={pagesContext}>
39
+ <header style={{ display: 'flex', gap: '20px' }}>
40
+ <Icon id="globe" width="24" />
41
+
42
+ <Translate value={state.i18n} onChange={fx.changeI18n} />
43
+
44
+ <I18n value="page.name" args={['Sinuhe', 'Maceda', 'Bouchan']} />
45
+
46
+ <button type="button" onClick={(e) => fx.increment(e)}>
47
+ increment
48
+ </button>
49
+ {' '}
50
+ <button type="button" onClick={(e) => fx.decrement(e)}>
51
+ decrement
52
+ </button>
53
+ {' '}
54
+ <button type="button" onClick={() => fx.zero({ value: 0 })}>
55
+ zero
56
+ </button>
57
+ {' '}
58
+ {state.num}
59
+ {' '}
60
+ {state.loading ? <span> Loading... </span> : <span> View.. </span>}
61
+ </header>
62
+
63
+ <aside className="m-2">
64
+ <Link href="/" className="mr-2">
65
+ /
66
+ </Link>
67
+ <Link href="#/" className="mr-2">
68
+ /home
69
+ </Link>
70
+ <Link href="#/env" className="mr-2">
71
+ /env
72
+ </Link>
73
+ <Link href="#/my-context" className="mr-2">
74
+ /my-context
75
+ </Link>
76
+ <Link href="#/mockapi" className="mr-2">
77
+ /mockapi
78
+ </Link>
79
+ <Link
80
+ href="#/search-params"
81
+ value={{ id: 1, user: 'Sinuhe' }}
82
+ className="mr-2"
83
+ >
84
+ /search-params
85
+ </Link>
86
+ <Link href="#/subpage/hello" className="mr-2">
87
+ /subpage/hello
88
+ </Link>
89
+ <Link href="#/translate" className="mr-2">
90
+ /translate
91
+ </Link>
92
+ <Link href="#/counter" className="mr-2">
93
+ /counter
94
+ </Link>
95
+ <Link href="#/images" className="mr-2">
96
+ /images
97
+ </Link>
98
+ <Link href="#/icons" className="mr-2">
99
+ /icons
100
+ </Link>
101
+ <Link href="#/resize" className="mr-2">
102
+ /resize
103
+ </Link>
104
+ <Link href="#/no" className="mr-2">
105
+ /no
106
+ </Link>
107
+ </aside>
108
+
109
+ <main ref={ref} className="m-2">
110
+ {Page && <Page qs={qs.queryString} resize={resize} />}
111
+ </main>
112
+ </PagesContext>
113
+ )
114
+ }
@@ -0,0 +1,71 @@
1
+ import { createUser, deleteUser, getUser, updateUser } from 'services/api'
2
+
3
+ const initialState = {
4
+ users: {},
5
+ user: {},
6
+ form: {
7
+ id: 0,
8
+ name: ''
9
+ }
10
+ }
11
+
12
+ async function handlerUser({ payload, set }) {
13
+ try {
14
+ const { data } = await getUser({ path: { id: payload } })
15
+ set({ users: data, user: {} })
16
+ } catch (e) {
17
+ console.error(e)
18
+ }
19
+ }
20
+
21
+ async function handlerCreateUser({ state, set }) {
22
+ try {
23
+ const { data } = await createUser({
24
+ body: {
25
+ username: state.form.name,
26
+ profile: {
27
+ firstName: state.form.name,
28
+ lastName: state.form.name
29
+ }
30
+ }
31
+ })
32
+ set({ users: {}, user: data })
33
+ } catch (e) {
34
+ console.error(e)
35
+ }
36
+ }
37
+
38
+ async function handlerUpdateUser({ payload, state, set }) {
39
+ try {
40
+ const { data } = await updateUser({
41
+ path: { id: payload },
42
+ body: {
43
+ username: state.form.name,
44
+ profile: {
45
+ firstName: state.form.name,
46
+ lastName: state.form.name
47
+ }
48
+ }
49
+ })
50
+ set({ users: {}, user: data })
51
+ } catch (e) {
52
+ console.error(e)
53
+ }
54
+ }
55
+
56
+ async function handlerDeleteUser({ payload }) {
57
+ try {
58
+ const { data } = await deleteUser({ path: { id: payload } })
59
+ console.info(data)
60
+ } catch (e) {
61
+ console.error(e)
62
+ }
63
+ }
64
+
65
+ export default {
66
+ initialState,
67
+ handlerUser,
68
+ handlerCreateUser,
69
+ handlerUpdateUser,
70
+ handlerDeleteUser
71
+ }
@@ -0,0 +1,101 @@
1
+ import { css, useFx } from 'nextia'
2
+ import functions from './functions'
3
+ import './style.css'
4
+
5
+ export default function MockapiPage() {
6
+ const { initialState, state, fx } = useFx(functions)
7
+
8
+ return (
9
+ <section className={css('MockapiPage', 'container')}>
10
+ <div>
11
+ <button type="button" onClick={() => fx.reset(['users', 'user'])}>
12
+ Reset
13
+ </button>
14
+
15
+ <button
16
+ type="button"
17
+ onClick={() => {
18
+ fx.reset(['users', 'user'])
19
+ fx.handlerUser()
20
+ }}
21
+ >
22
+ Get users
23
+ </button>
24
+
25
+ <button
26
+ type="button"
27
+ onClick={() => {
28
+ fx.reset(['users', 'user'])
29
+ fx.handlerUser(state.form.id)
30
+ }}
31
+ >
32
+ Get user
33
+ </button>
34
+
35
+ <button
36
+ type="button"
37
+ onClick={() => {
38
+ fx.reset(['users', 'user'])
39
+ fx.handlerCreateUser()
40
+ }}
41
+ >
42
+ Create user
43
+ </button>
44
+
45
+ <button
46
+ type="button"
47
+ onClick={() => {
48
+ fx.reset(['users', 'user'])
49
+ fx.handlerUpdateUser(state.form.id)
50
+ }}
51
+ >
52
+ Update user
53
+ </button>
54
+
55
+ <button
56
+ type="button"
57
+ onClick={() => {
58
+ fx.reset(['users', 'user'])
59
+ fx.handlerDeleteUser(state.form.id)
60
+ }}
61
+ >
62
+ Delete user
63
+ </button>
64
+ </div>
65
+
66
+ <div>
67
+ <h1>MockapiPage</h1>
68
+
69
+ <div>
70
+ id:{' '}
71
+ <input
72
+ type="text"
73
+ name="form.id"
74
+ value={state.form.id}
75
+ onChange={(evt) => fx.change(evt)}
76
+ />
77
+ </div>
78
+ <div>
79
+ name:{' '}
80
+ <input
81
+ type="text"
82
+ name="form.name"
83
+ value={state.form.name}
84
+ onChange={(evt) => fx.change(evt)}
85
+ />
86
+ </div>
87
+ </div>
88
+
89
+ <section>
90
+ <article style={{ display: 'flex' }}>
91
+ <pre style={{ margin: '0 50px 0 50px', width: '250px' }}>
92
+ state = {JSON.stringify(state, undefined, 2)}
93
+ </pre>
94
+ <pre style={{ margin: '0 5px 0 5px', width: '250px' }}>
95
+ initialState = {JSON.stringify(initialState, undefined, 2)}
96
+ </pre>
97
+ </article>
98
+ </section>
99
+ </section>
100
+ )
101
+ }
@@ -0,0 +1,57 @@
1
+ .MockapiPage {
2
+ background-color: #21252b;
3
+
4
+ &.container {
5
+ display: flex;
6
+ gap: 20px;
7
+ padding: 20px;
8
+
9
+ div {
10
+ width: 300px;
11
+ }
12
+ }
13
+
14
+ p {
15
+ padding-left: 25px;
16
+ margin: 0px;
17
+ }
18
+
19
+ pre {
20
+ color: gray;
21
+ font-size: 12px;
22
+ max-height: 700px;
23
+ overflow: auto;
24
+ }
25
+
26
+ button {
27
+ margin: 10px;
28
+ height: 50px;
29
+ width: 180px;
30
+ }
31
+
32
+ input[type="text"] {
33
+ margin: 20px;
34
+ height: 30px;
35
+ width: 180px;
36
+ }
37
+
38
+ input[type="checkbox"] {
39
+ height: 30px;
40
+ width: 30px;
41
+ }
42
+
43
+ input[type="radio"] {
44
+ height: 30px;
45
+ width: 30px;
46
+ }
47
+
48
+ select {
49
+ margin-left: 40px;
50
+ height: 30px;
51
+ width: 180px;
52
+ }
53
+
54
+ .my-css {
55
+ font-size: 20px;
56
+ }
57
+ }
@@ -0,0 +1,7 @@
1
+ const initialState = {}
2
+
3
+ function init({ context }) {
4
+ context.fx.set({ num: 300 })
5
+ }
6
+
7
+ export default { initialState, init }
@@ -0,0 +1,32 @@
1
+ import { css, useFx } from 'nextia'
2
+ import { useEffect } from 'react'
3
+ import functions from './functions'
4
+ import './style.css'
5
+
6
+ export default function MyContext() {
7
+ const { state, fx, context } = useFx(functions)
8
+
9
+ // biome-ignore lint/correctness/useExhaustiveDependencies: fx is stable and init should only run once
10
+ useEffect(() => {
11
+ fx.init()
12
+ }, [])
13
+
14
+ return (
15
+ <section className={css('MyContext', '')}>
16
+ <br />
17
+ <button type="button" onClick={(e) => context.fx.increment(e)}>
18
+ increment
19
+ </button>
20
+ {' '}
21
+ <button type="button" onClick={(e) => context.fx.decrement(e)}>
22
+ decrement
23
+ </button>
24
+ {' '}
25
+ <button type="button" onClick={() => context.fx.zero({ value: 0 })}>
26
+ zero
27
+ </button>
28
+ {' '}
29
+ {context.state.num}
30
+ </section>
31
+ )
32
+ }
@@ -0,0 +1,2 @@
1
+ .MyContext {
2
+ }
@@ -0,0 +1,3 @@
1
+ const initialState = {}
2
+
3
+ export default { initialState }
@@ -0,0 +1,15 @@
1
+ import { css, useFx } from 'nextia'
2
+ import functions from './functions'
3
+ import './style.css'
4
+
5
+ export default function ResizePage({ resize }) {
6
+ const { state, fx } = useFx(functions)
7
+
8
+ return (
9
+ <section className={css('ResizePage', '')}>
10
+ <pre style={{ margin: '0 50px 0 50px', width: '250px' }}>
11
+ {JSON.stringify(resize, undefined, 2)}
12
+ </pre>
13
+ </section>
14
+ )
15
+ }
@@ -0,0 +1,2 @@
1
+ .ResizePage {
2
+ }
@@ -0,0 +1,3 @@
1
+ const initialState = {}
2
+
3
+ export default { initialState }
@@ -0,0 +1,36 @@
1
+ import { Link } from 'components'
2
+ import { css, useFx } from 'nextia'
3
+ import { useEffect } from 'react'
4
+ import functions from './functions'
5
+ import './style.css'
6
+
7
+ export default function SearchParamsPage({ qs }) {
8
+ const { state, fx } = useFx(functions)
9
+
10
+ useEffect(() => {
11
+ console.info(qs)
12
+ }, [qs])
13
+
14
+ return (
15
+ <section className={css('SearchParamsPage', '')}>
16
+ <br />
17
+
18
+ <Link href="/">Go to href=/</Link>
19
+
20
+ <br />
21
+
22
+ <Link value={{ id: 2, user: 'Maceda' }}>Link id=2 user=Maceda </Link>
23
+
24
+ <br />
25
+
26
+ <Link value={{ id: 1, user: 'Sinuhe' }}>Link id=1 user=Sinuhe</Link>
27
+
28
+ <br />
29
+ <br />
30
+
31
+ <pre style={{ margin: '0 50px 0 50px' }}>
32
+ {JSON.stringify(qs, undefined, 2)}
33
+ </pre>
34
+ </section>
35
+ )
36
+ }
@@ -0,0 +1,2 @@
1
+ .SearchParamsPage {
2
+ }
@@ -0,0 +1,3 @@
1
+ const initialState = {}
2
+
3
+ export default { initialState }
@@ -0,0 +1,11 @@
1
+ import { css, useFx } from 'nextia'
2
+ import functions from './functions'
3
+ import './style.css'
4
+
5
+ export default function SubpageHelloPage() {
6
+ const { state, fx } = useFx(functions)
7
+
8
+ return (
9
+ <section className={css('SubpageHelloPage', '')}>/subpage/hello</section>
10
+ )
11
+ }
@@ -0,0 +1,2 @@
1
+ .SubpageHelloPage {
2
+ }
@@ -0,0 +1,5 @@
1
+ const initialState = {}
2
+
3
+ export default {
4
+ initialState
5
+ }
@@ -0,0 +1,31 @@
1
+ import { css, useFx } from 'nextia'
2
+ import functions from './functions'
3
+ import './style.css'
4
+ import { I18n, Message } from 'components'
5
+
6
+ export default function TranslatePage() {
7
+ const { state, fx } = useFx(functions)
8
+
9
+ return (
10
+ <section className={css('TranslatePage', '')}>
11
+ <I18n value="page.name" args={['Sinuhe', 'Maceda', 'Bouchan']} />
12
+
13
+ <ul>
14
+ <li>
15
+ <I18n value="ui.ok" />
16
+ </li>
17
+ <li>
18
+ <I18n value="ui.back" />
19
+ </li>
20
+ <li>
21
+ <I18n value="page.user.family" />
22
+ </li>
23
+ <li>
24
+ <I18n value="page.module.block.docker" />
25
+ </li>
26
+ </ul>
27
+
28
+ <Message />
29
+ </section>
30
+ )
31
+ }
@@ -0,0 +1,12 @@
1
+ .TranslatePage {
2
+ margin: 20px;
3
+
4
+ h3 {
5
+ color: gray;
6
+ }
7
+
8
+ button {
9
+ width: 40px;
10
+ margin: 15px;
11
+ }
12
+ }
@@ -0,0 +1,9 @@
1
+ import { env } from 'utils'
2
+ import { DELETE, GET, POST, PUT } from './http'
3
+
4
+ const API = env.PUBLIC_API
5
+
6
+ export const getUser = (p) => GET(`${API}/user/:id`, p.path, p.body)
7
+ export const createUser = (p) => POST(`${API}/user`, p.path, p.body)
8
+ export const updateUser = (p) => PUT(`${API}/user/:id`, p.path, p.body)
9
+ export const deleteUser = (p) => DELETE(`${API}/user/:id`, p.path, p.body)
@@ -0,0 +1,40 @@
1
+ function urlTemplate(url, path) {
2
+ const params = url.match(/:\w+/g) ?? []
3
+
4
+ return params.reduce((acc, e) => {
5
+ const key = e.split(':')[1]
6
+ return acc.replaceAll(e, path[key] ?? '')
7
+ }, url)
8
+ }
9
+
10
+ async function request(method, url, path, body, headers) {
11
+ url = urlTemplate(url, path)
12
+
13
+ if (method === 'GET') {
14
+ url += Object.keys(body).length ? `?${new URLSearchParams(body)}` : ''
15
+ }
16
+
17
+ const response = await fetch(url, {
18
+ method,
19
+ headers: { 'Content-Type': 'application/json', ...headers },
20
+ ...(method !== 'GET' && {
21
+ body: JSON.stringify(body)
22
+ })
23
+ })
24
+
25
+ const { ok, status, statusText } = response
26
+
27
+ if (ok) return { ok, status, statusText, data: await response.json() }
28
+
29
+ throw new Error(`ok: ${ok} , status: ${status} , statusText: ${statusText}`)
30
+ }
31
+
32
+ const factory =
33
+ (method) =>
34
+ (url = '', path = {}, body = {}, headers = {}) =>
35
+ request(method, url, path, body, headers)
36
+
37
+ export const GET = factory('GET')
38
+ export const POST = factory('POST')
39
+ export const PUT = factory('PUT')
40
+ export const DELETE = factory('DELETE')
@@ -0,0 +1,72 @@
1
+ :root {
2
+ view-transition-name: none;
3
+ }
4
+
5
+ /* fade */
6
+
7
+ @keyframes fade-in {
8
+ from {
9
+ opacity: 0;
10
+ }
11
+ to {
12
+ opacity: 1;
13
+ }
14
+ }
15
+ @keyframes fade-out {
16
+ from {
17
+ opacity: 1;
18
+ }
19
+ to {
20
+ opacity: 0;
21
+ }
22
+ }
23
+
24
+ ::view-transition-old(fade) {
25
+ animation: fade-out ease 0.3s forwards;
26
+ }
27
+
28
+ ::view-transition-new(fade) {
29
+ animation: fade-in ease 0.3s forwards;
30
+ }
31
+
32
+ /* count */
33
+
34
+ @keyframes count-in {
35
+ from {
36
+ scale: 1 0;
37
+ }
38
+ to {
39
+ scale: 1 1;
40
+ }
41
+ }
42
+
43
+ @keyframes count-out {
44
+ from {
45
+ scale: 1 1;
46
+ }
47
+ to {
48
+ scale: 1 0;
49
+ }
50
+ }
51
+
52
+ ::view-transition-new(count) {
53
+ animation: count-in 0.25s forwards;
54
+ transform-origin: 50% 0;
55
+ }
56
+
57
+ ::view-transition-old(count) {
58
+ animation: count-out 0.25s forwards;
59
+ transform-origin: 50% 100%;
60
+ }
61
+
62
+ /* count2 */
63
+
64
+ ::view-transition-new(count2) {
65
+ animation: count-in 0.25s forwards;
66
+ transform-origin: 10% 100%;
67
+ }
68
+
69
+ ::view-transition-old(count2) {
70
+ animation: count-out 0.25s forwards;
71
+ transform-origin: 10% 0%;
72
+ }
@@ -0,0 +1,7 @@
1
+ @font-face {
2
+ font-family: Roboto;
3
+ font-style: normal;
4
+ font-display: swap;
5
+ font-weight: bold;
6
+ src: url("./Roboto-Regular.ttf");
7
+ }