nextia 7.0.0 → 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.
Files changed (41) hide show
  1. package/package.json +9 -13
  2. package/src/bin.js +1 -2
  3. package/src/{lib.js → lib/fx.js} +16 -31
  4. package/src/lib/index.js +32 -0
  5. package/src/lib/ui.js +112 -0
  6. package/src/lib/utils.js +107 -0
  7. package/template/biome.json +21 -0
  8. package/template/package.json +3 -3
  9. package/template/src/assets/img/image.svg +6 -0
  10. package/template/src/assets/img/image.webp +0 -0
  11. package/template/src/components/Counter/index.jsx +1 -2
  12. package/template/src/components/Message/index.jsx +1 -1
  13. package/template/src/components/index.js +1 -5
  14. package/template/src/components/ui/Translate/index.jsx +10 -8
  15. package/template/src/pages/env/index.jsx +1 -2
  16. package/template/src/pages/icons/index.jsx +10 -9
  17. package/template/src/pages/icons/style.css +3 -0
  18. package/template/src/pages/images/index.jsx +10 -5
  19. package/template/src/pages/images/style.css +21 -2
  20. package/template/src/pages/index.jsx +22 -12
  21. package/template/src/pages/search-params/index.jsx +1 -2
  22. package/template/src/pages/translate/index.jsx +2 -2
  23. package/template/src/pages/{counter → view-transition}/index.jsx +2 -23
  24. package/template/src/pages/view-transition/style.css +2 -0
  25. package/template/src/services/api.js +1 -1
  26. package/template/src/theme/icons/icons.svg +3 -4
  27. package/template/src/theme/icons/index.css +55 -0
  28. package/template/src/theme/index.css +2 -2
  29. package/template/src/theme/{util.css → utils/index.css} +2 -0
  30. package/template/src/utils/index.js +1 -15
  31. package/template/test/index.test.js +2 -3
  32. package/template/src/assets/img/image.jpg +0 -0
  33. package/template/src/components/ui/I18n/index.jsx +0 -23
  34. package/template/src/components/ui/Icon/index.jsx +0 -50
  35. package/template/src/components/ui/Link/index.jsx +0 -12
  36. package/template/src/components/ui/Svg/index.jsx +0 -54
  37. package/template/src/pages/counter/style.css +0 -2
  38. package/template/src/theme/icons/exit.svg +0 -69
  39. package/template/src/utils/hooks.js +0 -49
  40. /package/template/src/pages/{counter → view-transition}/functions.js +0 -0
  41. /package/template/src/theme/{animations.css → utils/view-transition.css} +0 -0
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "nextia",
3
3
  "description": "Create fast web applications",
4
- "version": "7.0.0",
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",
@@ -23,7 +23,9 @@
23
23
  "bin": {
24
24
  "nextia": "src/bin.js"
25
25
  },
26
- "main": "src/lib.js",
26
+ "exports": {
27
+ ".": "./src/lib/index.js"
28
+ },
27
29
  "scripts": {
28
30
  "clean": "rm -fr my-app node_modules package-lock.json .coverage out",
29
31
  "format": "biome format",
@@ -32,13 +34,7 @@
32
34
  "test": "vitest run",
33
35
  "test:name": "vitest run --testNamePattern",
34
36
  "test:silent": "vitest run --silent",
35
- "test:coverage": "vitest run --silent --coverage",
36
- "test:version": "./src/bin.js",
37
- "test:my-app": "rm -fr my-app && src/bin.js my-app",
38
- "test:my-app:exists": "src/bin.js my-app",
39
- "test:my-page": "cd my-app && ../src/bin.js page my-page",
40
- "test:my-component": "cd my-app && ../src/bin.js component MyComponent",
41
- "test:my-container": "cd my-app && ../src/bin.js container MyContainer"
37
+ "test:coverage": "vitest run --silent --coverage"
42
38
  },
43
39
  "peerDependencies": {
44
40
  "react": "^19.2.3",
@@ -46,8 +42,8 @@
46
42
  },
