@startupjs-ui/mdx 0.2.0 → 0.3.1

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/CHANGELOG.md CHANGED
@@ -3,6 +3,25 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [0.3.1](https://github.com/startupjs/startupjs-ui/compare/v0.3.0...v0.3.1) (2026-06-08)
7
+
8
+ **Note:** Version bump only for package @startupjs-ui/mdx
9
+
10
+
11
+
12
+
13
+
14
+ # [0.3.0](https://github.com/startupjs/startupjs-ui/compare/v0.2.3...v0.3.0) (2026-05-27)
15
+
16
+
17
+ ### Features
18
+
19
+ * [BREAKING] [0.3] improve accessibility props for E2E tests. Support testID everywhere ([#31](https://github.com/startupjs/startupjs-ui/issues/31)) ([882588c](https://github.com/startupjs/startupjs-ui/commit/882588ca37d5e1fd14b5717b5697cf9ed47042e4))
20
+
21
+
22
+
23
+
24
+
6
25
  # [0.2.0](https://github.com/startupjs/startupjs-ui/compare/v0.1.23...v0.2.0) (2026-05-04)
7
26
 
8
27
 
@@ -5,7 +5,10 @@ import Span from '@startupjs-ui/span'
5
5
  import ScrollView from '@startupjs-ui/scroll-view'
6
6
  import refractor from 'refractor/core.js'
7
7
  // Supported languages
8
+ import languageJavascript from 'refractor/lang/javascript.js'
8
9
  import languageJsx from 'refractor/lang/jsx.js'
10
+ import languageTypescript from 'refractor/lang/typescript.js'
11
+ import languageTsx from 'refractor/lang/tsx.js'
9
12
  import languageStyl from 'refractor/lang/stylus.js'
10
13
  import languagePug from 'refractor/lang/pug.js'
11
14
  import languageMarkdown from 'refractor/lang/markdown.js'
@@ -14,9 +17,13 @@ import languageBash from 'refractor/lang/bash.js'
14
17
  import './index.cssx.styl'
15
18
 
16
19
  const SUB_LANGUAGE_REGEX = /(^|\W)(pug|styl|css)(`\s*\n)([^`]*\s*\n)(`)/
20
+ const TEMPLATE_LANGUAGE_ROOTS = new Set(['js', 'javascript', 'jsx', 'ts', 'typescript', 'tsx'])
17
21
 
18
22
  // Register all supported languages
23
+ refractor.register(languageJavascript)
19
24
  refractor.register(languageJsx)
25
+ refractor.register(languageTypescript)
26
+ refractor.register(languageTsx)
20
27
  refractor.register(languageStyl)
21
28
  refractor.register(languagePug)
22
29
  refractor.register(languageMarkdown)
@@ -24,6 +31,7 @@ refractor.register(languageJson)
24
31
  refractor.register(languageBash)
25
32
 
26
33
  // Register aliases
34
+ refractor.alias({ javascript: ['js'] })
27
35
  refractor.alias({ stylus: ['styl'] })
28
36
  refractor.alias({ bash: ['sh'] })
29
37
 
@@ -74,27 +82,27 @@ function getLines (code, language) {
74
82
  }
75
83
 
76
84
  function highlight (code, language) {
77
- if (language === 'jsx') {
85
+ if (TEMPLATE_LANGUAGE_ROOTS.has(language)) {
78
86
  const match = code.match(SUB_LANGUAGE_REGEX)
79
87
  if (match) {
80
88
  const splitIndex = match.index + match[0].length
81
89
  const start = code.slice(0, splitIndex)
82
90
  const next = code.slice(splitIndex + 1)
83
91
 
84
- const jsx = start.replace(SUB_LANGUAGE_REGEX, '$1$2$3$5')
85
- const highlightedJsx = getLines(jsx, 'jsx')
92
+ const rootCode = start.replace(SUB_LANGUAGE_REGEX, '$1$2$3$5')
93
+ const highlightedRoot = getLines(rootCode, language)
86
94
 
87
95
  const subLanguageName = match[2]
88
96
  const bodySubLanguage = match[4]
89
- const closingBacktick = modifyAndGetLastBacktick(highlightedJsx)
97
+ const closingBacktick = modifyAndGetLastBacktick(highlightedRoot)
90
98
 
91
99
  const merge = [
92
- ...highlightedJsx, // without trailing ` sign
100
+ ...highlightedRoot, // without trailing ` sign
93
101
  ...highlight(bodySubLanguage, subLanguageName),
94
102
  closingBacktick // the trailing ` sign
95
103
  ]
96
104
 
97
- if (next) merge.push(...highlight(next, 'jsx'))
105
+ if (next) merge.push(...highlight(next, language))
98
106
  return merge
99
107
  }
100
108
  }
