@meonode/ui 0.1.2 → 0.1.3
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/README.md +239 -229
- package/package.json +2 -3
- package/docs/basic-usage.md +0 -63
- package/docs/conditional-component-with-hook.md +0 -68
package/README.md
CHANGED
|
@@ -3,284 +3,294 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@meonode/ui)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
**Build React UIs with Type-Safe Fluency**
|
|
7
|
+
A structured approach to component composition with built-in theming, prop separation, and dynamic children handling.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// Quick Start Example
|
|
11
|
+
import { Component, Div, H1, Button } from '@meonode/ui';
|
|
12
|
+
|
|
13
|
+
const BlueButton = Component((props) =>
|
|
14
|
+
Button({
|
|
15
|
+
padding: '12px 24px',
|
|
16
|
+
borderRadius: '8px',
|
|
17
|
+
backgroundColor: 'dodgerblue',
|
|
18
|
+
color: 'white',
|
|
19
|
+
...props
|
|
20
|
+
})
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const App = Component(() =>
|
|
24
|
+
Div({
|
|
25
|
+
padding: '40px',
|
|
26
|
+
children: [
|
|
27
|
+
H1({ fontSize: '2rem' }, 'Welcome to Meonode'),
|
|
28
|
+
BlueButton({
|
|
29
|
+
onClick: () => alert('Hello World!'),
|
|
30
|
+
children: 'Get Started'
|
|
31
|
+
})
|
|
32
|
+
]
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
```
|
|
12
36
|
|
|
13
|
-
##
|
|
37
|
+
## Why @meonode/ui?
|
|
14
38
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* **Flexible Children Management:** Supports various child types, including primitives, other `@meonode/ui` nodes,
|
|
21
|
-
standard React elements, and functions for dynamic rendering (function-as-child pattern).
|
|
22
|
-
* **Type-Safe:** Written entirely in TypeScript, providing excellent autocompletion and compile-time safety.
|
|
23
|
-
* **Seamless Integration:** Works with existing React components and fits naturally into any React workflow.
|
|
24
|
-
* **`Component` HOC:** A higher-order component to easily wrap functions that return `@meonode/ui` instances, making
|
|
25
|
-
them standard React components.
|
|
26
|
-
* **Pre-built HTML Element Components:** Offers a suite of convenience functions (e.g., `Div`, `Span`, `H1`, `Button`) that wrap `Node()` for common HTML tags, streamlining development.
|
|
39
|
+
- 🎯 **Type-Safe Design** - Full TypeScript support with autocomplete for styles and themes
|
|
40
|
+
- 🎨 **CSS-in-JS Without Runtime** - Write styles directly in props with theme references
|
|
41
|
+
- 🧩 **Component-First Architecture** - Compose UIs from structured nodes instead of JSX
|
|
42
|
+
- 🌐 **Theme Propagation** - Contextual theming that works with any component structure
|
|
43
|
+
- ⚡ **Zero Dependencies** - Lightweight core (under 15kb gzipped)
|
|
27
44
|
|
|
28
45
|
## Installation
|
|
29
46
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
yarn add react @meonode/ui
|
|
47
|
+
```bash
|
|
48
|
+
npm install @meonode/ui react
|
|
49
|
+
# or
|
|
50
|
+
yarn add @meonode/ui react
|
|
35
51
|
```
|
|
36
52
|
|
|
37
|
-
To use the built-in integration with Material UI, install the following:
|
|
38
|
-
|
|
39
|
-
```shell
|
|
40
|
-
yarn add react @mui/material @meonode/ui @meonode/mui
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
53
|
## Core Concepts
|
|
46
54
|
|
|
47
|
-
###
|
|
48
|
-
|
|
49
|
-
The primary factory function to create `@meonode/ui` instances (internally `BaseNode`).
|
|
50
|
-
|
|
51
|
-
* `element`: The React element type (e.g., `'div'`, `MyReactComponent`, another `Node` instance).
|
|
52
|
-
* `props`: An object containing properties for the element, including standard HTML attributes, event handlers, `children`, `theme`, and direct CSS style properties.
|
|
53
|
-
|
|
54
|
-
### `BaseNode`
|
|
55
|
-
|
|
56
|
-
The internal representation of a React element within `@meonode/ui`. It holds the element type, processed props (with styles and DOM attributes separated), and processed children. You typically don't interact with `BaseNode` directly but through the `Node` factory. Each `BaseNode` instance has a `render()` method to convert it into a renderable React element.
|
|
57
|
-
|
|
58
|
-
### `Component(componentFunction)`
|
|
59
|
-
|
|
60
|
-
A Higher-Order Component (HOC) that wraps a function. This function receives props and should return either a `@meonode/ui` instance (created via `Node()`) or a standard `ReactNode`. The `Component` HOC ensures that if a `@meonode/ui` instance is returned, its `render()` method is called, making it renderable by React.
|
|
61
|
-
|
|
62
|
-
### Theming
|
|
63
|
-
|
|
64
|
-
Pass a `theme` object via the `theme` prop to any `Node`. This theme becomes available to that `Node` and its descendants. Style properties can then reference theme values using a dot-path string:
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## Usage Examples
|
|
69
|
-
|
|
70
|
-
### 1. Basic Usage with `Node()`
|
|
71
|
-
|
|
72
|
-
### 2. Applying Styles Directly root Prop
|
|
73
|
-
|
|
74
|
-
`@meonode/ui` intelligently separates CSS properties from other props. You can provide CSS properties directly at the root of the `props` object.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
### 3. Using Themes
|
|
78
|
-
|
|
79
|
-
Themes allow for centralized styling and easy reuse of design tokens.
|
|
80
|
-
|
|
81
|
-
### 4. Handling Children
|
|
82
|
-
|
|
83
|
-
`@meonode/ui` offers flexible ways to define children:
|
|
84
|
-
|
|
85
|
-
**Note on Function Children and Themes:** When a function child returns a `BaseNode` instance (e.g., `() => Node(...)`), `@meonode/ui` ensures that the parent's theme is propagated to this returned `BaseNode` if it doesn't already have its own theme. This allows `BaseNode`s created within the function to resolve theme-based styles like `color: 'theme.colors.highlight'`.
|
|
55
|
+
### 1. Component Creation
|
|
86
56
|
|
|
87
|
-
|
|
57
|
+
Create elements using the `Node` factory or pre-built components:
|
|
88
58
|
|
|
89
|
-
|
|
59
|
+
```tsx
|
|
60
|
+
import { Node, Div, H1 } from '@meonode/ui';
|
|
90
61
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
62
|
+
// Using Node factory
|
|
63
|
+
const Card = Node('div', {
|
|
64
|
+
padding: '20px',
|
|
65
|
+
borderRadius: '8px',
|
|
66
|
+
boxShadow: '0 2px 12px rgba(0,0,0,0.1)'
|
|
67
|
+
});
|
|
97
68
|
|
|
98
|
-
|
|
69
|
+
// Using pre-built components
|
|
70
|
+
const Header = () =>
|
|
71
|
+
Div({
|
|
72
|
+
padding: '20px',
|
|
73
|
+
backgroundColor: 'navy',
|
|
74
|
+
children: H1({ color: 'white' }, 'App Header')
|
|
75
|
+
});
|
|
76
|
+
```
|
|
99
77
|
|
|
100
|
-
|
|
78
|
+
### 2. Theming System
|
|
101
79
|
|
|
102
|
-
|
|
103
|
-
import { Component, Div, H1, P, Button } from '@meonode/ui';
|
|
80
|
+
Define and consume themes with dot-notation:
|
|
104
81
|
|
|
105
|
-
|
|
106
|
-
const
|
|
82
|
+
```tsx
|
|
83
|
+
const theme = {
|
|
107
84
|
colors: {
|
|
108
|
-
primary: '
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
85
|
+
primary: '#2196F3',
|
|
86
|
+
text: {
|
|
87
|
+
primary: '#1A237E',
|
|
88
|
+
secondary: '#455A64'
|
|
89
|
+
}
|
|
113
90
|
},
|
|
114
91
|
spacing: {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
large: '24px',
|
|
118
|
-
},
|
|
119
|
-
typography: {
|
|
120
|
-
h1Size: '2.5rem',
|
|
121
|
-
pSize: '1rem',
|
|
122
|
-
},
|
|
123
|
-
borders: {
|
|
124
|
-
radius: '4px',
|
|
125
|
-
thin: '1px solid #cccccc',
|
|
92
|
+
md: '16px',
|
|
93
|
+
lg: '24px'
|
|
126
94
|
}
|
|
127
95
|
};
|
|
128
96
|
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
theme
|
|
132
|
-
padding: 'theme.spacing.
|
|
133
|
-
backgroundColor: 'theme.colors.
|
|
134
|
-
borderRadius: 'theme.borders.radius',
|
|
135
|
-
boxShadow: '0 4px 8px rgba(0,0,0,0.1)',
|
|
136
|
-
maxWidth: '400px',
|
|
137
|
-
margin: 'theme.spacing.medium auto', // Center the card
|
|
138
|
-
border: 'theme.borders.thin',
|
|
97
|
+
const ThemedCard = Component(() =>
|
|
98
|
+
Div({
|
|
99
|
+
theme,
|
|
100
|
+
padding: 'theme.spacing.lg',
|
|
101
|
+
backgroundColor: 'theme.colors.primary',
|
|
139
102
|
children: [
|
|
140
|
-
H1({
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}),
|
|
146
|
-
P({
|
|
147
|
-
fontSize: 'theme.typography.pSize',
|
|
148
|
-
color: 'theme.colors.text',
|
|
149
|
-
lineHeight: '1.6',
|
|
150
|
-
marginBottom: 'theme.spacing.large',
|
|
151
|
-
children: 'This card is built using pre-built HTML components from @meonode/ui, styled with a custom theme.',
|
|
152
|
-
}),
|
|
153
|
-
Button({
|
|
154
|
-
backgroundColor: 'theme.colors.primary',
|
|
155
|
-
color: 'theme.colors.textOnPrimary',
|
|
156
|
-
padding: 'theme.spacing.small theme.spacing.medium',
|
|
157
|
-
border: 'none',
|
|
158
|
-
borderRadius: 'theme.borders.radius',
|
|
159
|
-
fontSize: 'theme.typography.pSize',
|
|
160
|
-
cursor: 'pointer',
|
|
161
|
-
children: 'Learn More',
|
|
162
|
-
onClick: () => alert('Button clicked!'),
|
|
163
|
-
// Example of hover style (though direct CSS-in-JS hover is more complex without a helper)
|
|
164
|
-
// For simple cases, you might rely on global CSS or a more advanced styling solution.
|
|
165
|
-
// This is a placeholder to show where you might think about interactions.
|
|
166
|
-
// Real hover effects would typically be handled by CSS classes or a more robust styling library.
|
|
167
|
-
}),
|
|
168
|
-
],
|
|
169
|
-
});
|
|
170
|
-
});
|
|
103
|
+
H1({ color: 'theme.colors.text.primary' }, 'Themed Title'),
|
|
104
|
+
P({ color: 'theme.colors.text.secondary' }, 'Content...')
|
|
105
|
+
]
|
|
106
|
+
})
|
|
107
|
+
);
|
|
171
108
|
```
|
|
172
109
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
110
|
+
### 3. Prop Handling
|
|
111
|
+
|
|
112
|
+
Automatic separation of CSS props from DOM attributes:
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
const ProfileCard = Component(({ user }) =>
|
|
116
|
+
Div({
|
|
117
|
+
// CSS Props
|
|
118
|
+
padding: '20px',
|
|
119
|
+
borderRadius: '8px',
|
|
120
|
+
// DOM Props
|
|
121
|
+
ariaRole: 'article',
|
|
122
|
+
tabIndex: 0,
|
|
123
|
+
// Children
|
|
124
|
+
children: `Welcome ${user.name}!`
|
|
125
|
+
})
|
|
126
|
+
);
|
|
180
127
|
```
|
|
181
128
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
## Usage Implementation
|
|
185
|
-
|
|
186
|
-
The example implementation can be seen in the docs folder:
|
|
187
|
-
1. [Basic Usage](./docs/basic-usage.md)
|
|
188
|
-
2. [Conditional Component With Hook](./docs/conditional-component-with-hook.md)
|
|
189
|
-
|
|
190
|
-
---
|
|
191
|
-
|
|
192
|
-
## API Overview
|
|
193
|
-
|
|
194
|
-
### `Node<E extends NodeElement>(element: E, props: Partial<NodeProps<E>> = {}): BaseNodeInstance<E>`
|
|
129
|
+
## Key Features
|
|
195
130
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
131
|
+
| Feature | Description |
|
|
132
|
+
|------------------------|-----------------------------------------------------------------------------|
|
|
133
|
+
| **Smart Prop Merge** | CSS properties are automatically merged with style object |
|
|
134
|
+
| **Theme Resolution** | `theme.` references resolve through component hierarchy |
|
|
135
|
+
| **Type Safety** | Autocomplete for CSS properties and theme paths |
|
|
136
|
+
| **HOC Support** | Wrap existing components with `Component()` for seamless integration |
|
|
137
|
+
| **Dynamic Children** | Function-as-child pattern with automatic theme propagation |
|
|
200
138
|
|
|
201
|
-
|
|
139
|
+
## Advanced Usage
|
|
202
140
|
|
|
203
|
-
|
|
204
|
-
* `component`: A function that accepts props and returns a `BaseNodeInstance` or any `ReactNode`.
|
|
205
|
-
* Returns: A standard React functional component.
|
|
141
|
+
### Component Composition
|
|
206
142
|
|
|
207
|
-
|
|
143
|
+
```tsx
|
|
144
|
+
const Dashboard = Component(() =>
|
|
145
|
+
Div({
|
|
146
|
+
display: 'grid',
|
|
147
|
+
gridTemplateColumns: '1fr 3fr',
|
|
148
|
+
gap: '20px',
|
|
149
|
+
children: [
|
|
150
|
+
Sidebar({
|
|
151
|
+
width: '240px',
|
|
152
|
+
items: navItems
|
|
153
|
+
}),
|
|
154
|
+
MainContent({
|
|
155
|
+
padding: '40px',
|
|
156
|
+
children: AnalyticsChart({ dataset })
|
|
157
|
+
})
|
|
158
|
+
]
|
|
159
|
+
})
|
|
160
|
+
);
|
|
161
|
+
```
|
|
208
162
|
|
|
209
|
-
|
|
210
|
-
* Converts the internal `BaseNode` representation into a standard, renderable React Node tree using `React.createElement`.
|
|
163
|
+
### With Conditional Children That Contains Hook
|
|
211
164
|
|
|
212
|
-
|
|
165
|
+
also add this
|
|
213
166
|
|
|
214
|
-
|
|
167
|
+
```ts
|
|
215
168
|
|
|
216
|
-
|
|
217
|
-
|
|
169
|
+
// Wraps a hook-capable component into a BaseNode-compatible Client Component
|
|
170
|
+
import { Component, Column, Row, Div, P } from '@meonode/ui'
|
|
171
|
+
import { useState, useEffect } from 'react'
|
|
172
|
+
|
|
173
|
+
// Shared theme object passed into components. This may be written in a different file and imported.
|
|
174
|
+
const theme = {
|
|
175
|
+
background: { primary: 'lightgreen', secondary: 'lightyellow' },
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Exported component rendered via <Component /> (client wrapper)
|
|
179
|
+
export default Component(() => {
|
|
180
|
+
// React hook for conditional UI
|
|
181
|
+
const [showDetails, setShowDetails] = useState(false)
|
|
182
|
+
|
|
183
|
+
// Declarative layout using Column as root container
|
|
184
|
+
return Column({
|
|
185
|
+
theme,
|
|
186
|
+
padding: 20,
|
|
187
|
+
gap: 15,
|
|
188
|
+
children: [
|
|
189
|
+
// Header row with a toggle button
|
|
190
|
+
Row({
|
|
191
|
+
gap: 10,
|
|
192
|
+
children: [
|
|
193
|
+
Div({
|
|
194
|
+
onClick: () => setShowDetails(prev => !prev),
|
|
195
|
+
style: {
|
|
196
|
+
cursor: 'pointer',
|
|
197
|
+
userSelect: 'none',
|
|
198
|
+
padding: '10px 20px',
|
|
199
|
+
backgroundColor: 'theme.background.primary', // Node engine will handle this
|
|
200
|
+
borderRadius: 5,
|
|
201
|
+
fontWeight: 'bold',
|
|
202
|
+
},
|
|
203
|
+
children: showDetails ? 'Hide Details' : 'Show Details',
|
|
204
|
+
}),
|
|
205
|
+
],
|
|
206
|
+
}),
|
|
218
207
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
208
|
+
// Conditionally render DetailComponent via function wrapper
|
|
209
|
+
// Ensures it's treated as a renderable function (deferred React class or element that is NOT called directly)
|
|
210
|
+
// Node engine will handle this like magic
|
|
211
|
+
showDetails && (() => DetailComponent({ info: 'Here are some details!' })), // Works like `Component(() => DetailComponent({ info: 'Here some details!' }))`,
|
|
212
|
+
showDetails && DetailComponent({ info: 'Here are some details!' })).render() // Works
|
|
213
|
+
],
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
// A stateful detail section using useEffect and styled Div
|
|
218
|
+
const DetailComponent = ({ info }: { info: string }) => {
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
console.log('DetailComponent mounted')
|
|
221
|
+
return () => {
|
|
222
|
+
console.log('DetailComponent unmounted')
|
|
223
|
+
}
|
|
224
|
+
}, [])
|
|
225
225
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
226
|
+
return Div({
|
|
227
|
+
padding: 15,
|
|
228
|
+
border: '1px solid green',
|
|
229
|
+
borderRadius: 6,
|
|
230
|
+
backgroundColor: 'theme.background.secondary', // Node engine will handle this
|
|
231
|
+
children: P({ children: info }),
|
|
232
|
+
})
|
|
233
|
+
}
|
|
232
234
|
|
|
233
|
-
|
|
234
|
-
- The `render()` method on a `BaseNode` instance recursively traverses its structure
|
|
235
|
-
- Calls `render()` on any child `BaseNode`s
|
|
236
|
-
- Ultimately uses `React.createElement` to construct the final tree of React elements
|
|
237
|
-
- The `nodeTheme` prop is removed before passing props to `createElement`
|
|
235
|
+
```
|
|
238
236
|
|
|
239
|
-
|
|
237
|
+
### Material UI Integration
|
|
240
238
|
|
|
241
|
-
|
|
239
|
+
```bash
|
|
240
|
+
yarn add @meonode/mui @mui/material
|
|
241
|
+
```
|
|
242
242
|
|
|
243
|
-
|
|
243
|
+
```ts
|
|
244
|
+
import { MuiButton, MuiTextField } from '@meonode/mui';
|
|
244
245
|
|
|
245
|
-
|
|
246
|
+
const MuiLoginForm = Component(() =>
|
|
247
|
+
Div({
|
|
248
|
+
maxWidth: '400px',
|
|
249
|
+
margin: '0 auto',
|
|
250
|
+
children: [
|
|
251
|
+
MuiTextField({
|
|
252
|
+
label: 'Email',
|
|
253
|
+
fullWidth: true,
|
|
254
|
+
margin: 'normal'
|
|
255
|
+
}),
|
|
256
|
+
MuiTextField({
|
|
257
|
+
label: 'Password',
|
|
258
|
+
type: 'password',
|
|
259
|
+
fullWidth: true,
|
|
260
|
+
margin: 'normal'
|
|
261
|
+
}),
|
|
262
|
+
MuiButton({
|
|
263
|
+
variant: 'contained',
|
|
264
|
+
color: 'primary',
|
|
265
|
+
children: 'Sign In'
|
|
266
|
+
})
|
|
267
|
+
]
|
|
268
|
+
})
|
|
269
|
+
);
|
|
270
|
+
```
|
|
246
271
|
|
|
247
|
-
|
|
272
|
+
## API Reference
|
|
248
273
|
|
|
249
|
-
|
|
274
|
+
### Core Functions
|
|
250
275
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
* Great alternative to JSX when you prefer a more functional programming approach
|
|
276
|
+
| Function | Parameters | Description |
|
|
277
|
+
|----------------|-----------------------------------------|-------------------------------------------------|
|
|
278
|
+
| `Node` | `element: string \| Component`, `props` | Creates a new UI node |
|
|
279
|
+
| `Component` | `(props) => Node \| ReactNode` | Converts node trees to React components |
|
|
256
280
|
|
|
257
|
-
---
|
|
258
281
|
|
|
259
282
|
## Contributing
|
|
260
283
|
|
|
261
|
-
|
|
284
|
+
We welcome contributions! Please follow these steps:
|
|
285
|
+
1. Fork the repository
|
|
286
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
287
|
+
3. Commit your changes
|
|
288
|
+
4. Push to the branch
|
|
289
|
+
5. Open a Pull Request
|
|
262
290
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
## [License](https://github.com/l7aromeo/meonode-ui/blob/main/LICENSE)
|
|
266
|
-
|
|
267
|
-
The MIT License (MIT)
|
|
268
|
-
Copyright (c) 2025 Ukasyah Rahmatullah Zada
|
|
291
|
+
Contact me on [Discord](https://discordapp.com/users/704803255561224264) for discussions.
|
|
269
292
|
|
|
270
|
-
|
|
271
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
272
|
-
in the Software without restriction, including without limitation the rights
|
|
273
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
274
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
275
|
-
furnished to do so, subject to the following conditions:
|
|
276
|
-
|
|
277
|
-
The above copyright notice and this permission notice shall be included in all
|
|
278
|
-
copies or substantial portions of the Software.
|
|
293
|
+
---
|
|
279
294
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
283
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
284
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
285
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
286
|
-
SOFTWARE.
|
|
295
|
+
**MIT Licensed** | Copyright © 2024 Ukasyah Rahmatullah Zada
|
|
296
|
+
*Empowering developers to build better UIs*
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meonode/ui",
|
|
3
|
-
"description": "
|
|
4
|
-
"version": "0.1.
|
|
3
|
+
"description": "A structured approach to component composition with built-in theming, prop separation, and dynamic children handling.",
|
|
4
|
+
"version": "0.1.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/main.js",
|
|
7
7
|
"types": "./dist/main.d.ts",
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"dist",
|
|
16
|
-
"docs",
|
|
17
16
|
"package.json",
|
|
18
17
|
"LICENSE",
|
|
19
18
|
"README.md"
|
package/docs/basic-usage.md
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
```ts
|
|
2
|
-
// Component wraps a React Server Component into a Client Component
|
|
3
|
-
// Think of it like a factory that transforms JSX-like node definitions into actual React components
|
|
4
|
-
import { Component, Column, Row, Div, Img, P } from '@meonode/ui'
|
|
5
|
-
|
|
6
|
-
const theme = { background: { primary: 'red', secondary: 'blue' } }
|
|
7
|
-
|
|
8
|
-
// This is the actual exported component
|
|
9
|
-
export default Component(() => {
|
|
10
|
-
return Card({
|
|
11
|
-
title: 'Genshin Impact',
|
|
12
|
-
subtitle: 'Adventure awaits!',
|
|
13
|
-
imageUrl: 'https://upload-os-bbs.mihoyo.com/upload/2021/03/05/75387538/f37ce39baf72ffb84cb5c0040bdcbf10_2126420710320647353.jpg',
|
|
14
|
-
})
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
// This function returns a structured layout using html.node helpers only
|
|
18
|
-
const Card = ({ title, subtitle, imageUrl }: { title: string; subtitle: string; imageUrl: string }) =>
|
|
19
|
-
Column({
|
|
20
|
-
theme,
|
|
21
|
-
// Column is just a <div> with `display: flex; flex-direction: column`
|
|
22
|
-
borderRadius: 10,
|
|
23
|
-
overflow: 'hidden',
|
|
24
|
-
border: '1px solid theme.background.primary', // Can use theme references
|
|
25
|
-
width: 300,
|
|
26
|
-
children: [
|
|
27
|
-
// Img translates to a real <img> tag
|
|
28
|
-
Img({
|
|
29
|
-
src: imageUrl,
|
|
30
|
-
width: '100%',
|
|
31
|
-
height: 180,
|
|
32
|
-
style: {
|
|
33
|
-
objectFit: 'cover', // Ensures the image fills the area without distortion
|
|
34
|
-
},
|
|
35
|
-
}),
|
|
36
|
-
// Div is a basic <div> wrapper
|
|
37
|
-
Div({
|
|
38
|
-
padding: 10,
|
|
39
|
-
backgroundColor: 'theme.background.secondary',
|
|
40
|
-
children: Column({
|
|
41
|
-
gap: 5,
|
|
42
|
-
children: [
|
|
43
|
-
// P maps directly to <p>, with styling
|
|
44
|
-
P({
|
|
45
|
-
style: {
|
|
46
|
-
fontSize: 18,
|
|
47
|
-
fontWeight: 600,
|
|
48
|
-
},
|
|
49
|
-
children: title,
|
|
50
|
-
}),
|
|
51
|
-
P({
|
|
52
|
-
style: {
|
|
53
|
-
fontSize: 14,
|
|
54
|
-
color: 'gray',
|
|
55
|
-
},
|
|
56
|
-
children: subtitle,
|
|
57
|
-
}),
|
|
58
|
-
],
|
|
59
|
-
}),
|
|
60
|
-
}),
|
|
61
|
-
],
|
|
62
|
-
})
|
|
63
|
-
```
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
```ts
|
|
2
|
-
|
|
3
|
-
// Wraps a hook-capable component into a BaseNode-compatible Client Component
|
|
4
|
-
import { Component, Column, Row, Div, P } from '@meonode/ui'
|
|
5
|
-
import { useState, useEffect } from 'react'
|
|
6
|
-
|
|
7
|
-
// Shared theme object passed into components. This may be written in a different file and imported.
|
|
8
|
-
const theme = {
|
|
9
|
-
background: { primary: 'lightgreen', secondary: 'lightyellow' },
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// Exported component rendered via <Component /> (client wrapper)
|
|
13
|
-
export default Component(() => {
|
|
14
|
-
// React hook for conditional UI
|
|
15
|
-
const [showDetails, setShowDetails] = useState(false)
|
|
16
|
-
|
|
17
|
-
// Declarative layout using Column as root container
|
|
18
|
-
return Column({
|
|
19
|
-
theme,
|
|
20
|
-
padding: 20,
|
|
21
|
-
gap: 15,
|
|
22
|
-
children: [
|
|
23
|
-
// Header row with a toggle button
|
|
24
|
-
Row({
|
|
25
|
-
gap: 10,
|
|
26
|
-
children: [
|
|
27
|
-
Div({
|
|
28
|
-
onClick: () => setShowDetails(prev => !prev),
|
|
29
|
-
style: {
|
|
30
|
-
cursor: 'pointer',
|
|
31
|
-
userSelect: 'none',
|
|
32
|
-
padding: '10px 20px',
|
|
33
|
-
backgroundColor: 'theme.background.primary', // Node engine will handle this
|
|
34
|
-
borderRadius: 5,
|
|
35
|
-
fontWeight: 'bold',
|
|
36
|
-
},
|
|
37
|
-
children: showDetails ? 'Hide Details' : 'Show Details',
|
|
38
|
-
}),
|
|
39
|
-
],
|
|
40
|
-
}),
|
|
41
|
-
|
|
42
|
-
// Conditionally render DetailComponent via function wrapper
|
|
43
|
-
// Ensures it's treated as a renderable function (deferred React class or element that is NOT called directly)
|
|
44
|
-
// Node engine will handle this like magic
|
|
45
|
-
showDetails ? () => DetailComponent({ info: 'Here are some details!' }) : null,
|
|
46
|
-
],
|
|
47
|
-
})
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
// A stateful detail section using useEffect and styled Div
|
|
51
|
-
const DetailComponent = ({ info }: { info: string }) => {
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
console.log('DetailComponent mounted')
|
|
54
|
-
return () => {
|
|
55
|
-
console.log('DetailComponent unmounted')
|
|
56
|
-
}
|
|
57
|
-
}, [])
|
|
58
|
-
|
|
59
|
-
return Div({
|
|
60
|
-
padding: 15,
|
|
61
|
-
border: '1px solid green',
|
|
62
|
-
borderRadius: 6,
|
|
63
|
-
backgroundColor: 'theme.background.secondary', // Node engine will handle this
|
|
64
|
-
children: P({ children: info }),
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
```
|