47
43
  "devDependencies": {
48
44
  "@vitejs/plugin-react": "^6.0.1",
49
- "@vitest/coverage-v8": "^4.0.18",
45
+ "@vitest/coverage-v8": "^4.1.2",
50
46
  "jsdom": "^29.0.1",
51
- "vitest": "^4.0.18"
47
+ "vitest": "^4.1.2"
52
48
  }
53
49
  }
package/src/bin.js CHANGED
@@ -190,7 +190,6 @@ async function createProject(name) {
190
190
  }
191
191
 
192
192
  await cp(templatePath, projectPath, { recursive: true })
193
- await cp(`${__dirname}/../biome.json`, `${projectPath}/biome.json`)
194
193
 
195
194
  await Promise.all(
196
195
  ['env.dev', 'env.prod', 'env.test', 'gitignore'].map((fileName) =>
@@ -230,7 +229,7 @@ async function main() {
230
229
 
231
230
  default:
232
231
  if (ARG1) await createProject(ARG1)
233
- else console.info(`v${pkg.version}\nnpm create nextia <ProjectName>`)
232
+ else console.info(`v${pkg.version}\nnextia <ProjectName>`)
234
233
  break
235
234
  }
236
235
  }
@@ -10,31 +10,12 @@
10
10
  import { createContext, use, useReducer } from 'react'
11
11
 
12
12
  const LOGGER = import.meta.env.DEV && import.meta.env.PUBLIC_LOGGER !== 'false'
13
- const PagesContext = createContext()
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,7 +147,7 @@ const reducerLogger = (state, action) => {
166
147
  */
167
148
 
168
149
  function useFx(functions = { initialState: {} }) {
169
- const pageContext = use(PagesContext)
150
+ const pagesFx = use(PagesFx)
170
151
  const { initialState } = functions
171
152
  const [state, dispatch] = useReducer(
172
153
  LOGGER ? reducerLogger : reducer,
@@ -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({ type: e, payload, initialState, isContext: !pageContext })
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
- if (pageContext) {
212
- props.context = pageContext
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 { css, PagesContext, useFx }
203
+ export { PagesFx, useFx }
@@ -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 }
@@ -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,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
+ }
@@ -22,10 +22,10 @@
22
22
  },
23
23
  "devDependencies": {
24
24
  "@vitejs/plugin-react": "^6.0.1",
25
- "@vitest/coverage-v8": "^4.0.18",
25
+ "@vitest/coverage-v8": "^4.1.2",
26
26
  "jsdom": "^29.0.1",
27
- "vite": "^8.0.1",
28
- "vitest": "^4.0.18"
27
+ "vite": "^8.0.3",
28
+ "vitest": "^4.1.2"
29
29
  },
30
30
  "dependencies": {
31
31
  "nextia": "file:../",
@@ -0,0 +1,6 @@
1
+ <svg width="256" height="256" viewBox="0 0 48 48" strokeWidth="1" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
2
+ <defs id="defs" />
3
+ <path id="background" d="m 6.9596e-8,0 h 50.155103930404003 v 48 h -50.155103930404003 z" fill="#f7df1e" />
4
+ <path id="label"
5
+ d="m 14.776102,40.11225 3.673125,-2.223 c 0.70875,1.256438 1.353375,2.319562 2.899688,2.319562 1.482186,0 2.416875,-0.57975 2.416875,-2.835 v -15.337125 h 4.510687 v 15.400875 c 0,4.671937 -2.738625,6.798562 -6.73425,6.798562 -3.608437,0 -5.703001,-1.868812 -6.766313,-4.124249 m 15.950625,-0.483 3.67275,-2.126438 c 0.966938,1.578938 2.223563,2.738813 4.446562,2.738813 1.869189,0 3.060938,-0.934501 3.060938,-2.223375 0,-1.5465 -1.224375,-2.094376 -3.286499,-2.99625 l -1.127439,-0.48375 c -3.254437,-1.385062 -5.413124,-3.125063 -5.413124,-6.798188 0,-3.38325 2.577562,-5.961 6.605249,-5.961 2.867626,0 4.929751,0.999001 6.411751,3.608813 l -3.51225,2.255625 c -0.773438,-1.385438 -1.610813,-1.933125 -2.899688,-1.933125 -1.321124,0 -2.158875,0.83775 -2.158875,1.933125 0,1.353187 0.837751,1.90125 2.770875,2.739 l 1.127625,0.483187 c 3.834375,1.643438 5.993063,3.318751 5.993063,7.088251 0,4.060124 -3.18975,6.283124 -7.475063,6.283124 -4.188562,0 -6.895125,-1.997625 -8.216062,-4.607625" />
6
+ </svg>
@@ -1,7 +1,6 @@
1
1
  import { useRef } from 'react'
2
2
  import './style.css'
3
- import { css } from 'nextia'
4
- import { startViewTransition } from 'utils'
3
+ import { css, startViewTransition } from 'nextia'
5
4
 
6
5
  export default function Counter({
7
6
  name,
@@ -1,4 +1,4 @@
1
- import { I18n } from 'components'
1
+ import { I18n } from 'nextia'
2
2
 
3
3
  export default function Messages({ name, className, style }) {
4
4
  return (
@@ -1,10 +1,6 @@
1
1
  import Counter from './Counter'
2
2
  import Message from './Message'
3
3
  // ui
4
- import I18n from './ui/I18n'
5
- import Icon from './ui/Icon'
6
- import Link from './ui/Link'
7
- import Svg from './ui/Svg'
8
4
  import Translate from './ui/Translate'
9
5
 
10
- export { Counter, I18n, Icon, Link, Message, Svg, Translate }
6
+ export { Counter, Message, Translate }
@@ -1,13 +1,15 @@
1
- export default function UiTranslate({
2
- value,
3
- onChange = () => {},
4
- className,
5
- style
6
- }) {
1
+ import { useFx } from 'nextia'
2
+
3
+ export default function UiTranslate({ className, style }) {
4
+ const { context } = useFx()
5
+
6
+ const { currentLocale, locales } = context.state.i18n
7
+ const { changeI18n } = context.fx
8
+
7
9
  return (
8
10
  <article className={className} style={style}>
9
- <select value={value.currentLocale} onChange={onChange}>
10
- {value.locales.map((e) => (
11
+ <select value={currentLocale} onChange={changeI18n}>
12
+ {locales.map((e) => (
11
13
  <option key={e} value={e} className="m-2">
12
14
  {e}
13
15
  </option>
@@ -1,6 +1,5 @@
1
- import { css, useFx } from 'nextia'
1
+ import { css, env, useFx } from 'nextia'
2
2
  import { useEffect } from 'react'
3
- import { env } from 'utils'
4
3
  import functions from './functions'
5
4
  import './style.css'
6
5
 
@@ -1,20 +1,21 @@
1
- import { Icon, Svg } from 'components'
2
- import { css, useFx } from 'nextia'
1
+ import { css, Icon, useFx } from 'nextia'
3
2
  import functions from './functions'
4
3
  import './style.css'
5
- import exitSvg from 'theme/icons/exit.svg?raw'
6
4
 
7
5
  export default function IconsPage() {
8
6
  const { state, fx } = useFx(functions)
9
7
 
10
8
  return (
11
9
  <section className={css('IconsPage', '')}>
12
- <Icon id="globe" width="32" />
13
- <Icon id="camera" width="32" />
14
- <Icon id="video" width="32" />
15
- <br />
16
- <br />
17
- <Svg src={exitSvg} width="32" />
10
+ <article>
11
+ <Icon id="globe" width="32" />
12
+ <Icon id="camera" width="32" />
13
+ <Icon id="video" width="32" />
14
+ </article>
15
+
16
+ <article>
17
+ <Icon id="exit" animate width="32" />
18
+ </article>
18
19
  </section>
19
20
  )
20
21
  }
@@ -1,4 +1,7 @@
1
1
  .IconsPage {
2
+ display: flex;
3
+ justify-content: space-around;
4
+
2
5
  svg {
3
6
  margin-right: 10px;
4
7
  }
@@ -1,20 +1,25 @@
1
- import image from 'assets/img/image.jpg'
2
- import { css, useFx } from 'nextia'
1
+ import image from 'assets/img/image.webp'
2
+ import { css, Svg, useFx } from 'nextia'
3
3
  import functions from './functions'
4
4
  import './style.css'
5
+ import imageSvg from 'assets/img/image.svg?raw'
5
6
 
6
7
  export default function ImagesPage() {
7
8
  const { state, fx } = useFx(functions)
8
9
 
9
10
  return (
10
11
  <section className={css('ImagesPage', '')}>
11
- <br />
12
12
  <p>css-img</p>
13
13
  <div className="css-img" />
14
14
 
15
- <br />
16
15
  <p>img</p>
17
- <img src={image} alt="img" height={200} />
16
+ <img src={image} alt="img" width="64" />
17
+
18
+ <p>svg</p>
19
+ <Svg src={imageSvg} width="64" />
20
+
21
+ <p>svg+css</p>
22
+ <Svg className="svg-css" src={imageSvg} width="64" />
18
23
  </section>
19
24
  )
20
25
  }
@@ -1,8 +1,27 @@
1
1
  .ImagesPage {
2
+ margin-left: 50px;
3
+
2
4
  .css-img {
3
- background: url("assets/img/image.jpg") no-repeat;
4
- height: 200px;
5
+ background: url("assets/img/image.webp") no-repeat;
6
+ height: 64px;
5
7
  background-repeat: no-repeat;
6
8
  background-size: contain;
7
9
  }
10
+
11
+ .svg-css {
12
+ #label {
13
+ animation: svg-css_arrow 2s ease-in-out infinite;
14
+ }
15
+ }
16
+ }
17
+
18
+ @keyframes svg-css_arrow {
19
+ 0%,
20
+ 100% {
21
+ transform: translateY(-2px);
22
+ }
23
+
24
+ 50% {
25
+ transform: translateY(2px);
26
+ }
8
27
  }
@@ -1,12 +1,22 @@
1
- import { I18n, Icon, Link, Translate } from 'components'
2
- import { PagesContext, useFx } from 'nextia'
1
+ import i18nFile from 'assets/i18n'
2
+ import { Translate } from 'components'
3
+ import {
4
+ I18n,
5
+ Icon,
6
+ Link,
7
+ PagesFx,
8
+ startViewTransition,
9
+ useFx,
10
+ useQueryString,
11
+ useResize
12
+ } from 'nextia'
3
13
  import { lazy, useEffect, useRef, useState } from 'react'
4
- import { startViewTransition, useQueryString, useResize } from 'utils'
14
+ import iconsFile from 'theme/icons/icons.svg?raw'
5
15
  import functions from './functions.js'
6
16
 
7
17
  export default function Pages() {
8
- const pagesContext = useFx(functions)
9
- const { state, fx } = pagesContext
18
+ const pages = useFx(functions)
19
+ const { state, fx } = pages
10
20
 
11
21
  const [Page, setPage] = useState()
12
22
  const qs = useQueryString()
@@ -35,11 +45,11 @@ export default function Pages() {
35
45
  }, [qs.hash])
36
46
 
37
47
  return (
38
- <PagesContext value={pagesContext}>
48
+ <PagesFx value={{ context: pages, icons: iconsFile, i18n: i18nFile }}>
39
49
  <header style={{ display: 'flex', gap: '20px' }}>
40
50
  <Icon id="globe" width="24" />
41
51
 
42
- <Translate value={state.i18n} onChange={fx.changeI18n} />
52
+ <Translate />
43
53
 
44
54
  <I18n value="page.name" args={['Sinuhe', 'Maceda', 'Bouchan']} />
45
55
 
@@ -89,8 +99,8 @@ export default function Pages() {
89
99
  <Link href="#/translate" className="mr-2">
90
100
  /translate
91
101
  </Link>
92
- <Link href="#/counter" className="mr-2">
93
- /counter
102
+ <Link href="#/view-transition" className="mr-2">
103
+ /view-transition
94
104
  </Link>
95
105
  <Link href="#/images" className="mr-2">
96
106
  /images
@@ -101,14 +111,14 @@ export default function Pages() {
101
111
  <Link href="#/resize" className="mr-2">
102
112
  /resize
103
113
  </Link>
104
- <Link href="#/no" className="mr-2">
105
- /no
114
+ <Link href="#/dashboard" className="mr-2">
115
+ /not-found
106
116
  </Link>
107
117
  </aside>
108
118
 
109
119
  <main ref={ref} className="m-2">
110
120
  {Page && <Page qs={qs.queryString} resize={resize} />}
111
121
  </main>
112
- </PagesContext>
122
+ </PagesFx>
113
123
  )
114
124
  }
@@ -1,5 +1,4 @@
1
- import { Link } from 'components'
2
- import { css, useFx } from 'nextia'
1
+ import { css, Link, useFx } from 'nextia'
3
2
  import { useEffect } from 'react'
4
3
  import functions from './functions'
5
4
  import './style.css'
@@ -1,7 +1,7 @@
1
- import { css, useFx } from 'nextia'
1
+ import { css, I18n, useFx } from 'nextia'
2
2
  import functions from './functions'
3
3
  import './style.css'
4
- import { I18n, Message } from 'components'
4
+ import { Message } from 'components'
5
5
 
6
6
  export default function TranslatePage() {
7
7
  const { state, fx } = useFx(functions)
@@ -3,33 +3,12 @@ import { css, useFx } from 'nextia'
3
3
  import functions from './functions'
4
4
  import './style.css'
5
5
 
6
- export default function CounterPage() {
6
+ export default function ViewTransitionPage() {
7
7
  const { state, fx } = useFx(functions)
8
8
 
9
9
  return (
10
- <section
11
- className={css(
12
- 'CounterPage',
13
- '',
14
- 'class-test',
15
- 'class-test',
16
- {},
17
- null,
18
- true,
19
- false,
20
- [],
21
- { 'css-false': false },
22
- undefined,
23
- { 'css-true': true },
24
- { 'css-true': true }
25
- )}
26
- >
10
+ <section className={css('ViewTransitionPage')}>
27
11
  Counters
28
- <div className={css(null)} />
29
- <div className={css(undefined)} />
30
- <div className={css([])} />
31
- <div className={css({})} />
32
- <div className={css()} />
33
12
  <Counter
34
13
  value={state.count}
35
14
  animation="count"
@@ -0,0 +1,2 @@
1
+ .ViewTransitionPage {
2
+ }
@@ -1,4 +1,4 @@
1
- import { env } from 'utils'
1
+ import { env } from 'nextia'
2
2
  import { DELETE, GET, POST, PUT } from './http'
3
3
 
4
4
  const API = env.PUBLIC_API
@@ -16,12 +16,11 @@
16
16
  inkscape:export-filename="icons.svg"
17
17
  inkscape:export-xdpi="96"
18
18
  inkscape:export-ydpi="96"
19
+ aria-hidden="true"
19
20
  xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
20
21
  xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
21
22
  xmlns="http://www.w3.org/2000/svg"
22
23
  xmlns:svg="http://www.w3.org/2000/svg">
23
- <title
24
- id="title">icons</title>
25
24
  <sodipodi:namedview
26
25
  id="namedview"
27
26
  pagecolor="#505050"
@@ -96,10 +95,10 @@
96
95
  style="display:none"
97
96
  sodipodi:insensitive="true">
98
97
  <path
99
- id="solid"
98
+ id="background"
100
99
  d="M 12,3.4 C 5.8,7.2 1.8,14 1.8,22 1.8,35 12,45 24,45 36,45 46,35 46,22 46,14 42,7.2 36,3.4" />
101
100
  <path
102
- id="animation"
101
+ id="ring"
103
102
  d="M 12,3.4 C 5.8,7.2 1.8,14 1.8,22 1.8,35 12,45 24,45 36,45 46,35 46,22 46,14 42,7.2 36,3.4" />
104
103
  <g
105
104
  id="arrow">
@@ -0,0 +1,55 @@
1
+ #exit.nextia-animate-icon {
2
+ transition: transform 200ms linear;
3
+
4
+ #ring {
5
+ stroke-dasharray: 1, 200;
6
+ stroke-dashoffset: 1;
7
+ transition:
8
+ stroke-dashoffset 400ms linear,
9
+ stroke-dasharray 400ms linear;
10
+ }
11
+
12
+ #arrow {
13
+ animation: exit_arrow 2s ease-in-out infinite;
14
+
15
+ #right,
16
+ #left {
17
+ display: none;
18
+ }
19
+ }
20
+
21
+ &:hover {
22
+ transform: rotate(-90deg);
23
+
24
+ #ring {
25
+ stroke-dasharray: 200, 1;
26
+ stroke: var(--primary-color);
27
+ stroke-width: 4px;
28
+ }
29
+
30
+ #arrow {
31
+ transform: translateY(2px);
32
+ stroke: var(--primary-color);
33
+
34
+ line {
35
+ stroke-width: 4px;
36
+ }
37
+
38
+ #right,
39
+ #left {
40
+ display: block;
41
+ }
42
+ }
43
+ }
44
+ }
45
+
46
+ @keyframes exit_arrow {
47
+ 0%,
48
+ 100% {
49
+ transform: translateY(2px);
50
+ }
51
+
52
+ 50% {
53
+ transform: translateY(6px);
54
+ }
55
+ }
@@ -1,6 +1,6 @@
1
- @import "./util.css";
2
1
  @import "./fonts/index.css";
3
- @import "./animations.css";
2
+ @import "./icons/index.css";
3
+ @import "./utils/index.css";
4
4
 
5
5
  :root {
6
6
  --primary-color: #344188;
@@ -1,3 +1,5 @@
1
+ @import "./view-transition.css";
2
+
1
3
  /**
2
4
  * Scrollbar
3
5
  */
@@ -1,19 +1,5 @@
1
- import { flushSync } from 'react-dom'
2
- import { useQueryString, useResize } from './hooks'
3
-
4
- const env = import.meta.env
5
-
6
- async function startViewTransition(fun = () => {}, ref, animation = 'fade') {
7
- if (!document.startViewTransition || env.PUBLIC_VIEW_TRANSITION === 'false')
8
- return fun()
9
-
10
- ref.style.viewTransitionName = animation
11
- await document.startViewTransition(() => flushSync(fun)).finished
12
- ref.style.viewTransitionName = ''
13
- }
14
-
15
1
  function sum(a, b) {
16
2
  return a + b
17
3
  }
18
4
 
19
- export { env, startViewTransition, sum, useQueryString, useResize }
5
+ export { sum }
@@ -1,8 +1,7 @@
1
- import { env, sum } from 'utils'
1
+ import { env } from 'nextia'
2
+ import { sum } from 'utils'
2
3
  import { test } from 'vitest'
3
4
 
4
- test('test', () => {})
5
-
6
5
  test('sumTest', () => {
7
6
  console.info(sum(1, 20))
8
7
  })
Binary file
@@ -1,23 +0,0 @@
1
- import i18nFile from 'assets/i18n'
2
- import { useFx } from 'nextia'
3
-
4
- export default function UiI18n({ value, args = [] }) {
5
- const { context } = useFx()
6
-
7
- try {
8
- let text = value.split('.').reduce((ac, el) => ac[el], i18nFile)
9
- text = text[i18nFile.locales.indexOf(context.state.i18n.currentLocale)]
10
-
11
- if (args) {
12
- text = text.replace(
13
- /([{}])\\1|[{](.*?)(?:!(.+?))?[}]/g,
14
- (match, _literal, number) => args[number] || match
15
- )
16
- }
17
-
18
- return text
19
- } catch {
20
- console.error(`Error in [il8n] => ${value}`)
21
- return value
22
- }
23
- }
@@ -1,50 +0,0 @@
1
- import { useEffect, useRef } from 'react'
2
- import icons from 'theme/icons/icons.svg?raw'
3
-
4
- export default function UiIcon({
5
- id,
6
- className,
7
- style,
8
- width = '48',
9
- height,
10
- viewBox = '0 0 48 48',
11
- fill = 'none',
12
- color = 'currentColor',
13
- stroke = 'currentColor',
14
- strokeWidth = '2',
15
- strokeLinecap = 'round',
16
- strokeLinejoin = 'round',
17
- ...props
18
- }) {
19
- const ref = useRef()
20
-
21
- useEffect(() => {
22
- const svg = new DOMParser()
23
- .parseFromString(icons, 'image/svg+xml')
24
- .documentElement.getElementById(id)
25
-
26
- if (svg) {
27
- ref.current.innerHTML = svg.innerHTML
28
- }
29
- }, [id])
30
-
31
- return (
32
- <svg
33
- xmlns="http://www.w3.org/2000/svg"
34
- ref={ref}
35
- id={id}
36
- className={className}
37
- style={style}
38
- width={width}
39
- height={height}
40
- viewBox={viewBox}
41
- fill={fill}
42
- color={color}
43
- stroke={stroke}
44
- strokeWidth={strokeWidth}
45
- strokeLinecap={strokeLinecap}
46
- strokeLinejoin={strokeLinejoin}
47
- {...props}
48
- />
49
- )
50
- }
@@ -1,12 +0,0 @@
1
- export default function UiLink({ children, href, value = {}, ...props }) {
2
- href ??= window.location.hash.split('?')[0]
3
- value = Object.keys(value).length
4
- ? `?${new URLSearchParams(value).toString()}`
5
- : ''
6
-
7
- return (
8
- <a href={href + value} {...props}>
9
- {children}
10
- </a>
11
- )
12
- }
@@ -1,54 +0,0 @@
1
- import { useEffect, useRef } from 'react'
2
-
3
- export default function UiSvg({
4
- src,
5
- width = '48',
6
- height,
7
- viewBox = '0 0 48 48',
8
- fill = 'none',
9
- color = 'currentColor',
10
- stroke = 'currentColor',
11
- strokeWidth = '2',
12
- strokeLinecap = 'round',
13
- strokeLinejoin = 'round'
14
- }) {
15
- const ref = useRef()
16
-
17
- useEffect(() => {
18
- if (!ref.current) return
19
-
20
- const svg = new DOMParser()
21
- .parseFromString(src, 'image/svg+xml')
22
- .querySelector('svg')
23
-
24
- svg.setAttribute('width', width)
25
- svg.setAttribute('height', height ?? width)
26
- svg.setAttribute('viewBox', viewBox)
27
- svg.setAttribute('fill', fill)
28
- svg.setAttribute('color', color)
29
- svg.setAttribute('stroke', stroke)
30
- svg.setAttribute('stroke-width', strokeWidth)
31
- svg.setAttribute('stroke-linecap', strokeLinecap)
32
- svg.setAttribute('stroke-linejoin', strokeLinejoin)
33
-
34
- const shadow =
35
- ref.current.shadowRoot ?? ref.current.attachShadow({ mode: 'open' })
36
- shadow.innerHTML = svg.outerHTML
37
-
38
- ref.current.style.width = `${width}px`
39
- ref.current.style.height = `${height ?? width}px`
40
- }, [
41
- src,
42
- width,
43
- height,
44
- viewBox,
45
- fill,
46
- color,
47
- stroke,
48
- strokeWidth,
49
- strokeLinecap,
50
- strokeLinejoin
51
- ])
52
-
53
- return <div ref={ref} />
54
- }
@@ -1,2 +0,0 @@
1
- .CounterPage {
2
- }
@@ -1,69 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="256" height="256" viewBox="0 0 48 48" fill="none"
2
- color="currentColor" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
3
-
4
- <style type="text/css">
5
- svg {
6
- transition: transform 200ms linear;
7
-
8
- #ani {
9
- transition:
10
- stroke-dashoffset 400ms linear,
11
- stroke-dasharray 400ms linear;
12
- stroke: var(--primary-color);
13
- stroke-dasharray: 1, 200;
14
- stroke-dashoffset: 1;
15
- }
16
-
17
- #arrow {
18
- animation: arrow 2s ease-in-out infinite;
19
-
20
- #right,
21
- #left {
22
- display: none;
23
- }
24
- }
25
- }
26
-
27
- svg:hover {
28
- transform: rotate(-90deg);
29
-
30
- #ani {
31
- stroke-dasharray: 200, 1;
32
- }
33
-
34
- #arrow {
35
- transform: translateY(2px);
36
- stroke: var(--primary-color);
37
-
38
- line {
39
- stroke-width: 6px;
40
- }
41
-
42
- #right,
43
- #left {
44
- display: block;
45
- }
46
- }
47
- }
48
-
49
- @keyframes arrow {
50
-
51
- 0%,
52
- 100% {
53
- transform: translateY(2px);
54
- }
55
-
56
- 50% {
57
- transform: translateY(6px);
58
- }
59
- }
60
- </style>
61
-
62
- <path id="solid" d="M 12,3.4 C 5.8,7.2 1.8,14 1.8,22 1.8,35 12,45 24,45 36,45 46,35 46,22 46,14 42,7.2 36,3.4" />
63
- <path id="ani" d="M 12,3.4 C 5.8,7.2 1.8,14 1.8,22 1.8,35 12,45 24,45 36,45 46,35 46,22 46,14 42,7.2 36,3.4" />
64
- <g id="arrow">
65
- <line id="left" x1="24" y1="25" x2="10" y2="14" />
66
- <line id="center" x1="24" y1="1.3" x2="24" y2="25" />
67
- <line id="right" x1="24" y1="25" x2="35" y2="14" />
68
- </g>
69
- </svg>
@@ -1,49 +0,0 @@
1
- import { useCallback, useEffect, useState } from 'react'
2
-
3
- export function useQueryString() {
4
- const getQueryString = useCallback(
5
- () => ({
6
- hash: window.location.hash.split('?')[0],
7
- queryString: Object.fromEntries(
8
- new URLSearchParams(window.location.hash.split('?')[1])
9
- )
10
- }),
11
- []
12
- )
13
-
14
- const [queryString, setQueryString] = useState(getQueryString)
15
-
16
- useEffect(() => {
17
- const handlePopState = () => setQueryString(getQueryString())
18
-
19
- window.addEventListener('popstate', handlePopState)
20
- return () => {
21
- window.removeEventListener('popstate', handlePopState)
22
- }
23
- }, [getQueryString])
24
-
25
- return queryString
26
- }
27
-
28
- export function useResize() {
29
- const getResize = useCallback(
30
- () => ({
31
- width: window.innerWidth,
32
- height: window.innerHeight
33
- }),
34
- []
35
- )
36
-
37
- const [resize, setResize] = useState(getResize)
38
-
39
- useEffect(() => {
40
- const handleResize = () => setResize(getResize())
41
-
42
- window.addEventListener('resize', handleResize)
43
- return () => {
44
- window.removeEventListener('resize', handleResize)
45
- }
46
- }, [getResize])
47
-
48
- return resize
49
- }