@@ -22,10 +22,16 @@ import Code from '../Code'
22
22
 
23
23
  // const RowComponent = props => pug`Div(...props row)`
24
24
  const ALPHABET = 'abcdefghigklmnopqrstuvwxyz'
25
+ const INLINE_COMPONENT = Symbol('startupjs.mdx.inlineComponent')
25
26
  const ListLevelContext = React.createContext()
26
27
  const BlockquoteContext = React.createContext()
27
28
  const PreContext = React.createContext()
28
29
 
30
+ function markInline (Component) {
31
+ Component[INLINE_COMPONENT] = true
32
+ return Component
33
+ }
34
+
29
35
  function getOrderedListMark (index, level) {
30
36
  switch (level) {
31
37
  case 1:
@@ -41,6 +47,130 @@ function P (props) {
41
47
  `
42
48
  }
43
49
 
50
+ const Strong = markInline(function Strong ({ children }) {
51
+ return pug`
52
+ Text(style={ fontWeight: 'bold' })= children
53
+ `
54
+ })
55
+
56
+ const Em = markInline(function Em ({ children }) {
57
+ return pug`
58
+ Text(style={ fontStyle: 'italic' })= children
59
+ `
60
+ })
61
+
62
+ const MdxLink = markInline(function MdxLink ({ children, href }) {
63
+ // function onPress (event) {
64
+ // const { url, hash } = $root.get('$render')
65
+ // const [_url, _hash] = href.split('#')
66
+ // if (url === _url && hash === `#${_hash}`) {
67
+ // event.preventDefault()
68
+ // // scrollTo({ anchorId: _hash })
69
+ // }
70
+ // }
71
+
72
+ // TODO: handle Anchor click with onPress
73
+ return pug`
74
+ Link.link(
75
+ to=href
76
+ size='l'
77
+ color='primary'
78
+ )= children
79
+ `
80
+ })
81
+
82
+ const MdxCode = markInline(observer(function MdxCode ({ children, className }) {
83
+ const isBlockCode = useContext(PreContext)
84
+ const language = (className || 'language-txt').replace(/language-/, '')
85
+ const [open, setOpen] = useState(false)
86
+ const $copyText = $('Copy code')
87
+
88
+ if (!isBlockCode) {
89
+ return pug`
90
+ Span.inlineCodeWrapper
91
+ Span.inlineCodeSpacer  
92
+ Span.inlineCode(style={
93
+ fontFamily: Platform.OS === 'ios' ? 'Menlo-Regular' : 'monospace'
94
+ })= children
95
+ Span.inlineCodeSpacer  
96
+ `
97
+ }
98
+
99
+ async function copyHandler () {
100
+ await setStringAsync(children)
101
+ $copyText.set('Copied')
102
+ }
103
+
104
+ function onMouseEnter () {
105
+ // we need to reutrn default text if it was copied
106
+ $copyText.set('Copy code')
107
+ }
108
+
109
+ let example
110
+
111
+ if (typeof children === 'string' && children.includes('[HACK EXAMPLE CODE]')) {
112
+ children = children.replace('[HACK EXAMPLE CODE]', '')
113
+ example = true
114
+ }
115
+
116
+ return pug`
117
+ Div.code(styleName={ 'code-example': example })
118
+ if example
119
+ Collapse.code-collapse(open=open variant='pure')
120
+ Collapse.Header.code-collapse-header(icon=false onPress=null)
121
+ Div.code-actions(align='right' row)
122
+ Div.code-action(
123
+ tooltip=open ? 'Hide code' : 'Show code'
124
+ onPress=() => setOpen(!open)
125
+ )
126
+ Icon.code-action-collapse(icon=faCode color='error')
127
+ Div.code-action(
128
+ tooltip=$copyText.get()
129
+ onPress=copyHandler
130
+ onMouseEnter=onMouseEnter
131
+ )
132
+ Icon.code-action-copy(icon=faCopy)
133
+ Collapse.Content.code-collapse-content
134
+ Code(language=language)= children
135
+ else
136
+ Code(language=language)= children
137
+ `
138
+ }))
139
+
140
+ function isInlineChild (child) {
141
+ if (typeof child === 'string' || typeof child === 'number') return true
142
+ if (!React.isValidElement(child)) return false
143
+ if (child.type === React.Fragment) {
144
+ return React.Children.toArray(child.props.children).every(isInlineChild)
145
+ }
146
+ return Boolean(child.type?.[INLINE_COMPONENT])
147
+ }
148
+
149
+ function wrapInlineListChildren (children) {
150
+ const wrappedChildren = []
151
+ let inlineChildren = []
152
+
153
+ function flushInlineChildren () {
154
+ if (!inlineChildren.length) return
155
+ wrappedChildren.push(pug`
156
+ P(key='inline-' + wrappedChildren.length size='l')= inlineChildren
157
+ `)
158
+ inlineChildren = []
159
+ }
160
+
161
+ children.forEach(child => {
162
+ if (isInlineChild(child)) {
163
+ inlineChildren.push(child)
164
+ } else {
165
+ flushInlineChildren()
166
+ wrappedChildren.push(child)
167
+ }
168
+ })
169
+
170
+ flushInlineChildren()
171
+ return wrappedChildren
172
+ }
173
+
44
174
  // function getTextChildren (children) {
45
175
  // const nestedChildren = _get(children, 'props.children')
46
176
  // if (nestedChildren) {
@@ -111,12 +241,8 @@ export default {
111
241
  P= children
112
242
  `
113
243
  },
114
- strong: ({ children }) => pug`
115
- Text(style={ fontWeight: 'bold' })= children
116
- `,
117
- em: ({ children }) => pug`
118
- Text(style={ fontStyle: 'italic' })= children
119
- `,
244
+ strong: Strong,
245
+ em: Em,
120
246
  hr: ({ children }) => pug`
121
247
  Divider(size='l')
122
248
  `,
@@ -138,25 +264,7 @@ export default {
138
264
  td: Td,
139
265
  th: Th,
140
266
  delete: P,
141
- a: ({ children, href }) => {
142
- // function onPress (event) {
143
- // const { url, hash } = $root.get('$render')
144
- // const [_url, _hash] = href.split('#')
145
- // if (url === _url && hash === `#${_hash}`) {
146
- // event.preventDefault()
147
- // // scrollTo({ anchorId: _hash })
148
- // }
149
- // }
150
-
151
- // TODO: handle Anchor click with onPress
152
- return pug`
153
- Link.link(
154
- to=href
155
- size='l'
156
- color='primary'
157
- )= children
158
- `
159
- },
267
+ a: MdxLink,
160
268
  ul: ({ children }) => children,
161
269
  ol: ({ children }) => {
162
270
  // eslint-disable-next-line react-hooks/rules-of-hooks
@@ -176,24 +284,15 @@ export default {
176
284
  // eslint-disable-next-line react-hooks/rules-of-hooks
177
285
  const level = useContext(ListLevelContext)
178
286
  const listIndex = index == null ? '•' : getOrderedListMark(index, level)
179
- let hasTextChild = false
180
287
  children = React.Children
181
288
  .toArray(children)
182
289
  .filter(child => child !== '\n')
183
- .map(child => {
184
- if (typeof child === 'string') {
185
- hasTextChild = true
186
- }
187
- return child
188
- })
290
+ const wrappedChildren = wrapInlineListChildren(children)
189
291
  return pug`
190
292
  Div(row)
191
293
  Span.listIndex= listIndex
192
294
  Div.listContent
193
- if hasTextChild
194
- P(size='l')= children
195
- else
196
- = children
295
+ = wrappedChildren
197
296
  `
198
297
  },
199
298
  blockquote: ({ children }) => {
@@ -258,62 +357,5 @@ export default {
258
357
  = children
259
358
  `
260
359
  },
261
- code: observer(({ children, className, ...props }) => {
262
- const isBlockCode = useContext(PreContext)
263
-
264
- if (!isBlockCode) {
265
- return pug`
266
- Span.inlineCodeWrapper
267
- Span.inlineCodeSpacer  
268
- Span.inlineCode(style={
269
- fontFamily: Platform.OS === 'ios' ? 'Menlo-Regular' : 'monospace'
270
- })= children
271
- Span.inlineCodeSpacer  
272
- `
273
- }
274
-
275
- const language = (className || 'language-txt').replace(/language-/, '')
276
- const [open, setOpen] = useState(false)
277
- const $copyText = $('Copy code')
278
-
279
- async function copyHandler () {
280
- await setStringAsync(children)
281
- $copyText.set('Copied')
282
- }
283
-
284
- function onMouseEnter () {
285
- // we need to reutrn default text if it was copied
286
- $copyText.set('Copy code')
287
- }
288
-
289
- let example
290
-
291
- if (typeof children === 'string' && children.includes('[HACK EXAMPLE CODE]')) {
292
- children = children.replace('[HACK EXAMPLE CODE]', '')
293
- example = true
294
- }
295
-
296
- return pug`
297
- Div.code(styleName={ 'code-example': example })
298
- if example
299
- Collapse.code-collapse(open=open variant='pure')
300
- Collapse.Header.code-collapse-header(icon=false onPress=null)
301
- Div.code-actions(align='right' row)
302
- Div.code-action(
303
- tooltip=open ? 'Hide code' : 'Show code'
304
- onPress=() => setOpen(!open)
305
- )
306
- Icon.code-action-collapse(icon=faCode color='error')
307
- Div.code-action(
308
- tooltip=$copyText.get()
309
- onPress=copyHandler
310
- onMouseEnter=onMouseEnter
311
- )
312
- Icon.code-action-copy(icon=faCopy)
313
- Collapse.Content.code-collapse-content
314
- Code(language=language)= children
315
- else
316
- Code(language=language)= children
317
- `
318
- })
360
+ code: MdxCode
319
361
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@startupjs-ui/mdx",
3
3
  "description": "MDX provider with a set of custom components for react-native support and syntax highlighting",
4
- "version": "0.2.0",
4
+ "version": "0.3.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -12,16 +12,16 @@
12
12
  "dependencies": {
13
13
  "@fortawesome/free-solid-svg-icons": "^7.1.0",
14
14
  "@mdx-js/react": "^3.0.0",
15
- "@startupjs-ui/alert": "^0.2.0",
16
- "@startupjs-ui/br": "^0.2.0",
17
- "@startupjs-ui/collapse": "^0.2.0",
18
- "@startupjs-ui/div": "^0.2.0",
19
- "@startupjs-ui/divider": "^0.2.0",
20
- "@startupjs-ui/icon": "^0.2.0",
21
- "@startupjs-ui/link": "^0.2.0",
22
- "@startupjs-ui/scroll-view": "^0.2.0",
23
- "@startupjs-ui/span": "^0.2.0",
24
- "@startupjs-ui/table": "^0.2.0",
15
+ "@startupjs-ui/alert": "^0.3.1",
16
+ "@startupjs-ui/br": "^0.3.0",
17
+ "@startupjs-ui/collapse": "^0.3.1",
18
+ "@startupjs-ui/div": "^0.3.1",
19
+ "@startupjs-ui/divider": "^0.3.0",
20
+ "@startupjs-ui/icon": "^0.3.0",
21
+ "@startupjs-ui/link": "^0.3.1",
22
+ "@startupjs-ui/scroll-view": "^0.3.0",
23
+ "@startupjs-ui/span": "^0.3.1",
24
+ "@startupjs-ui/table": "^0.3.1",
25
25
  "refractor": "^3.0.0"
26
26
  },
27
27
  "peerDependencies": {
@@ -30,5 +30,5 @@
30
30
  "react-native": "*",
31
31
  "startupjs": "*"
32
32
  },
33
- "gitHead": "0c586b841cba1c9d820542f6eca07470f5ea2659"
33
+ "gitHead": "60311773bdc83f354c797a272774304502d28c58"
34
34
  }