Axii is a javascript UI framework. The main features are:
The following contents will give you a tutorial to Axii basic usage. More documents is working in progress, help will be appreciated. Thanks to creators of React and Vue.
atom
to make non-object data like string
/number
/boolean
reactive. Object which do not need deep watch can use it too..value
to get and set value of data created by using atom
. Don't use .value
to bind data to element property.computed
to create computed value./** @jsx createElement */
import { createElement, atom, computed } from 'axii'
export default function Basic() {
const firstName = atom('')
const lastName = atom('')
const fullName = computed(() => {
if (!firstName.value && !lastName.value) return 'please enter your name'
return `${firstName.value}-${lastName.value}`
})
const setFirstName = (e) => {
firstName.value = e.target.value
}
const setLastName = (e) => {
lastName.value = e.target.value
}
return (
<div>
<input value={firstName} placeholder="firstName" onInput={setFirstName}/>
<input value={lastName} placeholder="lastName" onInput={setLastName}/>
<div>Hello, {fullName}.</div>
</div>
)
}
Use function to represent a dynamic structure such as repeated structure or conditional structure. (Axii will auto detect dynamic structure expression in jsx in the future, so user do not need to use function to declare.)
/** @jsx createElement */
import {createElement, atom, reactive} from 'axii'
export default function Code() {
const inputItem = atom('')
const items = reactive([])
const setInputItem = (e) => inputItem.value = e.target.value
const addItem = () => {
items.push(inputItem.value)
inputItem.value = ''
}
const removeItem = (index) => {
items.splice(index, 1)
}
return (
<div>
<input value={inputItem} placeholder="plan something" onInput={setInputItem}/>
<button onClick={addItem}>add</button>
<ul>
{function List() {
return items.map((item, i) => (
<li>
<span>{item}</span>
<button onClick={() => removeItem(i)}>x</button>
</li>
))
}}
</ul>
</div>
)
}
Axii provides an API called createComponent
to help user create better component with:
Layout Style refers to position or size related style, such as display
/width
/height
.
Axii introduces a special attribute type called Layout Attribute to help user deal with layout style. Here are the guidelines of how to use Layout Attribute:
div
or span
.text
/inline
/block
to declare layout type. Axii will apply display: inline
to text
type and display: inline-block
to inline
type.flex-display
or extend display by yourself to handle complex layout(Docs upcoming).width
/heigth
with layout type as prefix.import { createElement } from 'axii'
export default function Code() {
return (
<todoList block block-width="100%">
<todoItem block flex-display flex-justify-content="space-between">
<name>swimming</name>
<action>delete</action>
</todoItem>
<todoItem block flex-display flex-justify-content-space-between>
<name>swimming</name>
<action>delete</action>
</todoItem>
</todoList>
)
}
Layout attribute can be passed to Component to control component box style. Simply add layout:
namespace to attribute.
import { createElement } from 'axii'
function Box() {
return <div style={{background: '#cecece'}}>box</div>
}
export default function Code() {
return (
<container>
<line block>
<Box layout:inline/>
</line>
<line block block-margin-top-10px>
<Box layout:inline layout:inline-width-300px/>
</line>
<line block block-margin-top-10px>
<Box layout:block/>
</line>
</container>
)
}
Non-Layout style refer to layout irrelated style such as color
/font-family
.
User can write better non-layout style by implementing feature.
/** @jsx createElement */
import { createElement, atom, createComponent, propTypes } from 'axii'
function Component({ onFocus, onBlur}) {
return <container>
<line block>
<box use="input" onFocus={onFocus} onBlur={onBlur}/>
</line>
<line block>
<textLink use="a" href="#">a link</textLink>
</line>
</container>
}
Component.Style = (fragments) => {
fragments.root.elements.box.style(({focused}) => {
return {
borderStyle: 'solid',
borderWidth: 1,
outline: 'none',
borderColor: focused.value ? 'blue': 'black'
}
})
// pseudo class
fragments.root.elements.textLink.match.hover.style({
color: 'green'
})
}
Component.Style.propTypes = {
focused: propTypes.bool.default(() => atom(false)),
onFocus: propTypes.callback.default(() => ({focused}) => focused.value = true),
onBlur: propTypes.callback.default(() => ({focused}) => focused.value = false)
}
export default createComponent(Component)
Component created by using createComponent
is enhanced with:
Component event callback declared in proptypes is more powerful than simple function property. It allows you:
return false
.overwrite
.Component state managing have three modes differed by passing different type of data, See details below:
/** @jsx createElement */
import { createElement, atom, propTypes } from 'axii'
export default function Input({ value, onChange}) {
return <input value={value} onInput={onChange}/>
}
Input.propTypes = {
value: propTypes.string.default(() => atom('')),
onChange: propTypes.callback.default(() => ({value}, props, e) => {
value.value = e.target.value
})
}
Passing in non-reactive data makes component fully controlled by upper scope. Component default event callback will not mutate state.
/** @jsx createElement */
import { createElement } from 'axii'
import Input from './Input.jsx'
export default function ControlledComponent() {
const value = 'controlled value'
return <Input value={value}/>
}
If no data passed in, component will create and mutate state according to proptypes.
/** @jsx createElement */
import { createElement } from 'axii'
import Input from './Input.jsx'
export default function InputDemo() {
const onChange = () => {
}
return <Input onChange={onChange}/>
}
If pass in reactive data, component will mutate it with event callback defined in proptypes.
/** @jsx createElement */
import { createElement, atom } from 'axii'
import Input from './Input.jsx'
export default function InputDemo() {
const value = atom('')
return (
<container>
<Input value={value}/>
<div>{() => `value: ${value.value}`}</div>
</container>
)
}
Axii feature mechanism help you split component code by features, not structure. A feature can:
/** @jsx createElement */
import { createElement, createComponent, atom, propTypes } from 'axii'
function Input({ value, onChange}) {
return <input value={value} onInput={onChange}/>
}
Input.propTypes = {
value: propTypes.string.default(() => atom('')),
onChange: propTypes.callback.default(() => ({value}, props, e) => {
value.value = e.target.value
})
}
function InputStyle(fragments) {
fragments.root.elements.input.onFocus((e, {onFocus}) => {
onFocus()
})
fragments.root.elements.input.onBlur((e, {onBlur}) => {
onBlur()
})
fragments.root.elements.input.style(({focused}) => {
return {
borderStyle: 'solid',
borderWidth: 1,
outline: 'none',
borderColor: focused.value ? 'blue': 'black'
}
})
}
InputStyle.propTypes = {
focused: propTypes.string.default(() => atom(false)),
onFocus: propTypes.callback.default(() => ({focused}) => focused.value = true),
onBlur: propTypes.callback.default(() => ({focused}) => focused.value = false),
}
export const InputWithStyle = createComponent(Input, [InputStyle])
export default function InputDemo() {
return <InputWithStyle />
}
User can use Component.extend
to extend component by self, no extra support of developer needed.
/** @jsx createElement */
import { createElement } from 'axii'
import { InputWithStyle } from "./FeatureBasedInput.jsx";
const InputWithCustomStyle = InputWithStyle.extend(function CustomStyle(fragments){
fragments.root.elements.input.style(({focused}) => ({
borderColor: focused.value ? 'red' : 'black'
}))
})
export default function InputDemo() {
return <InputWithCustomStyle />
}
npx degit ariesate/engine/packages/vite-axii my-project
cd my-project
npm install
npm start
npm install axii-components --save
npm install axii-icons --save