@meonode/ui 0.1.73 → 0.1.74

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 (2) hide show
  1. package/README.md +30 -467
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
  [![Bundle Size](https://img.shields.io/bundlephobia/minzip/@meonode/ui)](https://bundlephobia.com/package/@meonode/ui)
6
6
 
7
- **Build React UIs with Type-Safe Fluency**
7
+ **Build React UIs with Type-Safe Fluency**
8
8
  A structured approach to component composition with built-in theming, prop separation, and dynamic children handling.
9
9
 
10
10
  ```tsx
@@ -117,10 +117,10 @@ const ThemedCard = Component(() =>
117
117
  backgroundColor: 'theme.colors.primary',
118
118
  children: [
119
119
  H1('Themed Title', { color: 'theme.colors.text.primary' }),
120
- P('Content...', { color: 'theme.colors.text.secondary' })
120
+ P('Content...', { color: 'theme.colors.text.secondary' })
121
121
  ]
122
122
  })
123
- );
123
+ )
124
124
  ```
125
125
 
126
126
  ### 3. Prop Handling
@@ -128,20 +128,24 @@ const ThemedCard = Component(() =>
128
128
  Automatic separation of CSS props from DOM attributes:
129
129
 
130
130
  ```tsx
131
- const ProfileCard = Component(({ user }) =>
131
+ type User = {
132
+ name: string
133
+ }
134
+
135
+ const ProfileCard = Component<{ user: User }>(({ user }) =>
132
136
  Div({
133
137
  // CSS Props
134
138
  padding: '20px',
135
139
  borderRadius: '8px',
136
-
140
+
137
141
  // DOM Props
138
142
  'aria-role': 'article',
139
143
  tabIndex: 0,
140
-
144
+
141
145
  // Children
142
146
  children: `Welcome ${user.name}!`
143
147
  })
144
- );
148
+ )
145
149
  ```
146
150
 
147
151
  ---
@@ -158,463 +162,6 @@ const ProfileCard = Component(({ user }) =>
158
162
 
159
163
  ---
160
164
 
161
- ## Advanced Patterns
162
-
163
- ### Component Composition
164
-
165
- ```tsx
166
- const Dashboard = Component(() =>
167
- Div({
168
- display: 'grid',
169
- gridTemplateColumns: '1fr 3fr',
170
- gap: '20px',
171
- children: [
172
- Sidebar({ width: '240px' }),
173
- MainContent({
174
- padding: '40px',
175
- children: AnalyticsChart({ dataset })
176
- })
177
- ]
178
- })
179
- );
180
- ```
181
-
182
- ### Material UI Integration
183
-
184
- ```bash
185
- yarn add @meonode/mui @mui/material
186
- ```
187
-
188
- ```tsx
189
- import { Button, TextField } from '@meonode/mui';
190
-
191
- const LoginForm = Component(() =>
192
- Div({
193
- maxWidth: '400px',
194
- margin: '0 auto',
195
- children: [
196
- TextField({ label: 'Email', fullWidth: true }),
197
- TextField({ label: 'Password', type: 'password' }),
198
- Button({
199
- variant: 'contained',
200
- children: 'Sign In'
201
- })
202
- ]
203
- })
204
- );
205
- ```
206
-
207
- ## Comprehensive Example: Theme-Switching & Conditional Components
208
-
209
- ```tsx
210
- 'use client'
211
- /**
212
- * This file showcases the integration of React hooks with `@meonode/ui` components
213
- * for building declarative user interfaces. It demonstrates different rendering
214
- * approaches, the use of Higher-Order Components (HOCs), and how theme context
215
- * is managed and propagated within the @meonode/ui component tree.
216
- */
217
- import {
218
- Button,
219
- Center,
220
- Column,
221
- Component,
222
- Fixed,
223
- Node,
224
- type NodeInstance,
225
- P,
226
- Portal,
227
- Row,
228
- type Theme
229
- } from '@meonode/ui'
230
- import { useState, useEffect, ReactElement, ReactNode } from 'react'
231
- import { CssBaseline, FormControlLabel, TextField } from '@meonode/mui'
232
- import { Switch as MUISwitch } from '@mui/material'
233
- import { styled } from '@mui/material'
234
-
235
- // Styled Material UI Switch component for theme toggling
236
- const MaterialUISwitch = styled(MUISwitch)(({ theme }) => ({
237
- width: 62,
238
- height: 34,
239
- padding: 7,
240
- '& .MuiSwitch-switchBase': {
241
- margin: 1,
242
- padding: 0,
243
- transform: 'translateX(6px)',
244
- '&.Mui-checked': {
245
- color: '#fff',
246
- transform: 'translateX(22px)',
247
- '& .MuiSwitch-thumb:before': {
248
- backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(
249
- '#fff',
250
- )}" d="M4.2 2.5l-.7 1.8-1.8.7 1.8.7.7 1.8.6-1.8L6.7 5l-1.9-.7-.6-1.8zm15 8.3a6.7 6.7 0 11-6.6-6.6 5.8 5.8 0 006.6 6.6z"/></svg>')`,
251
- },
252
- '& + .MuiSwitch-track': {
253
- opacity: 1,
254
- backgroundColor: '#aab4be',
255
- ...theme.applyStyles('dark', {
256
- backgroundColor: '#8796A5',
257
- }),
258
- },
259
- },
260
- },
261
- '& .MuiSwitch-thumb': {
262
- backgroundColor: '#001e3c',
263
- width: 32,
264
- height: 32,
265
- '&::before': {
266
- content: "''",
267
- position: 'absolute',
268
- width: '100%',
269
- height: '100%',
270
- left: 0,
271
- top: 0,
272
- backgroundRepeat: 'no-repeat',
273
- backgroundPosition: 'center',
274
- backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(
275
- '#fff',
276
- )}" d="M9.305 1.667V3.75h1.389V1.667h-1.39zm-4.707 1.95l-.982.982L5.09 6.072l.982-.982-1.473-1.473zm10.802 0L13.927 5.09l.982.982 1.473-1.473-.982-.982zM10 5.139a4.872 4.872 0 00-4.862 4.86A4.872 4.872 0 0010 14.862 4.872 4.872 0 0014.86 10 4.872 4.872 0 0010 5.139zm0 1.389A3.462 3.462 0 0113.471 10a3.462 3.462 0 01-3.473 3.472A3.462 3.462 0 016.527 10 3.462 3.462 0 0110 6.528zM1.665 9.305v1.39h2.083v-1.39H1.666zm14.583 0v1.39h2.084v-1.39h-2.084zM5.09 13.928L3.616 15.4l.982.982 1.473-1.473-.982-.982zm9.82 0l-.982.982 1.473 1.473.982-.982-1.473-1.473zM9.305 16.25v2.083h1.389V16.25h-1.39z"/></svg>')`,
277
- },
278
- ...theme.applyStyles('dark', {
279
- backgroundColor: '#003892',
280
- }),
281
- },
282
- '& .MuiSwitch-track': {
283
- opacity: 1,
284
- backgroundColor: '#aab4be',
285
- borderRadius: 20 / 2,
286
- ...theme.applyStyles('dark', {
287
- backgroundColor: '#8796A5',
288
- }),
289
- },
290
- }))
291
-
292
- /**
293
- * Light theme configuration containing color palette values.
294
- * Used by `@meonode/ui` components when resolving theme references in light mode.
295
- */
296
- const lightTheme: Theme = {
297
- mode: 'light',
298
- colors: {
299
- primary: '#2563eb',
300
- secondary: '#64748b',
301
- accent: '#10b981',
302
- background: '#ffffff',
303
- foreground: '#0f172a',
304
- border: '#e2e8f0',
305
- muted: '#f8fafc',
306
- success: '#16a34a',
307
- warning: '#eab308',
308
- danger: '#dc2626',
309
- },
310
- }
311
-
312
- /**
313
- * Dark theme configuration containing color palette values.
314
- * Used by `@meonode/ui` components when resolving theme references in dark mode.
315
- */
316
- const darkTheme: Theme = {
317
- mode: 'dark',
318
- colors: {
319
- primary: '#3b82f6',
320
- secondary: '#94a3b8',
321
- accent: '#34d399',
322
- background: '#0f172a',
323
- foreground: '#f8fafc',
324
- border: '#334155',
325
- muted: '#1e293b',
326
- success: '#22c55e',
327
- warning: '#facc15',
328
- danger: '#ef4444',
329
- },
330
- }
331
-
332
- /**
333
- * Main page component using React hooks for state management.
334
- * Manages theme mode and visibility of additional content sections.
335
- * Wrapped by `Component` HOC to ensure React compatibility and SSR/CSR support.
336
- */
337
- export default Component(() => {
338
- const [showMore, setShowDetails] = useState(false)
339
- const [mode, setMode] = useState<'dark' | 'light'>('light')
340
- const theme = mode === 'dark' ? darkTheme : lightTheme
341
-
342
- return Column({
343
- theme: theme.colors,
344
- padding: 20,
345
- gap: 15,
346
- minHeight: '100vh',
347
- backgroundColor: 'theme.background',
348
- color: 'theme.foreground',
349
- children: [
350
- CssBaseline,
351
- Center({
352
- children: FormControlLabel({
353
- control: Node(MaterialUISwitch).render() as ReactElement,
354
- alignItems: 'center',
355
- label: mode === 'dark' ? 'Dark Mode' : 'Light Mode',
356
- labelPlacement: 'start',
357
- checked: mode === 'dark',
358
- onChange: () => setMode(prev => (prev === 'dark' ? 'light' : 'dark')),
359
- }),
360
- }),
361
- Button('Show Modal', {
362
- onClick: () => Modal({ theme: theme.colors }),
363
- cursor: 'pointer',
364
- userSelect: 'none',
365
- padding: '10px 20px',
366
- backgroundColor: 'theme.primary',
367
- borderRadius: 5,
368
- fontWeight: 'bold',
369
- color: 'white',
370
- }),
371
- Button(showMore ? 'Hide Details' : 'Show More Details', {
372
- onClick: () => setShowDetails(prev => !prev),
373
- cursor: 'pointer',
374
- userSelect: 'none',
375
- padding: '10px 20px',
376
- backgroundColor: 'theme.accent',
377
- borderRadius: 5,
378
- fontWeight: 'bold',
379
- color: 'white',
380
- }),
381
-
382
- /**
383
- * Component rendering examples demonstrating theme context propagation:
384
- * - Direct Node instance rendering
385
- * - Rendered Node instances
386
- * - ReactNode components
387
- * - HOC usage patterns
388
- */
389
- DetailComponent({
390
- info: 'Detail 1: Rendering a component that returns a @meonode/ui Node instance directly. The internal Row component correctly receives theme context from the parent Column.',
391
- }),
392
-
393
- DetailComponent({
394
- info: 'Detail 2: Rendering a component that returns a @meonode/ui Node instance, then calling .render(). The internal Row component also correctly receives theme context.',
395
- }).render(),
396
-
397
- // ❌ Fails: The Node HOC expects the wrapped function to return a ReactNode, not a @meonode/ui Node instance.
398
- // Node(DetailComponent, { info: 'Detail 3: Attempting to wrap a component returning a Node instance with Node HOC.' }),
399
-
400
- ReturnRenderedDetailComponent({
401
- info: 'Detail 4: Rendering a component that explicitly returns a ReactNode (.render() is called internally). The internal Row component correctly receives theme context from the parent Column.',
402
- }),
403
-
404
- Node(ReturnRenderedDetailComponent, {
405
- info: "Detail 5: Wrapping a component returning ReactNode with Node HOC (without .render()). Renders successfully. However, the Node HOC does NOT propagate theme context to the wrapped component's children.",
406
- }),
407
-
408
- Node(ReturnRenderedDetailComponent, {
409
- info: 'Detail 6: Wrapping a component returning ReactNode with Node HOC, then calling .render(). Renders successfully. Theme context is NOT propagated by the Node HOC.',
410
- }).render(),
411
-
412
- WrappedDetailComponent({
413
- info: 'Detail 7: Using Component HOC with Node instance returns. Theme context is correctly propagated.',
414
- }),
415
-
416
- /**
417
- * Conditional rendering examples (visible when showMore is true)
418
- * Demonstrates:
419
- * - Inline function wrappers
420
- * - Component HOC usage
421
- * - Theme context propagation patterns
422
- */
423
- showMore &&
424
- (() =>
425
- DetailComponent({
426
- info: 'Detail 8: Conditional rendering of a Node instance component using inline function wrapper. Theme context is correctly received.',
427
- })),
428
-
429
- showMore &&
430
- (() =>
431
- DetailComponent({
432
- info: 'Detail 9: Conditional rendering of a Node instance component with .render() call. Theme context is correctly propagated.',
433
- }).render()),
434
-
435
- showMore &&
436
- WrappedDetailComponent({
437
- info: 'Detail 10: Conditional rendering using Component HOC with Node instance. Theme context is correctly propagated.',
438
- }),
439
-
440
- showMore &&
441
- (() =>
442
- ReturnRenderedDetailComponent({
443
- info: 'Detail 11: Conditional rendering of ReactNode component using inline function. Theme context is properly propagated.',
444
- })),
445
-
446
- showMore &&
447
- Component(() =>
448
- ReturnRenderedDetailComponent({
449
- info: 'Detail 12: Conditional rendering with Component HOC wrapping ReactNode component. Note that theme context is not propagated in this case.',
450
- }),
451
- ),
452
-
453
- // showMore && ReturnRenderedDetailComponent({
454
- // info: 'Detail 15: Direct component call violates Rules of Hooks!'
455
- // }), // ❌ Fails: Direct call to a component function using hooks inside render logic without a React-aware wrapper.
456
- ],
457
- })
458
- })
459
-
460
- /**
461
- * Styled detail section component returning a Node instance.
462
- * Uses useEffect for lifecycle logging.
463
- * Theme context is received from parent @meonode/ui components.
464
- */
465
- const DetailComponent = ({ info }: { info: string }): NodeInstance => {
466
- useEffect(() => {
467
- console.log('DetailComponent mounted:', info)
468
- return () => {
469
- console.log('DetailComponent unmounted:', info)
470
- }
471
- }, [info])
472
-
473
- return Row({
474
- alignItems: 'center',
475
- gap: 10,
476
- padding: 4,
477
- border: '2px solid theme.accent',
478
- borderRadius: 6,
479
- backgroundColor: 'theme.warning',
480
- color: 'theme.danger',
481
- children: [P(info, { flex: 1, padding: '0 20px' }), TextField({ flex: 1, sx: { background: 'theme.primary' } })],
482
- })
483
- }
484
-
485
- /**
486
- * Styled detail section component wrapped with Component HOC.
487
- * Similar to DetailComponent but demonstrates HOC pattern.
488
- * Theme context is correctly propagated through the HOC.
489
- */
490
- const WrappedDetailComponent = Component(({ info }): NodeInstance => {
491
- useEffect(() => {
492
- console.log('DetailComponent mounted')
493
- return () => {
494
- console.log('DetailComponent unmounted')
495
- }
496
- }, [info])
497
-
498
- return Row({
499
- alignItems: 'center',
500
- gap: 10,
501
- padding: 4,
502
- border: '2px solid theme.accent',
503
- borderRadius: 6,
504
- backgroundColor: 'theme.warning',
505
- color: 'theme.danger',
506
- children: [P(info, { flex: 1, padding: '0 20px' }), TextField({ flex: 1, sx: { background: 'theme.primary' } })],
507
- })
508
- })
509
-
510
- /**
511
- * Alternative detail component implementation returning ReactNode.
512
- * Explicitly calls .render() for React compatibility.
513
- * Demonstrates theme context usage with standard React patterns.
514
- */
515
- const ReturnRenderedDetailComponent = ({ info }: { info: string }): ReactNode => {
516
- useEffect(() => {
517
- console.log('ReturnRenderedDetailComponent mounted')
518
- return () => {
519
- console.log('ReturnRenderedDetailComponent unmounted')
520
- }
521
- }, [info])
522
-
523
- return Row({
524
- alignItems: 'center',
525
- gap: 10,
526
- padding: 4,
527
- border: '2px solid theme.accent',
528
- borderRadius: 6,
529
- backgroundColor: 'theme.warning',
530
- color: 'theme.danger',
531
- children: [P(info, { flex: 1, padding: '0 20px' }), TextField({ flex: 1, sx: { background: 'theme.primary' } })],
532
- }).render()
533
- }
534
-
535
- /**
536
- * Modal component using Portal HOC for DOM placement.
537
- * Demonstrates theme-aware content rendering outside main hierarchy.
538
- * Includes nested modal support and Material UI integration.
539
- */
540
- const Modal = Portal(({ theme, portal }) => {
541
- useEffect(() => {
542
- console.log('Modal mounted')
543
- return () => {
544
- console.log('Modal unmounted')
545
- }
546
- }, [])
547
-
548
- return Fixed({
549
- theme,
550
- top: 0,
551
- left: 0,
552
- right: 0,
553
- bottom: 0,
554
- display: 'flex',
555
- backgroundColor: 'rgba(0, 0, 0, 0.5)',
556
- justifyContent: 'center',
557
- alignItems: 'center',
558
- onClick: e => {
559
- if (e.target === e.currentTarget) {
560
- portal.unmount()
561
- }
562
- },
563
- children: [
564
- Column({
565
- width: '50%',
566
- height: '80%',
567
- backgroundColor: 'theme.background',
568
- borderRadius: '8px',
569
- boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
570
- padding: 10,
571
- gap: 10,
572
- color: 'theme.foreground',
573
- children: [
574
- Button('More Modal', {
575
- onClick: () => Modal({ theme }),
576
- cursor: 'pointer',
577
- userSelect: 'none',
578
- padding: '10px 20px',
579
- backgroundColor: 'theme.primary',
580
- borderRadius: 5,
581
- fontWeight: 'bold',
582
- color: 'white',
583
- }),
584
- Center({ fontWeight: 'bold', children: 'Modal' }),
585
- Center({ children: Math.random() * 1000 }),
586
- TextField({
587
- sx: {
588
- '& .MuiFormLabel-root': {
589
- color: 'theme.foreground',
590
- '&.Mui-focused': {
591
- color: 'theme.foreground',
592
- },
593
- },
594
- '& .MuiOutlinedInput-root': {
595
- color: 'theme.foreground',
596
- '& fieldset': {
597
- borderColor: 'theme.foreground',
598
- },
599
- '&:hover fieldset': {
600
- borderColor: 'theme.foreground',
601
- },
602
- '&.Mui-focused fieldset': {
603
- borderColor: 'theme.foreground',
604
- },
605
- borderRadius: 2,
606
- },
607
- },
608
- label: 'Hello',
609
- fullWidth: true,
610
- }),
611
- ],
612
- }),
613
- ],
614
- })
615
- })
616
- ```
617
-
618
165
  ## Passing Context Wrapper To Portal
619
166
  ```ts
620
167
  import { Provider, useSelector } from 'react-redux'
@@ -664,12 +211,28 @@ const Modal = Portal(ReduxProvider, ({ portal }) => {
664
211
  console.log('Redux State value: ', someReduxState)
665
212
  }, [])
666
213
 
667
- // ...
214
+ return Div({
215
+ padding: 10,
216
+ backgroundColor: 'white',
217
+ boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
218
+ children: [
219
+ P('Content...', { color: 'theme.colors.text.secondary' }),
220
+ Button({ children: 'Close', onClick: () => portal.unmount() })
221
+ ]
222
+ })
668
223
  })
669
224
  ```
670
225
 
671
226
  ---
672
227
 
228
+ ## Repo Example Usage
229
+
230
+ This section provides a practical example of how to integrate `@meonode/ui` within a Next.js application. The linked repository showcases proper theme handling, especially when utilizing Redux with a preloaded state, and demonstrates its usage within a Server Component (RootLayout) environment. Crucially, it also illustrates how to effectively manage **conditional components that contain React hooks**, providing a robust pattern for dynamic UI rendering. This example is particularly useful for understanding how to set up a robust UI system with `@meonode/ui` in a complex React ecosystem like Next.js.
231
+
232
+ [Example Usage Of React Meonode in NextJS](https://github.com/l7aromeo/react-meonode)
233
+
234
+ ---
235
+
673
236
  ## API Reference
674
237
 
675
238
  ### Core Functions
@@ -678,7 +241,7 @@ const Modal = Portal(ReduxProvider, ({ portal }) => {
678
241
  |-------------|---------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
679
242
  | `Node` | `element: NodeElement \| React.ComponentType`, `baseProps: object` | Constructs a configurable UI node that supports flexible properties and dynamic styling. |
680
243
  | `Component` | `(props: P) => ComponentNode` | Transforms node trees into reusable React components with built-in type safety and seamless integration. |
681
- | `Portal` | • `(component: (props: P) => ComponentNode)` or <br/> • `(providers: NodeElement \| NodeElement[], component: (props: P) => ComponentNode)` | Creates a React Portal component. Accepts either a component function directly, or a tuple of providers (e.g. Redux Provider) and the component. The component receives portal controls for mounting/unmounting. |
244
+ | `Portal` | • `(component: (props: P) => ComponentNode)` or <br/> • `(provider: NodeElement, component: (props: P) => ComponentNode)` | Creates a React Portal component. Accepts either a component function directly, or a provider (e.g. Redux Provider) and the component. The component receives portal controls for mounting/unmounting. |
682
245
 
683
246
  ---
684
247
 
@@ -698,5 +261,5 @@ For major changes, please open an issue first to discuss your proposal.
698
261
 
699
262
  ---
700
263
 
701
- **MIT Licensed** | Copyright © 2024 Ukasyah Rahmatullah Zada
264
+ **MIT Licensed** | Copyright © 2024 Ukasyah Rahmatullah Zada
702
265
  *Empowering developers to build better UIs*
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meonode/ui",
3
3
  "description": "A structured approach to component composition with built-in theming, prop separation, and dynamic children handling.",
4
- "version": "0.1.73",
4
+ "version": "0.1.74",
5
5
  "type": "module",
6
6
  "main": "./dist/main.js",
7
7
  "types": "./dist/main.d.ts",